Summary
Move the transaction-context and block-height eligibility decision for unconfirmed outgoing transaction replay from the Swift SDK into platform-wallet, exposed through rs-platform-wallet-ffi.
Swift must continue to load PersistentTxo rows, resolve SwiftData relationships, preserve legacy walletId routing, and marshal selected transaction records. Swift must not decide whether a spender context is settled for spend purposes.
Rationale
PlatformWalletPersistenceHandler.buildUnconfirmedOutgoingTxRecordBuffer currently checks spender.context and spender.blockHeight to decide whether a transaction is eligible for replay. These fields encode wallet-state semantics. The current alignment with spendIsInBlock reduces drift, but it does not remove duplicate policy.
A small Rust-owned FFI predicate can make platform-wallet the single owner of this semantic rule without requiring Swift to send every SwiftData relationship to Rust.
The write-side spendIsInBlock behavior and the restore-side eligibility behavior must move together. Otherwise Swift persistence and Rust restoration can disagree about whether an input remains unspent.
Affected areas
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
spendIsInBlock
buildUnconfirmedOutgoingTxRecordBuffer
packages/rs-platform-wallet
- Define the authoritative settled-spend / replay-eligibility rule.
packages/rs-platform-wallet-ffi
- Expose the minimal predicate required by Swift.
Required changes
- Add a
platform-wallet API that owns the context and block-height decision used to determine whether an outgoing spender remains eligible for replay.
- Expose that API through
rs-platform-wallet-ffi.
- Replace the Swift checks of
spender.context and spender.blockHeight with the FFI predicate.
- Keep Swift-owned persistence work in Swift:
- Find TXO-linked spenders.
- Check persisted relationship liveness and
isSpent.
- Route legacy TXO rows through their owning account when
walletId is absent.
- Exclude asset-lock funding transactions.
- Marshal records into
UnconfirmedOutgoingTxRecordFFI.
- Update the write-side
spendIsInBlock use so it obtains the same semantic result from the Rust-owned rule.
- Add Rust and Swift tests for mempool, InstantSend-locked, in-block, and invalid or inconsistent block-height combinations.
Acceptance criteria
platform-wallet is the only owner of transaction-context and block-height eligibility semantics for unconfirmed outgoing replay.
- Swift does not contain an independent policy condition over
PersistentTransaction.context or PersistentTransaction.blockHeight for this decision.
- The write path and restore path use the same Rust-owned rule.
- Swift retains only SwiftData selection, legacy-row routing, asset-lock exclusion, and FFI marshalling responsibilities.
- Tests cover the supported context cases and prevent a future divergence.
Backlinks
Summary
Move the transaction-context and block-height eligibility decision for unconfirmed outgoing transaction replay from the Swift SDK into
platform-wallet, exposed throughrs-platform-wallet-ffi.Swift must continue to load
PersistentTxorows, resolve SwiftData relationships, preserve legacywalletIdrouting, and marshal selected transaction records. Swift must not decide whether a spender context is settled for spend purposes.Rationale
PlatformWalletPersistenceHandler.buildUnconfirmedOutgoingTxRecordBuffercurrently checksspender.contextandspender.blockHeightto decide whether a transaction is eligible for replay. These fields encode wallet-state semantics. The current alignment withspendIsInBlockreduces drift, but it does not remove duplicate policy.A small Rust-owned FFI predicate can make
platform-walletthe single owner of this semantic rule without requiring Swift to send every SwiftData relationship to Rust.The write-side
spendIsInBlockbehavior and the restore-side eligibility behavior must move together. Otherwise Swift persistence and Rust restoration can disagree about whether an input remains unspent.Affected areas
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swiftspendIsInBlockbuildUnconfirmedOutgoingTxRecordBufferpackages/rs-platform-walletpackages/rs-platform-wallet-ffiRequired changes
platform-walletAPI that owns the context and block-height decision used to determine whether an outgoing spender remains eligible for replay.rs-platform-wallet-ffi.spender.contextandspender.blockHeightwith the FFI predicate.isSpent.walletIdis absent.UnconfirmedOutgoingTxRecordFFI.spendIsInBlockuse so it obtains the same semantic result from the Rust-owned rule.Acceptance criteria
platform-walletis the only owner of transaction-context and block-height eligibility semantics for unconfirmed outgoing replay.PersistentTransaction.contextorPersistentTransaction.blockHeightfor this decision.Backlinks