Skip to content

feat: expose Platform-to-Shielded capacity preflight - #4360

Open
llbartekll wants to merge 2 commits into
v4.2-devfrom
codex/fix-platform-to-shielded-max
Open

feat: expose Platform-to-Shielded capacity preflight#4360
llbartekll wants to merge 2 commits into
v4.2-devfrom
codex/fix-platform-to-shielded-max

Conversation

@llbartekll

@llbartekll llbartekll commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a cached, account-aware Platform Payment shield-capacity planner shared by preflight and execution
  • expose the preflight result through Rust, C FFI, and Swift SDK APIs
  • enforce the versioned minimum-input rules and map live insufficient-capacity races to a typed error

Why

The wallet previously treated the displayed Platform balance as fully shieldable. A Platform Payment account may need to retain protocol reserve and may contain inputs that cannot participate in the transition, so sending the displayed maximum could fail with Insufficient shielded balance.

Companion iOS PR: dashpay/dashwallet-ios#971

Testing

  • cargo test -p platform-wallet --features shielded shield_input_selection_tests (11 passed)
  • cargo test -p platform-wallet --features shielded shield_input_fetch_error_tests (1 passed)
  • cargo test -p platform-wallet-ffi --features shielded shield (15 passed)
  • SwiftDashSDK ErrorHandlingTests (45 passed)
  • cargo fmt --all -- --check
  • rebuilt the simulator XCFramework and built the Swift SDK example

Summary by CodeRabbit

  • New Features

    • Added shielded transaction preflight checks showing account balance, reserved fees, maximum shieldable amount, availability, and advisory reasons.
    • Preflight checks work without signing, broadcasting, or coordinator access.
    • Added typed handling for insufficient shielded capacity errors across SDKs.
  • Bug Fixes

    • Improved shielded input selection, fee reservation, minimum-balance handling, and arithmetic-overflow reporting.
    • Clarified address ordering and capacity calculations.
  • Tests

    • Added coverage for preflight validation, capacity reporting, error mapping, overflow, dust handling, and input selection.

@thepastaclaw

thepastaclaw commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — next in queue (commit d958b28)
Queue position: 1/1

@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 53312b42-7b25-4314-84fe-dea7c29b5c4e

📥 Commits

Reviewing files that changed from the base of the PR and between 5f9af28 and d958b28.

📒 Files selected for processing (12)
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet-ffi/src/shielded_types.rs
  • packages/rs-platform-wallet/src/error.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/mod.rs
  • packages/rs-platform-wallet/src/wallet/platform_addresses/wallet.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/rs-platform-wallet/src/wallet/shielded/operations.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
 ___________________________________________________________________________________________
< Optimism is an occupational hazard of programming; feedback is the treatment. - Kent Beck >
 -------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

The wallet now plans shield capacity from cached payment-account state, exposes that plan through Rust FFI and Swift, and maps shield-specific insufficient balance and arithmetic overflow errors to dedicated result codes.

Changes

Shielded preflight flow

Layer / File(s) Summary
Wallet planning and execution
packages/rs-platform-wallet/src/wallet/platform_wallet.rs, packages/rs-platform-wallet/src/lib.rs, packages/rs-platform-wallet/src/wallet/mod.rs
The wallet adds shared input planning, fee reservation, capacity reporting, overflow checks, and planner-backed shield execution. Tests cover ordering, dust, minimum amounts, overflow, and zero capacity.
Typed shield balance errors
packages/rs-platform-wallet/src/wallet/shielded/operations.rs
Shield address shortfalls now use typed errors with available and required balances. Other fetch failures retain generic shield build errors.
FFI preflight and result contract
packages/rs-platform-wallet-ffi/src/shielded_types.rs, packages/rs-platform-wallet-ffi/src/shielded_send.rs, packages/rs-platform-wallet-ffi/src/error.rs
The FFI adds the preflight data structure and wallet-only entry point. Shield insufficient balance maps to result code 41, while input-sum overflow maps to arithmetic overflow.
Swift API and typed errors
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swift, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift, packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift
Swift exposes asynchronous preflight results and maps result code 41 to a typed error. Tests verify raw values, messages, and invalid-handle behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SwiftClient
  participant PlatformWalletManagerShieldedSync
  participant ShieldedShieldPreflightFFI
  participant PlatformWallet
  SwiftClient->>PlatformWalletManagerShieldedSync: shieldedShieldPreflight(walletId, paymentAccount)
  PlatformWalletManagerShieldedSync->>ShieldedShieldPreflightFFI: invoke preflight
  ShieldedShieldPreflightFFI->>PlatformWallet: resolve wallet and calculate cached capacity
  PlatformWallet-->>ShieldedShieldPreflightFFI: capacity fields and reason
  ShieldedShieldPreflightFFI-->>PlatformWalletManagerShieldedSync: FFI preflight result
  PlatformWalletManagerShieldedSync-->>SwiftClient: ShieldedShieldPreflight or typed error
