Add Stealth Send and Stealth Swap (Houdini privacy routing)#6066
Draft
j0ntz wants to merge 8 commits into
Draft
Conversation
edge-core-js swap-to-address makes EdgeSwapRequest.toWallet optional (an address-only toAddressInfo destination replaces it) and makes EdgeTxActionSwap.payoutWalletId and EdgeTxSwap.payoutWalletId optional. Sweep the consumers: wallet-to-wallet swap scenes throw if their destination wallet is missing, savedAction and swap-details readers tolerate a missing payout wallet.
Extract the swap confirmation scene's price-impact computation and the quote card's colored percentage text into PriceImpactText, so the send scene's quote row can reuse the same delta UI.
HOUDINI_CHAINS snapshots Houdini's GET /chains intersected with Edge currency pluginIds (per-chain address validation regex + memoNeeded), mirroring the edge-exchange-plugins chain mapping. Register the houdini swap plugin through HOUDINI_INIT env config like other providers.
The send scene now offers a "Recipient receives" asset selector (Houdini-served destination chains) and a Stealth Send toggle. Stealth or a cross-asset recipient turns the send into a swap-to-address quote: live quotes via account.fetchSwapQuotes with toAddressInfo, linked "You send"/"Recipient gets" amount rows whose edited side is the guaranteed amount, the shared price-impact indicator, a real expiry countdown that re-quotes, the quote's network fee, and a destination tag row on memoNeeded chains that rides toMemos to the provider. Stealth restricts quotes to Houdini; a plain cross-asset send fans out to every provider. The confirm slider approves the quote and lands on the swap success scene. Plain same-asset sends are unchanged, including multi-recipient UTXO sends, which now also show a total-amount row. Multi-recipient and stealth/cross-asset are mutually exclusive, gated in both directions. Constrained callers (locked or hidden tiles, FIO requests, payment protocol, custom broadcast/completion hooks) keep today's behavior. Cross-chain destination addresses validate against the destination chain's own rules via a new AddressTile2 validation override, since the source wallet cannot parse them.
A Stealth Swap toggle card on the amount-entry scene restricts the quote request to the Houdini privacy provider (all other providers disabled for the request), with the final copy and a working "Learn more" link. The confirmation scene keeps the restriction on re-quotes and renders the powered-by card as a fixed provider: no chevron and no "tap to change provider" hint, via a now-optional PoweredByCard onPress.
UI walks for the new send-to-address swap surfaces: the recipient-asset selector and toggle card on the send scene (including the destination tag row on an XRP destination) and the Stealth Swap toggle on the swap amount-entry scene. Neither flow requests a live quote.
The address tile's Enter/Myself/Scan/Paste entry buttons had no stable selectors.
Contributor
Author
📸 Test evidence (iOS sim, live wiring)agent proof 1216251688512498 01 stealth send toggle agent proof 1216251688512498 02 recipient chain picker agent proof 1216251688512498 03 send to any quote agent proof 1216251688512498 04 send to any success agent proof 1216251688512498 05 stealth swap toggle agent proof 1216251688512498 06 stealth swap houdini only agent proof 1216251688512498 07 learn more browser agent proof 1216251688512498 08 xrp destination tag agent proof 1216251688512498 09 plain send regression agent proof 1216251688512498 10 multi recipient gating Captured by the agent's in-app test run (build-and-test). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.










CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
toAddressInfo+EdgeSyntheticDestinationWallet)Draft until those publish and the version pins bump — CI cannot typecheck the
toAddressInfopath against published edge-core-js 2.46.1. Locally this branch is verified against the linked dep branches (tscclean, eslint clean, full jest suite green).Requirements
If you have made any visual changes to the GUI. Make sure you have:
Description
Asana task
Production Stealth Send + Stealth Swap (UI Proposal A), replacing every prototype hack from #6054 with real wiring. The prototype PRs (#6054, #6031) stay open as reference.
Send (SendScene2 becomes a send-to-address swap). Judgement call: integrated INTO
SendScene2in place, no feature flag and no parallel scene — the spec's "SendScene2 becomes a send-to-address swap" reading. The new "Recipient receives" selector (variant A1: visible before address entry) and Stealth Send toggle card render only when the scene is an unconstrained plain send; callers that pre-lock or hide tiles, pre-fill an address (payment protocol, deep links), pay FIO requests, or take over broadcast/completion keep today's behavior untouched. Alternate (a feature-flagged parallel scene replacing the wallet Send button, the prototype's approach) rejected: it forks the send flow and leaves every other send entry point without the feature. TheTransactionListTopreroute andHoudiniSendScenere-skin from the prototype are gone.account.fetchSwapQuotes({ toAddressInfo })quote; plain same-asset sends keep the existing UI and behavior (regression-checked, including multi-recipient UTXO sends).~"Estimated". Editing "Recipient gets" issuesquoteFor: 'to'(reverse quote).PriceImpactText(the prototype's recreated delta UI is deleted); real expiry countdown that re-quotes on expiry; real network fee from the quote.makeStealthSwapRequestOptions(all other providers disabled per-request); a plain cross-asset send fans out to every enabled provider (verified live: ChangeNOW won the fan-out and the send executed to the success scene).memoNeededper the Houdini chain metadata; the entered tag ridestoAddressInfo.toMemosto the provider (which forwards it asdestinationTagon order creation).AddressTile2+ the existingAddressModalfamily. Cross-chain destination addresses cannot go through the source wallet'sparseUri, soAddressTile2gains acrossChainAddressValidationoverride that validates against the destination chain's own regex (Houdini's per-chainaddressValidation).src/util/houdiniChains.ts) is a generated snapshot of HoudiniGET /chains(2026-07-02) intersected with Edge currency pluginIds — 38 chains including 6 memo chains — replacing the prototype's 4-chain hardcode and hard-coded memoNeeded flags. Native-asset destinations only for now (token destinations are a follow-up); dynamic sourcing from the API through the swap plugin is a follow-up noted in the module.SwapSuccessScene.Known conflict, multiple recipients (UTXO). Gated in both directions per the spec: stealth ON or a mismatched recipient hides "Add Another Address"; with multiple recipients present, the stealth toggle is disabled and the card expands with "Stealth Send and cross-asset recipients are not available when sending to multiple recipients," and the recipient-asset selector locks. Multi-recipient sends now also show a Total Amount row (the UI call the task left open) instead of only per-recipient amounts.
Other conflicts noticed by inspection (documented calls): max-spend is not offered in swap-send mode (the plain-mode max flow still works; a swap-send max via the plugins'
getMaxSwappableis a clean follow-up); PIN spending limits do not gate stealth sends, consistent with the existing swap flow which they also do not gate; FIO sends are constrained-mode and keep today's flow; theSwapDetailsCardon transaction details requires a payout wallet, so a stealth send's tx detail omits the exchange card (follow-up: render it from the payout asset info alone).Swap. Stealth Swap toggle card on the amount-entry scene (final copy, working inline "Learn more"); the quote request is restricted to Houdini and the restriction survives re-quotes on the confirmation scene; the powered-by card renders as a fixed provider (no chevron, no "tap to change provider") via a now-optional
PoweredByCard.onPress.Copy is the final verbatim spec copy; the prototype's
houdini_incognito_*keys are gone (new keys arestealth_*). "Learn more" opens https://gist.github.com/j0ntz/b3f8101f0a1f79539150fc73511bff8b in the browser as the placeholder (final URL TBD).savedAction sweep for the now-optional
toWallet/payoutWalletIdacross swap scenes,SwapProviderRow,ExchangeQuoteComponent,CategoriesActions,TransactionDetailsScene, andSwapDetailsCard.Testing (iOS sim, edge-funds, deps linked via updot): a REAL cross-asset send-to-address executed end to end — Base ETH → Ethereum address, live ChangeNOW quote from the provider fan-out, slider approved, deposit broadcast on-chain (txid
0xfd51a5c5d4ba44267d257506c977a8e88952f56987e653d2b812502fa739cf8b), SwapSuccessScene reached, transaction details render. Stealth Send/Swap quote paths verified live through the Houdini plugin (the shared dev API key's free-tier 10-exchanges/day cap blocked executing the stealth orders themselves this run; the quote retrieval, provider restriction, XRP destination-tag row, and tag-to-order threading are all verified — the last via the plugin's live-recorded fixture test). Multi-recipient gating, plain-send regression, and "Learn more" verified on-device; jest suite green (snapshots updated for the new rows); maestro walks added undermaestro/14-stealth/.