Skip to content

Add core-side synthetic destination for swap-to-address#730

Open
j0ntz wants to merge 1 commit into
masterfrom
jon/stealth-send-swap
Open

Add core-side synthetic destination for swap-to-address#730
j0ntz wants to merge 1 commit into
masterfrom
jon/stealth-send-swap

Conversation

@j0ntz

@j0ntz j0ntz commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Asana task

Production implementation of swap-to-address (the core piece of Stealth Send / send-to-any-asset). EdgeSwapRequest accepts an optional toAddressInfo descriptor as an alternative to toWallet (exactly one of the two required); the core builds a synthetic, bridgified destination wallet from it, backed by the real currencyConfig, so swap plugins receive an EdgeCurrencyWallet unchanged. Nothing Houdini-specific lives in core: the same mechanism powers provider-agnostic send-to-any-asset (verified in-app with a ChangeNOW-routed cross-chain send).

Judgement calls, with alternates:

  • Slim descriptor. EdgeSwapToAddressInfo carries only toPluginId, toAddress, and optional toMemos. toPluginId selects the currencyConfig (not derivable from the request); toAddress is the payload; the destination token comes from the request's own toTokenId. Alternate (descriptor carrying toTokenId) was rejected as a two-sources-of-truth hazard.
  • Destination memos ride getMemos on the synthetic wallet. For memo-required payout chains (e.g. an XRP destination tag), the descriptor's toMemos is only the GUI-to-core transport; plugins never read the descriptor. The synthetic wallet exposes getMemos() (EdgeSyntheticDestinationWallet), so the plugin-facing mechanism stays wallet-shaped. Alternate (plugins reading a tag field off the request descriptor) was rejected to keep the plugin interface generic. A pure no-descriptor design is not physically possible: the value must cross the GUI-core bridge somewhere, and the descriptor is the plain-data channel that crosses yaob cleanly.
  • EdgeTxActionSwap.payoutWalletId and EdgeTxSwap.payoutWalletId are both optional now. A swap-to-address destination has no payout wallet; payoutAddress carries the destination. Making only the action type optional (the prototype state) left a latent inconsistency in EdgeTxSwap. Alternate (keeping them required and writing the synthetic synthetic://<pluginId> id) was rejected: it embeds fake wallet ids in persistent tx metadata.
  • Log privacy. Both swap-quote logging paths redact the pasted destination address AND memo values (a deposit tag can identify the recipient's exchange account).

The yaob bridge-crossing proof test covers descriptor-only construction, plugin-faithful reads, memo round-trip via getMemos, and the synthetic surviving the wire format back to the GUI.

Testing: tsc clean, 166 mocha tests pass (3 new), eslint clean. In-app verification (linked into edge-react-gui with the Stealth Send UI): a real cross-chain send-to-address executed on the iOS sim through this mechanism (Base ETH to an Ethereum address via ChangeNOW, deposit broadcast on-chain, swap success scene reached).


Note

Medium Risk
Touches the central swap quote path and public API types (EdgeSwapRequest, swap tx metadata); plugins unchanged but GUI consumers assuming payoutWalletId is always set need updates.

Overview
Enables swap-to-address (private send / send-to-any-asset) by letting EdgeSwapRequest specify either toWallet or a new toAddressInfo descriptor (toPluginId, toAddress, optional toMemos). The core validates exactly one destination, then resolveSwapRequest in fetchSwapQuotes builds a bridgified synthetic EdgeCurrencyWallet from real currencyConfig so existing swap plugins still see a normal toWallet (addresses/receive address return the pasted destination; optional getMemos on EdgeSyntheticDestinationWallet for tags).

payoutWalletId on EdgeTxActionSwap and EdgeTxSwap is now optional when there is no payout wallet; payoutAddress holds the destination. Disk cleaners and SwapCurrencyError were updated for optional toWallet / toAddressInfo. Swap quote logging redacts pasted addresses and memo values.

New tests cover synthetic wallet behavior across the yaob bridge.

Reviewed by Cursor Bugbot for commit c12ad45. Bugbot is set up for automated code reviews on this repo. Configure here.

@j0ntz

j0ntz commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

📸 Test evidence (in-app swap-to-address via synthetic destination)

agent proof 1216251688512498 03 send to any quote

agent proof 1216251688512498 03 send to any quote

agent proof 1216251688512498 04 send to any success

agent proof 1216251688512498 04 send to any success

Captured by the agent's in-app test run (build-and-test).

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit edc8fbd. Configure here.

Comment thread src/core/swap/swap-api.ts
- EdgeSwapRequest accepts an optional toAddressInfo descriptor
  (toPluginId, toAddress, toMemos) as an alternative to toWallet, with
  exactly one of the two required. The core builds a synthetic,
  bridgified destination wallet backed by the real currencyConfig, so
  swap plugins receive an EdgeCurrencyWallet unchanged.
- Destination memos (e.g. an XRP destination tag) use the descriptor
  only as GUI-to-core transport; plugins read them off the synthetic
  wallet's getMemos method (EdgeSyntheticDestinationWallet).
- EdgeTxActionSwap.payoutWalletId and EdgeTxSwap.payoutWalletId become
  optional, since a swap-to-address destination has no payout wallet.
- The pasted destination address and memo values are redacted from
  swap-quote logs.
@j0ntz
j0ntz force-pushed the jon/stealth-send-swap branch from edc8fbd to c12ad45 Compare July 3, 2026 03:13
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