Skip to content

refactor(send): migrate Core sends from split setFunding/buildSigned to finalizeAtomic - #920

Merged
QuantumExplorer merged 1 commit into
swift-sdk-integrationfrom
feat/migrate-v2-atomic-send
Aug 6, 2026
Merged

refactor(send): migrate Core sends from split setFunding/buildSigned to finalizeAtomic#920
QuantumExplorer merged 1 commit into
swift-sdk-integrationfrom
feat/migrate-v2-atomic-send

Conversation

@QuantumExplorer

Copy link
Copy Markdown
Member

What was done?

The platform SDK's v1 split Core-send path (setFundingbuildSignedCoreTransactionbroadcastTransaction) is being removed upstream (dashpay/platform#4323): funding selection and signing race across the C ABI, so two concurrent same-account builds can select the same UTXO. This migrates every call site to the atomic v2 surface before that removal reaches the platform pin.

  • buildAndSign finalizes atomically (finalizeAtomic) and returns the single-shot FinalizedCoreTransaction; tx bytes come from serializedData() (feeds the BIP70 Payment POST and the txHash computation).
  • Deferred confirm-then-broadcast semantics are preserved: the handle holds the input reservation until broadcast, and discarding it (user backs out of the payment sheet, or a BIP70 POST-failure retry rebuilds) abandons the build Rust-side and returns the inputs to spendable — a rebuilt retry can no longer double-select.
  • Broadcast goes through broadcastTransactionWithOutcome; rejected/unknown outcomes map to new SendError.broadcastRejected/.broadcastUnconfirmed. The handle is consumed on every attempt, so a failed broadcast is re-prepared rather than re-fired (comments updated where the old path promised identical-bytes rebroadcast).
  • CoinJoin sweep and CrowdNode selected-input sends finalize atomically and serialize before broadcasting (broadcast consumes the handle).

Requires the sibling ../platform checkout at v4.2-dev ≥ #4286 (serializedData()); should merge before the pin advances past dashpay/platform#4323.

How Has This Been Tested?

xcodebuild -workspace DashWallet.xcworkspace -scheme dashpay against the iPhone 16 simulator: BUILD SUCCEEDED, zero warnings introduced in the touched files, with ../platform at v4.2-dev tip 6dc1b4660e and a freshly built DashSDKFFI.xcframework (ios + sim slices). Send-path behavior (standard, BIP70, sweep, selected-input) needs a testnet QA pass, which I'd fold into the next scheduled send-flow QA round.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c1f3ede-e6d6-41bc-99fd-de8b0a3c91be

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

…to finalizeAtomic

The SDK's v1 split build path (setFunding → buildSigned → CoreTransaction →
broadcastTransaction) is being removed upstream: funding selection and
signing raced across the C ABI, so two concurrent same-account builds could
select the same UTXO. finalizeAtomic commits selection + reservation in one
native operation and returns a single-shot FinalizedCoreTransaction.

- buildAndSign now finalizes atomically and returns the
  FinalizedCoreTransaction; tx bytes come from serializedData() (needed by
  the BIP70 Payment POST and the txHash computation).
- Deferred confirm-then-broadcast semantics are preserved: the handle holds
  the input reservation until broadcast, and discarding it (user backs out,
  POST retry rebuilds) abandons the build and releases the inputs Rust-side.
- Broadcast goes through broadcastTransactionWithOutcome; rejected/unknown
  outcomes map to new SendError.broadcastRejected/broadcastUnconfirmed. The
  handle is single-shot: a failed broadcast is re-prepared, not re-fired.
- CoinJoin sweep and CrowdNode selected-input sends finalize atomically and
  serialize before broadcasting (broadcast consumes the handle).

Requires the sibling platform checkout at v4.2-dev >= #4286 (serializedData).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer force-pushed the feat/migrate-v2-atomic-send branch from 51f9b33 to d0c5370 Compare August 6, 2026 19:13

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed

@QuantumExplorer
QuantumExplorer merged commit 019dc10 into swift-sdk-integration Aug 6, 2026
1 check passed
QuantumExplorer added a commit that referenced this pull request Aug 6, 2026
…he merge

All four fixes are to upstream code that no longer compiled at
swift-sdk-integration HEAD (the maya-swift-sdk merge, PR #916, landed after
the finalizeAtomic refactor, PR #920, without rebasing over it):

- CrowdNode error observers (OnlineAccountEmailController,
  CrowdNodePortalViewController): drop the `if let` on a value the
  preceding `compactMap { $0 }` already unwrapped — a non-optional
  binding is a compile error.
- MAYA swap deposit (SwiftDashSDKTransactionSender.buildAndSignSwapDeposit):
  migrate from the removed split setFunding/buildSigned surface to
  finalizeAtomic, returning FinalizedCoreTransaction like every other
  send path; assertSwapDepositShape takes the serialized bytes + fee
  since the finalized handle exposes no raw `.data`.
- WalletSendService.buildPreparedSwapDeposit: serialize via
  `serializedData()` (the PreparedStandardSend initializer now holds a
  FinalizedCoreTransaction).

Candidate for cherry-pick to swift-sdk-integration — the base branch does
not build without these.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Aug 7, 2026
…-funds prompt (#858)

* feat(coinjoin): offer BIP44 or Shielded destination in post-sync move-funds prompt

The first-sync "Move your mixed coins" popup previously always swept the
leftover CoinJoin balance into the BIP44 spendable balance. When the
balance is large enough to be worth shielding (>= 2x the shield pool fee
+ asset-lock base cost + L1 send-fee reserve, and the shielded
sub-wallet is bound), a destination-choice sheet now offers:

- Dash Wallet balance: the existing one-hop sweep.
- Shielded balance: sweep CoinJoin -> own BIP44 receive address, wait
  for the swept outputs to become spendable, then asset-lock the net
  amount into the shielded pool (Type 18), with a step checklist.

The user authenticates once: the sweep leg runs the spend gate and the
asset lock skips its own via a new explicit alreadyAuthorized parameter
on ShieldedTransferCoordinator.performAssetLock.

Failure posture: the sweep-leg result is kept on the ViewModel, so
"Try again" never re-sweeps an emptied CoinJoin account - it resumes
from the UTXO wait, or resumes a committed asset lock on its exact
outpoint (mirroring the transfer confirm sheet); a stuck lock that
survives the session is picked up by the home tx list's
ShieldedRecoverySheet. Every failure mode leaves the funds spendable in
the BIP44 balance. Small balances keep the BIP44-only dialog, and the
availability check fails closed to it.

Plumbing: sweepCoinJoin(to:) now also returns the net swept duffs; the
CrowdNode UTXO-wait helper is promoted for reuse (waitForFunds);
ShieldedTransferStepList gains a positional (labels/currentIndex) init
for stages the coordinator phases can't express; the wallet source's
onMain trampoline is promoted for the availability check's host reads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(coinjoin): shield mixed coins via a direct CoinJoin-drain asset lock

Replaces the two-hop shielded destination (sweep CoinJoin -> BIP44, wait,
asset-lock) with a single CoinJoin-funded drain asset lock: every
mixed-coin UTXO funds the Type 18 lock directly (lock value =
sum(inputs) - L1 fee, computed SDK-side) and the shielded pool receives
lock_value - pool_fee. The mixed coins never hop through a transparent
BIP44 address, and the flow is one transaction with one PIN prompt.

- ShieldedTransferCoordinator.performAssetLock gains an
  AssetLockFundingSource (.bip44(amountDuffs:) | .coinJoinDrain) routed
  to the new SDK wrapper shieldedFundFromCoinJoinDrain; the historical
  amountDuffs entry point delegates unchanged. The alreadyAuthorized
  seam is removed - the coordinator's own gate is the flow's single
  prompt again. Resume-by-outpoint covers drain locks identically.
- CoinJoinMoveFundsSheet: the shielded path is one coordinator run with
  the same step checklist as the internal transfer; auth-cancel returns
  to the destination choice; the CoinJoin balance is re-tallied on
  completion so the popup/Settings surfaces self-clear.
- The dead two-hop plumbing is removed (sweepCoinJoinForShielding,
  waitForSweptCoinJoinFunds, the sweep net-amount return, waitForFunds,
  and their error strings).

Consumes SwiftDashSDK's shieldedFundFromCoinJoinDrain (platform
feat/coinjoin-asset-lock-funding, on rust-dashcore key-wallet drain
support) - rebuild DashSDKFFI.xcframework after pulling platform.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(build): repair swift-sdk-integration compile breaks surfaced by the merge

All four fixes are to upstream code that no longer compiled at
swift-sdk-integration HEAD (the maya-swift-sdk merge, PR #916, landed after
the finalizeAtomic refactor, PR #920, without rebasing over it):

- CrowdNode error observers (OnlineAccountEmailController,
  CrowdNodePortalViewController): drop the `if let` on a value the
  preceding `compactMap { $0 }` already unwrapped — a non-optional
  binding is a compile error.
- MAYA swap deposit (SwiftDashSDKTransactionSender.buildAndSignSwapDeposit):
  migrate from the removed split setFunding/buildSigned surface to
  finalizeAtomic, returning FinalizedCoreTransaction like every other
  send path; assertSwapDepositShape takes the serialized bytes + fee
  since the finalized handle exposes no raw `.data`.
- WalletSendService.buildPreparedSwapDeposit: serialize via
  `serializedData()` (the PreparedStandardSend initializer now holds a
  FinalizedCoreTransaction).

Candidate for cherry-pick to swift-sdk-integration — the base branch does
not build without these.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer deleted the feat/migrate-v2-atomic-send branch August 7, 2026 08:22
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.

1 participant