Loading

Possibly related PRs

Suggested reviewers: quantumexplorer, shumkov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the pull request's main change: exposing Platform-to-Shielded capacity preflight.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-platform-to-shielded-max

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/rs-platform-wallet-ffi/src/shielded_send.rs (1)

599-610: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the operation string gate with a shared constant.

The dedicated code depends on operation == "shielded shield". The only producer is the literal passed at the shield call site. If that literal is edited (for example to "shield" or "Platform → shielded"), this arm stops matching and the error silently regresses to ErrorShieldedInsufficientBalanceErrorWalletOperation. The compiler reports nothing.

Bind the label once and reuse it at both places.

♻️ Proposed refactor
+/// Operation label for the Platform-to-shielded entry point. The
+/// `ShieldedInsufficientBalance` arm below keys on this exact value.
+const SHIELD_OPERATION: &str = "shielded shield";
+
 fn map_spend_result(
     result: Result<(), PlatformWalletError>,
     operation: &str,
 ) -> PlatformWalletFFIResult {
         Err(e @ PlatformWalletError::ShieldedInsufficientBalance { .. })
-            if operation == "shielded shield" =>
+            if operation == SHIELD_OPERATION =>

At the shield entry point (outside this range):

    map_spend_result(result, SHIELD_OPERATION)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet-ffi/src/shielded_send.rs` around lines 599 - 610,
Define a shared constant for the shield operation label and use it both at the
shield entry point’s map_spend_result call and in the
ShieldedInsufficientBalance match guard within the operation-result mapping
logic. Replace the duplicated "shielded shield" literals with that constant
while preserving the existing error mapping behavior.
packages/rs-platform-wallet/src/wallet/platform_wallet.rs (1)

106-133: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a versioned invariant test for the shield input minimum. Current versions use min_input_amount = 100_000, while the two-action shield fee is 161_097_600; input 0 therefore clears the minimum after the fee reservation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/rs-platform-wallet/src/wallet/platform_wallet.rs` around lines 106 -
133, Add a versioned invariant test covering the shield input minimum, asserting
current versions use min_input_amount = 100_000 and the two-action shield fee is
161_097_600. Verify input 0 remains above the protocol minimum after fee
reservation, while preserving the existing behavior in the candidate-selection
loop.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/rs-platform-wallet-ffi/src/error.rs`:
- Around line 556-558: Update the documentation comment for the enum variant
assigned code 13, ErrorArithmeticOverflow, to remove the claim that it is
reserved or has no in-tree producer. Ensure the text states that
InputSumOverflow can map to this code, matching the mapping arm in
PlatformWalletError’s conversion.
- Around line 362-368: Record error code 41 in ERROR_CODE_REGISTRY.md and update
DashSdkError.fromPlatformWalletNative to map
PlatformWalletError.ShieldedInsufficientBalance explicitly instead of falling
back to PlatformWallet.Generic. Preserve the existing ABI code and use the
corresponding Kotlin error type for this platform payment-account shortfall.

---

Nitpick comments:
In `@packages/rs-platform-wallet-ffi/src/shielded_send.rs`:
- Around line 599-610: Define a shared constant for the shield operation label
and use it both at the shield entry point’s map_spend_result call and in the
ShieldedInsufficientBalance match guard within the operation-result mapping
logic. Replace the duplicated "shielded shield" literals with that constant
while preserving the existing error mapping behavior.

In `@packages/rs-platform-wallet/src/wallet/platform_wallet.rs`:
- Around line 106-133: Add a versioned invariant test covering the shield input
minimum, asserting current versions use min_input_amount = 100_000 and the
two-action shield fee is 161_097_600. Verify input 0 remains above the protocol
minimum after fee reservation, while preserving the existing behavior in the
candidate-selection loop.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 20359b11-f5ab-44a7-bbca-a55496c44408

📥 Commits

Reviewing files that changed from the base of the PR and between 86f3878 and 5f9af28.

📒 Files selected for processing (10)
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet-ffi/src/shielded_types.rs
  • packages/rs-platform-wallet/src/lib.rs
  • packages/rs-platform-wallet/src/wallet/mod.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/rs-platform-wallet/src/wallet/shielded/operations.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift

Comment thread packages/rs-platform-wallet-ffi/src/error.rs Outdated
Comment thread packages/rs-platform-wallet-ffi/src/error.rs

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preliminary review — Codex only

The shared planner improves fee-reserve and minimum-input handling, but it can advertise and select a maximum requiring more address inputs than the protocol permits, so the primary preflight guarantee is not yet reliable. The new planner also misses persisted funded addresses after relaunch, and the newly exposed error contract needs clearer funding-domain separation plus a Kotlin mirror update.
Source: reviewer backend model for all four reviewer lanes: gpt-5.6-sol; final verifier backend model: gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and is not reviewer evidence.

Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 1 blocking | 🟡 3 suggestion(s) | 💬 1 nitpick(s)

2 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-platform-wallet/src/wallet/platform_wallet.rs`:
- [BLOCKING] packages/rs-platform-wallet/src/wallet/platform_wallet.rs:180-192: Account for the versioned maximum address-input count
  The planner includes every qualifying address in the usable suffix and derives `max_shieldable_credits` from their complete balance, without applying `platform_version.dpp.state_transitions.max_address_inputs`. Platform rejects any address-based transition exceeding that limit; the active address-funds versions currently permit 16 inputs. A fragmented account can therefore receive `can_shield == true` and a maximum that `select_inputs(max_shieldable_credits)` represents with more than 16 inputs. The shield path fetches those inputs and then constructs the Orchard proof before the oversized transition is rejected. Pass the versioned input cap into the planner, derive capacity from an input set that can actually be represented under the cap, enforce the same cap during selection, and add a regression test with one more qualifying input than allowed.
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/platform_wallet.rs:1550-1560: Include hydrated address balances in the shield candidate set
  This enumerates only the transient `account.addresses.addresses` pool. The wallet's established `PlatformAddressWallet::candidate_address_set` deliberately unions that pool with `account.address_balances`, because the derived pool can be empty immediately after relaunch while persisted funded balances are already hydrated. In that state the new preflight reports zero capacity and the shared execution planner rejects shielding even though the displayed Platform balance includes funded addresses. Build a deduplicated union of derived addresses and `address_balances` keys before reading cached balances, and cover a funded address present only in `address_balances`.

In `packages/rs-platform-wallet/src/wallet/shielded/operations.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/shielded/operations.rs:131-138: Use a domain-specific error for Platform shield capacity
  `ShieldedInsufficientBalance` is the existing note-pool selection error for shielded transfer, unshield, withdrawal, and identity creation, but this change also returns it for a transparent Platform-address shortfall. That makes the Rust error's funding domain ambiguous and leaves its display text saying “Insufficient shielded balance” for a payment-account failure. The ambiguity propagates into FFI handling: the blanket conversion maps the shared variant to payment-account code 41, while `map_spend_result` must gate that code using the exact operation label `"shielded shield"` to keep note-pool failures generic. Add a distinct variant such as `PlatformShieldCapacityExceeded` for cached selection and live address-input shortfalls, map code 41 from that variant, and retain `ShieldedInsufficientBalance` exclusively for shielded notes.

In `packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt`:
- [SUGGESTION] packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt:503-527: Mirror the new shield-capacity error in Kotlin
  Code 41 now reaches the existing Kotlin `PlatformWalletManager.shieldedShield` path through `take_pwffi_error`, but `fromPlatformWalletNative` has no code-41 branch and converts it to `PlatformWallet.Generic`. This loses the typed payment-account-capacity outcome that Swift receives and prevents Kotlin callers from handling the newly documented refresh-and-reconfirm contract by error type. Add a dedicated `DashSdkError.PlatformWallet` subtype and map native code 41 to it. The separately cited `packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md` is not present as a tracked file at this head, so that part of the requested change cannot be applied at the stated path unless the registry is introduced separately.

In `packages/rs-platform-wallet-ffi/src/error.rs`:
- [NITPICK] packages/rs-platform-wallet-ffi/src/error.rs:113-116: Update the arithmetic-overflow code documentation
  The comment still says error code 13 is reserved and has no in-tree producer, but this PR adds an `InputSumOverflow` conversion to `ErrorArithmeticOverflow`. Update the comment so consumers of the FFI contract know that code 13 can now be returned when candidate input balances overflow `u64`.

Comment thread packages/rs-platform-wallet/src/wallet/platform_wallet.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/platform_wallet.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/shielded/operations.rs
@thepastaclaw

Copy link
Copy Markdown
Collaborator

The PR title checks are failing because the title is not Conventional Commits formatted. I do not have permission to edit this PR title; please rename it to feat: expose Platform-to-Shielded capacity preflight.

@llbartekll llbartekll changed the title Expose Platform-to-Shielded capacity preflight feat: expose Platform-to-Shielded capacity preflight Aug 10, 2026
@llbartekll

Copy link
Copy Markdown
Contributor Author

Addressed the remaining automated-review feedback in d958b28:

  • enforced versioned max_address_inputs in advertised capacity and execution, with a max-plus-one regression
  • included deduplicated hydrated balance-only addresses after relaunch
  • separated PlatformShieldCapacityExceeded from shielded-note insufficiency and removed the operation-string gate
  • mirrored native code 41 with a typed Kotlin error and test
  • corrected code 13 documentation and added the fee/minimum/reserve invariant test

Validation: planner 12/12, hydrated union, fee invariant, live-shortfall mapping, and focused FFI mappings passed; cargo fmt and git diff check passed. The Kotlin test is committed but could not run locally because this machine has no configured Android SDK, so CI remains the authoritative Kotlin run. The Conventional Commit title checks are green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants