Repository navigation
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
j0ntz
left a comment
There was a problem hiding this comment.
This branch was self-reviewed before the PR opened (code-review at high effort, then curated).
Fixed before review (1)
src/docs/stealth-send-swap.md:113: the captions of two code blocks quoted from edge-core-js pointed at edge-react-gui blobs. They link to edge-core-js. Folded into "Load Houdini's token list from the info server".
Rejected in curation (8)
src/components/scenes/SendScene2.tsx:1003: a same-chain swap-send drops the amount of a scanned or pasted payment URI. Rejected: on the same chain the URI is parsed with the source asset's code, so its amount is not in the token's units. Dropping it costs a retype and never sends a wrong amount. Stated in the PR description.src/components/modals/RecipientAssetListModal.tsx:60: the new rows have no highlight for the selected asset. Rejected: the task's layout D says "nothing marking the current pick".src/actions/HoudiniTokensActions.ts:20: a later rollup withouthoudiniTokensreplaces the loaded list with{}. Rejected: the server omits the field only before its first complete fetch, and after that a failed fetch keeps serving the last list.src/components/scenes/SwapCreateScene.tsx:306: with Stealth Swap on, the pickers hide every token while the list is empty. Rejected: the spec says to show only served tokens, and chain coins stay listed.src/util/houdiniChains.ts:426: matching lowercases contract addresses on both sides for every chain. Rejected: the spec asks for a lowercase match. A mismatch hides a token and never lists a wrong one. USDT on Tron and USDC on Solana match on the simulator.src/actions/HoudiniTokensActions.ts:22: one malformed entry makes the cleaner throw, and the stored list stays. Rejected: both ends of the format are ours, and a stale list is better than an empty one.src/components/scenes/SendScene2.tsx:2029: the swap provider's name is gone from the Exchange Rate row. Rejected: requested by the task owner during the run.src/actions/HoudiniTokensActions.ts:21: every refresh dispatches a new object when the list is unchanged. Rejected: the refresh runs every 10 minutes and two memos recompute.
302cf2e to
d83e976
Compare
d83e976 to
8a28b76
Compare
peachbits
left a comment
There was a problem hiding this comment.
Data from the server. Per the review on EdgeApp/edge-info-server#166, the server would serve Houdini's chain and per-asset data keyed by Edge pluginId and tokenId. This PR can then read it as is, with no contract-address matching (inline comment on getTokenFlag). memoNeeded, addressValidation and the coin's hasSelfPrivate would also come from the served data, and HOUDINI_CHAINS would keep only what Houdini does not provide. Open points for the rework:
evmChainIdand the URI scheme names are Edge data, not Houdini's. Either the server's chain table carries them, or a small table stays here.- Before the rollup loads, or while the server runs without a Houdini key, there would be no chains at all. Stealth Send could stay hidden until the data arrives, or the app could ship a fallback copy.
addressValidationarrives as a regex string, and the scene uses it to guess which chain a pasted address belongs to. A pattern that fails to compile should mark its chain unavailable, and a permissive one would match every paste. Houdini reports^.*$for the IBC chains (see the comment in edge-exchange-pluginssrc/mappings/houdini.ts), which is why they are excluded today.
Behavior and tests. The three Warnings and four Suggestions inline apply to the current code and carry over to the rework.
afa9855 to
7af7eec
Compare
|
Reworked to read
The inline Warnings and Suggestions are answered on their threads. |
178407c to
cbd1838
Compare
cbd1838 to
df73664
Compare
df73664 to
025be96
Compare
025be96 to
ce7a7ff
Compare
ce7a7ff to
0aa582d
Compare
0aa582d to
68d28e3
Compare
peachbits
left a comment
There was a problem hiding this comment.
The earlier asks and the Bugbot fixes all check out at 68d28e3b20: tsc is clean and all 1150 jest tests pass. The rest is inline. The Warning on the wallet switch describes a destination tag that gets dropped; it predates this PR but sits in the switch the PR reworks. The other two Warnings ask for tests on logic that broke during this PR.
Suggestion: after the autosquash, "Load Houdini's token list from the info server" keeps its original body, which says the server serves houdiniTokens and the app matches tokens by contract address. Both are gone, so that message needs rewording when the fixups are squashed.
bacb300 to
0b2218d
Compare
|
The five inline threads are answered with their fixups (9830e64, 0b2218d, 14ab129). The reword of "Load Houdini's token list from the info server" happens at landing, when the fixups fold into it; the commit tool here cannot carry an
|
The info server serves the tokens Houdini can route as `houdiniTokens` in its rollup. Read the field from the raw rollup with a cleaner of our own, keep it in redux, and match it to Edge tokens by contract address.
0b2218d to
d9052c7
Compare
|
Rebased onto develop with #6242 in, head d9052c7. The reconcile was substantial, so please look at the rebased head before it lands.
The design doc has a Phase 40 entry for the reconcile. tsc and eslint are clean, and jest passes 1260 of 1260 tests in 134 suites. |
peachbits
left a comment
There was a problem hiding this comment.
Checked the rebase against both parents (develop at e79f5e9 with #6242, and the approved 0b2218d): the own-network switch, getHoudiniChain(houdiniChains, ...) in detection and isFormatOf, getOwnNetworkBlock on the source asset, the ${sourceKey}->${sourceKey} probe key and getUriAmountSide all read right, and #6242's tests are ported. tsc clean, jest 1260/1260, no new eslint warnings. One ask inline.
For the next rebase: #6241 removed the third parameter of makeStealthSwapRequestOptions, which SendScene2.tsx:3937 still passes.
d9052c7 to
9068d1a
Compare
f570671 to
5326298
Compare
5326298 to
66c4d78
Compare
2a735c6 to
0b5c90f
Compare
d27ef0a to
fe37002
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fe37002. Configure here.
The design doc now says a plain or same-asset send keeps a payment code's amount only when the parse names no asset or names the asset being sent, and records that change as Phase 41. Stamp the design doc with the fingerprint of the code tree it documents (f8a62bfec8c19efdeddd44f2473e1ef7bbdc3aa8) Reload Houdini's list whenever an info rollup lands, answering Bugbot's finding that the Stealth list can stay empty after the rollup arrives. On a warm launch the signed rollup is applied in the background, after Services mounts. The refresher's mount run read no houdiniAssets, wrote an empty list, and the next run was 10 minutes later, so Stealth stayed hidden until then. network.ts now exposes onInfoRollup, notified by both rollup paths (fetchPublicRollup and the signed applyPublicRollup in keysStore), and Services reloads the list on each notification. A keysStore test proves the warm refresh notifies listeners with the rollup that carries houdiniAssets. Document the rollup listener that reloads Houdini's list: the body explains why the refresher's mount run can beat the warm-launch rollup and how onInfoRollup closes the gap, the testing list adds the keysStore case, and Phase 42 records the change. Stamp the design doc with the fingerprint of the code tree it documents (3399e0238bc1ef0d6f9f922b0f907b621f103bc5) Document payout-token codes and the kept amount on adopt The TDD now covers parseCrossChainPayment's payout tokenId, the entered amount surviving a Myself pick or an adopting address, the new tests, and Phase 43. Stamp the design doc with the fingerprint of the code tree it documents (89eb5e47f12ead4aed1d56a0df4bec989fc55daa) Pin the TDD's code captions to the pushed head The captions pointed at a local commit that the push rewrote, so every code link returned a 404. State that the kept amount on adopt is not reachable by tapping A sim drive (0.0005 BTC code, clear icon, Myself, an ETH wallet) adopted the ETH address and showed 0 BTC: every route back to an empty address tile runs handleResetSendTransaction, which zeroes the amount, and the amount tile renders only once an address is set. The Myself paragraph and Phase 43 now say the adopt keep guards the handler contract and is pinned by the unit cases. Captions re-pin to the pushed head 2a735c6. Record the own-network amount keep in the design doc Notes that a bare own-network address keeps the entered amount through keepEnteredAmount, adds the switch-back scene case to the Testing counts, and logs the Bugbot finding under Phase 43. Stamp the design doc with the fingerprint of the code tree it documents (859bf13522cf8102db52fba42e115d7832723c0a) Repin code captions to the pushed head The own-network keep moved SendScene2 and its doc lines, so the captions now cite 708912c, the pushed tip that carries that code. Stamp the design doc with the fingerprint of the code tree it documents (3cc795d423cf53e2c1b27d5f52d421aa64170f20) Design doc: a token invoice on a same-chain token payout is read with the payout token's code (Payment URI amounts), the new scene case and counts in Testing, and Phase 45. Stamp the design doc with the fingerprint of the code tree it documents (c3131a1886cb4ca10e9f379f1f8e207738a9dd2d) Fixup-for: auto
Houdini's routes come from the info server's `houdiniAssets` field, cleaned by `asHoudiniChains`, where they came from `houdiniTokens` and `asHoudiniTokens`. Each chain, keyed by pluginId, carries its memo flag, address pattern and coin, plus the tokens Houdini serves on it by tokenId. That list takes over from the hard-coded `HOUDINI_CHAINS` table and from matching tokens by contract address. A swap-send tracks a recipient asset (plugin and token) where it tracked a chain, so the quote asks for the picked token and same-asset compares both. The recipient picker and the Myself wallet picker list each chain coin followed by the tokens Houdini serves on it, less the destinations the info server withdrew.
Test the own-network switch with Stealth on Covers peachbits' ask on the reconcile: a destination tag read from an XRP code survives the switch back to the wallet's network, a token with no private route to itself is refused with the self-private reason, and a source whose same-asset private route came back unavailable is refused with the route reason. Each test fails under the mutation the review named for it. Type the fake XRP settings in full The fake XRP wallet's settings left out customFeeSettings, which the fake plugin's settings type requires; tsc rejected the test file. Fixup-for: human
Drop a URI amount stated in another asset on a plain or same-asset send Answers Bugbot's finding on 9068d1a: with Stealth on and no destination picked, a token send treats a payment code as a plain send, so an EIP-681 coin value (which withStatedAsset marks as the coin) was written into the token amount. A coin code for 0.002 ETH on a USDT wallet filled 2,000,000,000 USDT. The plain branch now keeps a code's amount only when the parse states the source asset, and a scene test pins it. Keep the entered amount when adopting a payout adoptDestination handed the scene a bare address, and the receive-side branch of handleChangeAddress set the send amount from the address alone, so a "Myself" pick on another asset, or an address that adopts another chain, cleared the amount the user had entered. The adopt resets the guaranteed side to send, and that amount is in the asset being sent, so a destination that arrives with no amount of its own now keeps it through keepEnteredAmount, stated in the source asset. Accept a payment code for the payout token parseCrossChainPayment refused every token code, a rule left from coin-only payouts, so a USDT invoice scanned for a USDT payout was called an invalid address. It now takes the payout's tokenId: a code naming that token is accepted with its amount, in the token's units; another token's code is still refused; and a coin code on a token payout keeps its address but not its amount. Keep the entered amount through the own-network switch Answers Bugbot's finding on 0b5c90f: adoptOwnNetworkDestination handed the bare own-chain parse to handleChangeAddress, whose plain branch writes the parse's nativeAmount (undefined for a bare address) over the amount already entered. It now wraps the parse in keepEnteredAmount, as the self-transfer pick and adoptDestination do, so a code's own amount still wins and a bare address keeps the source-asset amount. A scene case drives a BTC code, an Ethereum switch, then a bare BTC address. Retry a token payout's invoice with the token's own code: the ETH parser throws on a transfer code asked as ETH, so the earlier fix for Bugbot's token-invoice finding never accepted one on a real chain Read a token invoice with the payout token's own code when the payout is a token on the wallet's own chain. The wallet parsed it with the coin's code, which an EVM parser refuses for a token transfer, so an ETH-to-USDT send declined a USDT invoice (Bugbot: same-chain token invoices rejected). The stated amount prices the receive side. Scene test covers it. Fixup-for: auto
The send scene's Exchange Rate row listed the swap provider under the rate. The row now shows the rate and the price impact only.
With Stealth Swap on, the Exchange scene wallet pickers list only the chain coins and tokens Houdini serves, and the receiving picker also drops the destinations the info server withdrew. With it off they are unfiltered.
fe37002 to
0186e0a
Compare
There was a problem hiding this comment.
Claude Code Review
Claude Code Review is paused for this repository because its GitHub organization is not linked to your Claude organization.
To turn it back on, one person needs both roles: owner of the GitHub organization (or the account owner, for a personal repository) and admin or owner of your Claude organization. If that is not you, pass this on to someone who is.
Open Organization settings, Git providers and click Link next to the GitHub organization's name. If it is not listed, click Enable GitHub integration or Connect your GitHub first, whichever the page shows.
New pull requests are then reviewed as before. To get a review on one that is already open, someone with write access can comment @claude review.
Tip: disable this comment in your organization's Code Review settings.

Technical Design Document
stealth-send-swap.md
Description
Asana task
A swap-send or Stealth Send can pay out only a chain's coin, because the app has no list of the tokens HoudiniSwap routes. This reads that list from the info server rollup (
houdiniAssets, served by EdgeApp/edge-info-server#166) and lets the send scene pay out a token, such as TRX to USDT on Tron or SOL to USDC on Solana.houdiniChains.tsreadshoudiniAssetsas served, keyed by EdgepluginIdandtokenId. No contract address is matched in the app.memoNeeded,addressValidationand each asset'shasSelfPrivatecome from the served data. The baked-in Houdini chain table is gone.evmChainIdis Edge data that Houdini does not serve. It stays in a smallEDGE_CHAIN_DATAtable. URI schemes are matched by EdgepluginIdonly (schemeNamesChain).^.*$for some chains), drops its chain.EDGE_CHAIN_DATAreplaces 7 served patterns that reject real addresses or accept wrong ones: cardano, dash, ecash, hedera, monero, pivx and solana. It also pinsmemoNeededtrue for the six chains whose deposits are credited only by tag (cosmoshub, hedera, ripple, stellar, thorchainrune and ton). A servedfalsecannot drop a destination tag. Edge's Solana pattern requires both letter cases. A 42-characterbc1or cashaddr made only of base58 characters is not offered Solana.network.tsexposesonInfoRollup, notified by both rollup paths (the fetched rollup and the signed one applied on a warm launch), andServicesreloads Houdini's list on each. A rollup that lands after login shows Stealth at once instead of after the next 10-minute poll.makeSwapSendRequestbuilds the swap request with the realtoTokenId.pickKeepsRecipientandgetWalletSwitchResetdecide when a pick or a source wallet switch keeps the entered address, with unit tests next to the other helpers.getMemoCarrymoves a memo into the Destination Tag row when a swap-send starts and back into the plain memo when it ends. Toggling Stealth or picking the source asset back on a tag chain keeps the tag.getWalletSwitchMemoCarrydoes the same for a source wallet switch that keeps the address with Stealth armed.RecipientAssetListModal, built onListModal.RadioListModalloses its search prop and filter, since this scene was their only caller.getSelfTransferPick,keepEnteredAmountandisReceiveSideGuaranteed, with unit tests. It still leaves out the source wallet: a same-chain token payout to yourself needs a second wallet on that chain.parseCrossChainPaymenttakes the payout'stokenId: a payment code naming that token is accepted with its amount in the token's units, another token's code is refused, and a coin code on a token payout keeps its address but not its amount. Asked for ETH, the Ethereum parser throws on a USDTtransfercode. When that first ask throws on a payment URI,parseAsPayoutasks again with the payout token's code. Bare text is never asked as a token, since Tron reads USDT text as a PIX lookup. A token payout on the wallet's own chain reads a payment URI with the payout token's code inhandleUnparsedAddress. An ETH wallet paying out USDT therefore takes a USDTtransferinvoice with its payee and a guaranteed USDT receive amount.value=no longer sizes a token spend.keepEnteredAmount).Review notes:
getUriAmountSide). An amount another chain's parser read, or one stated in the payout token on the sending chain, sets the receive amount. One stated in the sent asset sets the send amount. Any other asset sets neither.Tested on an iOS simulator, with the app pointed at a local info server that served the live Houdini list:
No enabled exchanges support USDT (on Tron)and no quote is fetched. For this frame the app's two info poll intervals were shortened to 5 seconds as an uncommitted edit, because the shipped intervals are 5 and 10 minutes.houdiniAssetslist: the picker's served token rows and a Stealth USDT payout quote on Tron. From a Tron wallet with Tether on Tron picked, "Myself" and the TRX row of a second Tron wallet reverted the payout to TRX as a plain send. With BTC banned as a Houdini source and Stealth Swap on, BTC to ETH showed the no-enabled-exchanges toast; with Stealth Swap off the same pair quoted through Maya Protocol. These were quotes only, with no funds sent.68d28e3with quotes only and no funds sent:No enabled exchanges support USDT (on Tron), and the receiving picker drops that wallet's USDT row while keeping its TRX row.No enabled exchanges support TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t (on Tron).memoNeededfalse for Cosmos Hub: a Stealth Send of 20 ATOM with memo 104729 keeps the Destination Tag row.bc1address is adopted as Bitcoin with no network picker and no Solana candidate.getSelfTransferPick, re-driven at0b2218dwith quotes only and no funds sent:9507981, with quotes only and no funds sent:bc1address switches back to a plain Bitcoin send with Stealth off.fe37002, with quotes only and no funds sent:value=code fills the address and leaves the amount at 0 USDC.0186e0a, with quotes only and no funds sent:transferinvoice entered on an Ethereum wallet with Tether on Ethereum picked: accepted with the payee and a guaranteed 25 USDT. Houdini returned no route at that size, since the invoice is under its $25 floor.houdiniChains.tshelpers,RecipientAssetListModal, and instealthSwap.test.tsthe Houdini bans on both sides.SwapCreateScene.test.tsxchecks that a Houdini-only ban on either side blocks a Stealth Swap and not a plain swap, andAddressTile2.test.tsxchecks that a token row in the "Myself" picker reads that token's receive address.SendScene2.recipientNetwork.ui.test.tsxcovers the own-network switch with Stealth on (a destination tag from an XRP code survives, a token with no private route to itself is refused, a source whose private route came back unavailable is refused), the entered amount kept through a "Myself" pick, an adopting address and a switch back, a coin code on a token wallet leaving the amount empty, and a USDT invoice accepted on an ETH wallet paying out USDT.paymentUri.test.tscovers payout-token codes, including a parser that reads a token invoice only when asked for that token, andkeysStore.test.tscovers the warm refresh notifying rollup listeners.Screenshots 01 to 23 were taken before the rebase onto develop, 24 to 28 after it. Screenshots 29 to 31 show the wallet switch. Screenshot 32 shows the ban message. Screenshots 33 to 36 show the SOL to USDC send through "Myself". The last batch (2026-10-14, at
7af7eec) is the review rework: 11 and 12 the served token rows and Stealth USDT quote, 13 and 14 the "Myself" reset, 15 and 16 the Stealth Swap source ban and its control. The batch at68d28e3carries 11, 12, 15 and 16 forward and adds 17 to 26 for the Cursor review fixes; 19, 20 and 26 were taken with the uncommitted edits named above. Screenshots 27 to 29 show the last three items of that list. Screenshots 30 and 31, at0b2218d, show the Cosmos Hub wallet switch before and after. Screenshots 32 to 36, also at0b2218d, show the three "Myself" picks in that order. Screenshots 37 to 39, at9507981, show the warm-launch Stealth Swap, the amount reset after clearing the address, and the switch back to the wallet's own network; they are carried tofe37002. Screenshots 40 and 41 show the USDT invoice before and after the parser retry, 42 and 43 the USDC Stealth send and its empty amount after thevalue=code, and 44 and 45 the kept amount with the gate removed. Screenshots 46 and 47, at0186e0a, show the USDT invoice on the Ethereum wallet entered and accepted.CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
houdiniAssets. Until it deploys withhoudiniApiKeyandhoudiniApiSecretset, Stealth Send and Stealth Swap stay hidden. It must reach production before this app release.Requirements
If you have made any visual changes to the GUI. Make sure you have:
Tested on an iOS simulator only (a large-screen iPhone).
Test evidence
8a28b762026-10-10
🪓 Info server poll intervals shortened to 5 seconds (uncommitted) so a ban injected after the pick reaches the scene
1. recipient picker rows
2. recipient picker search
3. recipient row
5. success
6. sol usdc picker search
7. sol usdc recipient row
8. sol usdc quote
9. myself picker
10. myself picker search
11. myself recipient usdc
12. myself sol usdc quote
13. injected ban usdc solana absent
14. exchange from stealth off
15. exchange to stealth off
16. exchange to create stealth off
17. exchange stealth swap on
18. exchange from stealth on
19. exchange to stealth on
20. exchange to create stealth on
21. usdt usdt recipient row
22. usdt usdt stealth quote
23. usdt usdt stealth success
24. rebased recipient picker rows
25. rebased recipient picker search tron
26. tron myself picker my tron 2
27. tron myself provider minimum
28. tron myself trx usdt quote
29. token payout before wallet switch
30. token payout after wallet switch
31. same asset switch keeps address
32. 🪓 ban message names chain
33. myself sol usdc quote 0105
34. myself sol usdc success
35. myself sol usdc tx details
36. my solana 2 usdc received
68d28e32026-10-14
🪓 19-20: showed the You send row before an address was entered (SendScene2 amount-row gate); 26: forced destDescription undefined so the ban message falls back to asset ids. Both reverted.
11. served token rows in recipient picker
12. stealth usdt payout quote
15. stealth swap houdini source ban refused
16. stealth off control quote
17. myself token receive address
18. myself same asset after token
19. 🪓 amount before myself
20. 🪓 myself keeps amount
21. plain memo before stealth
22. stealth carries memo to tag
23. stealth off tag back to memo
24. stealth swap dest ban refused
25. stealth swap picker drops banned dest
26. 🪓 dest ban fallback ids
27. memo floor tag row stays
28. bc1 address adopts bitcoin no solana
29. list kept after empty poll stealth offered
0b2218d2026-10-14
30. wallet switch tag before
31. wallet switch tag after
32. myself adopt usdc
33. myself same asset plain send
34. token picked no address
35. myself source asset ends swap
36. myself source asset after myself token
0186e0aFilter Stealth Swap pickers to served assets
2026-10-16
🪓 amount row shown before any address (SendScene2 swap You-send gate removed, reverted)
37. warm launch stealth swap shown
38. clear then myself amount reset
39. own network switch back plain send
40. token invoice refused before fix
41. token invoice accepted
42. usdc stealth on
43. usdc coin value amount empty
44. 🪓 amount before myself
45. 🪓 myself keeps amount
46. same chain usdt invoice entered
47. same chain usdt invoice accepted