Skip to content

feat(wallet): restore MAYACHAIN swap routes with OP_RETURN memo deposits - #916

Merged
romchornyi merged 5 commits into
swift-sdk-integrationfrom
feat/maya-swift-sdk
Aug 6, 2026
Merged

feat(wallet): restore MAYACHAIN swap routes with OP_RETURN memo deposits#916
romchornyi merged 5 commits into
swift-sdk-integrationfrom
feat/maya-swift-sdk

Conversation

@romchornyi

@romchornyi romchornyi commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

Maya swaps were disabled during the DashSync unlink because SwiftDashSDK could
not build an OP_RETURN output, and every MAYACHAIN route needs the swap memo
encoded on-chain. The workaround forced NEAR-intents routing on every Sell quote
and hid Maya-only coins from the picker, which left a set of destination assets
unswappable.

With the SDK-side controls in place (dashpay/platform#4286, dashpay/rust-dashcore#922),
Maya comes back as routes inside Dash DEX. The standalone Maya portal stays
hidden — ServiceDataProvider.shouldShow(.maya) is unchanged.

What was done?

Send path

  • SwiftDashSDKTransactionSender.buildAndSignSwapDeposit builds the MAYACHAIN
    deposit shape: VOUT0 vault payment, VOUT1 zero-value OP_RETURN memo,
    VOUT2 change back to the VIN0 address, with BIP-69 output sorting off.
    It then decodes the signed bytes and refuses to return unless the output
    shape, the memo payload, the VOUT2 == VIN0 script and a ≥ 1 duff/byte fee
    all check out.
  • WalletSendService.sendSwapDeposit reuses the existing sync/online/authorize
    plumbing; the standard send signature is untouched so no other caller can
    silently drop a memo.
  • SendCoinsService.sendSwapKitSwap gained a memo parameter and routes
    memo-bearing deposits to the new path, leaving memo-less NEAR routes on the
    plain send.

Routing

  • MAYACHAIN and MAYACHAIN_STREAMING are separate SwapKit providers with
    different /tokens lists (31 vs 18). Classification now unions both —
    building it from the streaming list alone hid Maya-routable assets such as
    KUJI.KUJI and XRD.XRD.
  • Maya-only coins are no longer filtered out of the Sell picker.
  • Coins routable by both protocols are quoted against NEAR and MAYACHAIN and
    SwapKit's ranking decides, which is what the picker's "Multiple networks"
    label already promised. The provider list stays explicit so routing cannot
    widen to THORChain/Chainflip.

Guards (both kept — a memo-less deposit orphans the funds at the vault)

  • SwapKitSwapProvider and OrderPreviewViewModel.resolveExecutionData each
    refuse a memo over the 80-byte OP_RETURN limit, surfacing a specific message
    instead of a generic "coin unavailable". Measured against the live API, real
    Maya memos run 72–80 bytes and the length varies with the amount as well
    as the address (79/80/79/79 at 0.1/1/10/50 DASH on one route), so the copy
    names both levers.
  • Maya's 10 000-duff dust floor is enforced, keyed on the presence of a memo
    rather than on the execution-network display label.

Incidental

  • fix(crowdnode): viewModel.$error publishes Error?, and Optional has no
    localizedDescription, so both CrowdNode error sinks failed to compile — the
    dashpay scheme did not build at all on this branch before it. Unrelated to
    the Maya work; kept as its own commit so it can be cherry-picked if preferred.

How Has This Been Tested?

Not covered: the refund path. Change-to-VIN0 only matters when MAYAChain
refunds a swap, and that has not been exercised end to end.

The unit-test target remains broken repo-wide, so no app-side tests were added.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Added support for MayaChain swap deposits with optional transaction memos.
    • Added routing for MayaChain and streaming providers, including improved asset and quote selection.
    • Added validation for memo-bearing deposits, including memo size and minimum amount requirements.
  • Bug Fixes

    • Improved handling and display of oversized memo errors.
    • Improved swap tracking for unresolved transactions, which now expire after 24 hours.
    • Improved error filtering in CrowdNode screens.

…scription

`viewModel.$error` publishes `Error?`, and `Optional` has no
`localizedDescription`, so both error sinks failed to compile. The dashpay
scheme did not build at all on this branch until this was fixed.

Unrelated to the Maya work that surfaced it — split out so it can be reviewed
and landed against the base branch on its own.
Maya was disabled during the DashSync unlink because SwiftDashSDK could not
build an OP_RETURN output. With the SDK-side controls in place, Maya returns as
routes inside Dash DEX — the standalone Maya portal stays hidden.

Send path:
- SwiftDashSDKTransactionSender.buildAndSignSwapDeposit builds VOUT0=vault,
  VOUT1=zero-value OP_RETURN memo, VOUT2=change back to VIN0, then decodes the
  signed bytes and refuses to return unless the shape, the memo payload, the
  VOUT2==VIN0 script and a >= 1 duff/byte fee all check out
- WalletSendService.sendSwapDeposit reuses the existing sync/online/authorize
  plumbing; the standard send signature is untouched
- SendCoinsService.sendSwapKitSwap routes memo-bearing deposits to it and keeps
  memo-less NEAR routes on the plain send

Routing:
- MAYACHAIN and MAYACHAIN_STREAMING are separate SwapKit providers with
  different token lists (31 vs 18); classification unions both, otherwise
  Maya-routable assets such as KUJI.KUJI and XRD.XRD are invisible
- NEAR stays preferred; Maya is requested only for assets NEAR cannot route
- mayaOnly assets are no longer hidden from the Sell picker

Guards (both kept — a memo-less deposit orphans the funds at the vault):
- the provider and OrderPreviewViewModel each refuse a memo over the 80-byte
  OP_RETURN limit, reporting a specific error rather than "coin unavailable"
- Maya's 10k-duff dust floor is enforced, keyed on the memo rather than on the
  execution-network display label

Measured live: real Maya memos run 72-80 bytes, and for one route the length
varies with the amount (79/80/79/79 at 0.1/1/10/50 DASH), so the ceiling is
reachable in production and the copy names both the amount and the address.
The picker labels a coin routable by both protocols "Multiple networks", but the
Sell quote only ever asked NEAR, so the label promised a choice the code never
made. Offer both and let SwapKit's route ranking decide.

The provider list stays explicit rather than nil: no filter would also admit
THORChain, Chainflip and the rest, which neither the classification nor the
deposit path accounts for.

Maya-only coins keep the Maya providers, NEAR-only keep NEAR, and an unusable
classification still falls back to NEAR.

A MAYACHAIN route can now win for a coin that previously always deposited
memo-less, so the 80-byte memo ceiling and Maya's dust floor apply to
dual-routable coins too. Both guards run on the fresh pre-commit quote.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

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: e4670ada-3cf7-4fd1-b9b4-61ae2cde36e0

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
📝 Walkthrough

Walkthrough

Changes

Maya swap deposit flow

Layer / File(s) Summary
Provider classification and quote routing
DashWallet/Sources/Models/SwapKit/SwapKitConstants.swift, DashWallet/Sources/Models/SwapKit/SwapKitSwapProvider.swift, DashWallet/Sources/UI/Buy Sell/BuySellPortalView.swift, DashWallet/Sources/UI/Swap/SelectCoin/SelectCoinViewModel.swift
Maya routing now uses both MAYACHAIN providers. Sell quotes select providers from asset classification. SwapKit trims memos and accepts non-empty memos up to 80 UTF-8 bytes.
Swap deposit construction and broadcast
DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKTransactionSender.swift, DashWallet/Sources/Models/Transactions/WalletSendService.swift
The SDK builds, signs, and validates MAYACHAIN swap deposits. WalletSendService authorizes, broadcasts, returns the txid, and maps invalid memo errors.
Memo validation and execution propagation
DashWallet/Sources/Models/Swap/SwapExecutionData.swift, DashWallet/Sources/UI/Swap/OrderPreview/OrderPreviewViewModel.swift, DashWallet/Sources/Models/Transactions/SendCoinsService.swift, DashWallet/Sources/Models/Swap/SwapKitErrorCopy.swift, DashWallet/Sources/Models/Swap/SwapTrackingService.swift
Swap execution data carries the memo. Order preview validates memo size and minimum deposit amount. Send logic selects memo-bearing deposits. Maya memo errors and tracking states are updated.

Error observer cleanup

Layer / File(s) Summary
Non-nil error observation
DashWallet/Sources/UI/CrowdNode/Online/OnlineAccountEmailController.swift, DashWallet/Sources/UI/CrowdNode/Portal/CrowdNodePortalViewController.swift
CrowdNode error observers discard nil values before processing.

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

Suggested reviewers: jeanpierreroma, llbartekll

Sequence Diagram(s)

sequenceDiagram
  participant OrderPreviewViewModel
  participant SendCoinsService
  participant WalletSendService
  participant SwiftDashSDKTransactionSender
  participant DashNetwork
  OrderPreviewViewModel->>SendCoinsService: Submit swap with memo
  SendCoinsService->>WalletSendService: sendSwapDeposit(vaultAddress, amount, memo)
  WalletSendService->>SwiftDashSDKTransactionSender: Build and sign MAYACHAIN deposit
  SwiftDashSDKTransactionSender-->>WalletSendService: Signed transaction and txHash
  WalletSendService->>DashNetwork: Broadcast transaction
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: restoring MAYACHAIN routes with OP_RETURN memo deposits.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/maya-swift-sdk

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

@HashEngineering

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
✅ 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

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
DashWallet/Sources/Models/SwapKit/SwapKitSwapProvider.swift (1)

416-438: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject a MAYACHAIN route that has no memo.

When best.providers contains MAYACHAIN or MAYACHAIN_STREAMING, an absent or whitespace-only swapResponse.memo becomes nil. SendCoinsService.sendSwapKitSwap then uses the plain-send path. The vault receives DASH without the required OP_RETURN instruction.

Require a non-empty memo for Maya routes before creating SwapQuoteResult.

Proposed fix
 let memo = swapResponse.memo?.trimmingCharacters(in: .whitespacesAndNewlines)
+let isMayaRoute = best.providers.contains {
+    SwapKitConstants.mayaProviders.contains($0.uppercased())
+}
+guard !isMayaRoute || memo?.isEmpty == false else {
+    return errorResult(NSLocalizedString(
+        "MAYACHAIN returned no deposit memo. Please refresh and try again.",
+        comment: "SwapKit"
+    ))
+}
 if let memo, !memo.isEmpty, memo.utf8.count > Constants.maxMemoBytes {
🤖 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 `@DashWallet/Sources/Models/SwapKit/SwapKitSwapProvider.swift` around lines 416
- 438, Before creating SwapQuoteResult in the neutral-result mapping, detect
whether best.providers contains MAYACHAIN or MAYACHAIN_STREAMING and require
swapResponse.memo to be non-empty after whitespace trimming. Return the existing
appropriate errorResult when a Maya route lacks a valid memo; preserve the
current over-length rejection and non-Maya behavior.
🧹 Nitpick comments (1)
DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKTransactionSender.swift (1)

124-178: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Add automated tests for the swap-deposit transaction shape.

Test an 80-byte UTF-8 memo, an 81-byte UTF-8 memo, VOUT ordering, zero-value OP_RETURN output, change output, and invalid shape rejection. This flow sends funds and depends on exact serialized-output semantics.

Also applies to: 573-617

🤖 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
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKTransactionSender.swift`
around lines 124 - 178, Add automated coverage for buildAndSignSwapDeposit and
assert the serialized transaction shape: accept an 80-byte UTF-8 memo, reject an
81-byte memo, preserve vault/payment at VOUT0 and zero-value OP_RETURN at VOUT1,
return change when applicable, and reject transactions failing
assertSwapDepositShape. Use test fixtures or mocks that avoid broadcasting while
exercising the exact output serialization and validation paths.
🤖 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.

Outside diff comments:
In `@DashWallet/Sources/Models/SwapKit/SwapKitSwapProvider.swift`:
- Around line 416-438: Before creating SwapQuoteResult in the neutral-result
mapping, detect whether best.providers contains MAYACHAIN or MAYACHAIN_STREAMING
and require swapResponse.memo to be non-empty after whitespace trimming. Return
the existing appropriate errorResult when a Maya route lacks a valid memo;
preserve the current over-length rejection and non-Maya behavior.

---

Nitpick comments:
In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKTransactionSender.swift`:
- Around line 124-178: Add automated coverage for buildAndSignSwapDeposit and
assert the serialized transaction shape: accept an 80-byte UTF-8 memo, reject an
81-byte memo, preserve vault/payment at VOUT0 and zero-value OP_RETURN at VOUT1,
return change when applicable, and reject transactions failing
assertSwapDepositShape. Use test fixtures or mocks that avoid broadcasting while
exercising the exact output serialization and validation paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4322d5d9-afd3-433b-8198-c0dd559b691e

📥 Commits

Reviewing files that changed from the base of the PR and between 24ecde5 and f1550ab.

📒 Files selected for processing (13)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKTransactionSender.swift
  • DashWallet/Sources/Models/Swap/SwapExecutionData.swift
  • DashWallet/Sources/Models/Swap/SwapKitErrorCopy.swift
  • DashWallet/Sources/Models/Swap/SwapTrackingService.swift
  • DashWallet/Sources/Models/SwapKit/SwapKitConstants.swift
  • DashWallet/Sources/Models/SwapKit/SwapKitSwapProvider.swift
  • DashWallet/Sources/Models/Transactions/SendCoinsService.swift
  • DashWallet/Sources/Models/Transactions/WalletSendService.swift
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalView.swift
  • DashWallet/Sources/UI/CrowdNode/Online/OnlineAccountEmailController.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/CrowdNodePortalViewController.swift
  • DashWallet/Sources/UI/Swap/OrderPreview/OrderPreviewViewModel.swift
  • DashWallet/Sources/UI/Swap/SelectCoin/SelectCoinViewModel.swift

…ble"

QA hit this picking a Uphold destination address in the Dash DEX flow.

`EnterAddressViewModel.resolveSourceState` distinguishes "this coin isn't
supported" (.notAvailable) from "your session ended" (.loggedOut) purely by
`DWUpholdClient.isAuthorized`. That flag only asks whether a token is stored,
never whether Uphold still accepts it — and an expired token stays in the
keychain. So the whole .loggedOut branch was unreachable for the most common
way to lose a session, and the user saw "Not available" with no hint that
re-authorizing would fix it:

    Fetch cards failed (HTTP 401): access token has expired
    Got 0 cards: [], looking for SOL on solana
    No card for SOL, creating card and address
    Create card failed with status 401 for SOL

A 401 now clears the stored token via the new
`DWUpholdClient.invalidateRejectedSession`, so `isAuthorized` reports NO and
the existing UI offers re-login. It deliberately skips the revoke call `logOut`
makes: Uphold has already rejected the token, so revoking it would just fail
again. Note the 401 also made the code read "no cards" as "user has no card"
and go on to create one — that second request is now not reached.

Also stop asking Uphold to mint an address on a network its address endpoint
does not accept. Naming a network in `upholdNetwork(for:)` is not the same as
Uphold being able to create one there, so selecting USDC/USDT on Arbitrum
produced a guaranteed 400 on every tap:

    No 'arbitrum' address on card 0b046a36…, creating one
    Address creation failed (HTTP 400).
    {"code":"validation_failed","errors":{"network":[{"code":"invalid",…}]}}

The UI outcome was already correct — nil address plus a live session maps to
.notAvailable — so this removes the pointless round trip and the misleading
error log, not a user-visible bug. Addresses already present on the card are
still used for any network.
A QA report of the "Conversion failed" screen could not be diagnosed from an
exported log: the whole swap flow left no trace of why it failed.

That screen renders the `default` branch of `SwapKitErrorCopy.message` — an
error code the mapper does not recognise — and every path to it discarded the
raw reason on the way:

- `SwapKitSwapProvider.errorResult` wrapped the message into a result and
  returned it unlogged, and it is the funnel for every quote and swap failure
- `OrderPreviewViewModel.setFailure` mapped straight to user-facing copy
- `SwapConvertViewModel.applyQuoteError` rewrote the API error into UI strings
- `SwapKitErrorCopy` collapsed anything unmapped into "something went wrong"

So a screenshot was the only evidence that existed, and it says nothing.

Log at each of those points, before the message is rewritten, plus one line on
the success side when the deposit is broadcast — without it a log cannot
distinguish "the deposit never went out" from "it went out and the swap failed
afterwards".

An unmapped code is now visible in the logs, which is also how we find out
which SwapKit errors still deserve their own copy.
@romchornyi

Copy link
Copy Markdown
Contributor Author

Status update for review — both SDK dependencies have landed since this PR was opened:

v4.2-dev now pins rust-dashcore at dca5b05b and carries no local [patch] override, so this branch builds against released SDK content rather than a work-in-progress pin. Verified locally: dashpay scheme builds clean against platform at v4.2-dev with a freshly rebuilt DashSDKFFI.xcframework, and a full MAYACHAIN swap was exercised on mainnet from a TestFlight build.

Two things worth a reviewer's attention, both stated in the description but easy to miss:

  1. The refund path is not covered. Change-to-VIN0 only matters when MAYAChain refunds a swap, and that has not been exercised end to end. It fails silently if wrong — the swap succeeds and only a later refund goes to an address the user was never told to watch.
  2. The 80-byte memo ceiling is reachable in production. Measured against the live API, real Maya memos run 72–80 bytes, and length varies with the amount as well as the destination address (79/80/79/79 at 0.1/1/10/50 DASH on one route). Both guards that refuse an over-length memo are deliberate and should not be relaxed — a memo-less deposit orphans the funds at the vault.

Unrelated fixes riding along, kept as their own commits so they can be split if preferred: the CrowdNode Error? unwrap (without it the dashpay scheme did not build on this branch at all) and the Uphold expired-session handling.

@HashEngineering HashEngineering 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.

Looks good!

@romchornyi
romchornyi merged commit 2fdcda2 into swift-sdk-integration Aug 6, 2026
2 checks passed
@romchornyi
romchornyi deleted the feat/maya-swift-sdk branch August 6, 2026 20:59
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>
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.

3 participants