fix(swap-widget): clear stale manual receive address on chain change#12353
Conversation
Closes SS-5660. Manual receive addresses persisted across buy-chain switches, leaking the previous chain's address into quote requests and balance fetches (e.g. an EVM 0x… address hitting mempool.space and 400ing). Gate the derived receiveAddress by validity against the current buyChainId in SwapWidgetCore so consumers never observe a stale value, and clear customReceiveAddress when invalid for the new chain so modal state stays in sync. Replace shape-only regex validation with checksum decoding: bs58check (BTC / LTC / DOGE legacy), bech32 + bech32m with BIP350 enforcement (BTC / LTC SegWit + Taproot), cashaddrjs (BCH CashAddr), and PublicKey length check (Solana). Covers BIP44/49/84 where each chain supports it. Trim input once at the AddressInputModal boundary so validated value and stored value agree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughReplaces regex validation with decoding-based validators routed by CAIP, adds bs58check/cashaddrjs and types, trims and validates modal input, gates and clears custom receive addresses by current chain, and adds comprehensive tests covering UTXO/EVM/Cosmos/Solana and edge cases. ChangesAddress Validation and Network-Aware Custom Receive Address
Sequence Diagram(s)sequenceDiagram
participant User as User Input
participant AddressModal as AddressInputModal
participant Validator as validateAddress
participant SwapWidget
participant State as Component State
User->>AddressModal: Enter address (may include whitespace)
AddressModal->>AddressModal: useMemo trim input
AddressModal->>Validator: validateAddress(trimmedInput, chainId)
Validator-->>AddressModal: { valid, error? }
AddressModal->>AddressModal: Update confirm disabled state
User->>AddressModal: Click confirm
AddressModal->>Validator: Re-validate trimmedInput
Validator-->>AddressModal: { valid, error? }
alt valid
AddressModal->>SwapWidget: onAddressChange(trimmedAddress)
SwapWidget->>State: set customReceiveAddress
else invalid
AddressModal->>AddressModal: Show error, keep open
end
User->>SwapWidget: Switch network (buyChainId changes)
SwapWidget->>Validator: validateAddress(customReceiveAddress, newChainId)
Validator-->>SwapWidget: { valid: false }
alt invalid for new chain
SwapWidget->>SwapWidget: clear customReceiveAddress
SwapWidget->>State: receiveAddress = walletReceiveAddress
else still valid
SwapWidget->>State: receiveAddress = customReceiveAddress
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/swap-widget/package.json (1)
88-88: ⚡ Quick winRemove redundant
@types/bs58checkto avoid type declaration conflicts.Line 88 adds
@types/bs58check, butbs58check@4.0.0already includes built-in TypeScript declarations. Installing both packages simultaneously creates unnecessary duplication and risks type inconsistencies as the packages evolve independently.♻️ Proposed fix
- "`@types/bs58check`": "^2.1.0",🤖 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 `@packages/swap-widget/package.json` at line 88, Remove the redundant type package by deleting the "`@types/bs58check`" dependency entry from package.json; keep the runtime package "bs58check@4.0.0" which already provides its own TypeScript declarations to avoid duplicate type definitions and potential conflicts during compilation.
🤖 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.
Inline comments:
In `@packages/swap-widget/src/utils/addressValidation.ts`:
- Around line 23-27: The isValidBase58Check function currently only checks the
version byte; update it to also validate payload length by ensuring
bs58check.decode(address) returns a Buffer of exactly 21 bytes (1 version byte +
20-byte hash160) before returning allowedVersionBytes.includes(decoded[0]); keep
the existing try/catch and failure semantics, and reference the function name
isValidBase58Check, the bs58check.decode call and allowedVersionBytes to locate
where to add this length check.
- Around line 55-61: The current bech32 decode block returns true without
checking SegWit witness version and program length; after decoding (the
variables prefix, words, witnessVersion from codec.decode(lower)), validate that
witnessVersion is an integer between 0 and 16 inclusive, convert the remaining
words to a witness program byte array (using the appropriate fromWords utility
for the codec) and ensure the program length is between 2 and 40 bytes, and
additionally enforce that if witnessVersion === 0 then the program length is
exactly 20 or 32 bytes; only return true if all these checks pass and keep the
existing codec (bech32 vs bech32m) checks in place.
---
Nitpick comments:
In `@packages/swap-widget/package.json`:
- Line 88: Remove the redundant type package by deleting the "`@types/bs58check`"
dependency entry from package.json; keep the runtime package "bs58check@4.0.0"
which already provides its own TypeScript declarations to avoid duplicate type
definitions and potential conflicts during compilation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f62dc2d5-d491-4019-8f2d-b229b3187ca3
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
packages/swap-widget/package.jsonpackages/swap-widget/src/components/AddressInputModal.tsxpackages/swap-widget/src/components/SwapWidget.tsxpackages/swap-widget/src/utils/__tests__/addressValidation.test.tspackages/swap-widget/src/utils/addressValidation.tspackages/swap-widget/src/vite-env.d.ts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Description
Fixes SS-5660: a manual receive address persisted after switching the buy network, leaking the previous chain's address into quote requests and balance fetches. The downstream symptoms were swap failures on submit and
400 Bad Requestfrommempool.space/api/address/0x…when an EVM address was handed to a Bitcoin balance fetcher.Two parts:
1. Receive-address state (primary fix) —
SwapWidgetCorenow gates the derivedreceiveAddressby validity against the currentbuyChainId. A custom address is only consumed if it validates for the active buy chain; otherwise consumers seewalletReceiveAddress. A cleanup effect clearscustomReceiveAddresswhen it becomes invalid after a chain change, so the address modal state stays consistent with what gets sent on-wire.2. Address validation hardening (alongside fix) — the validity check that the gate relies on was a regex-only shape match; this changes the validators to proper checksum decoding so cross-chain false positives don't leak:
bs58checkfor legacy P2PKH / P2SH (BTC, LTC, DOGE) — catches typos via the SHA256d 4-byte checksum and distinguishes chains by version byte.bech32/bech32mfor SegWit and Taproot (BTC, LTC) with BIP350 codec-vs-witness-version enforcement.cashaddrjsfor BCH CashAddr (polymod checksum + prefix match).PublicKeydecode for Solana (32-byte length check, replacing the 32-44-char base58 length filter that let through e.g. Bitcoin addresses).COSMOS_SDK_VALIDATORStable mirroringUTXO_VALIDATORS.AddressInputModaltrims input once at the boundary so the validated value and stored value agree.Covers BIP44 / BIP49 / BIP84 / Taproot where each chain supports them. Litecoin re-accepts the legacy pre-2018 P2SH
3…form (version byte0x05) to keep older BIP49 wallets working — same intentional cross-chain ambiguity already present for BCH legacy.Issue (if applicable)
closes #12321
Risk
Medium. Touches the swap-widget receive-address derivation and the validator used by the address input modal. Addresses with corrupted checksums that previously passed the shape-only regex will now be rejected — this is the intended behavior. Legacy
0x05(BTC/LTC P2SH collision) and BCH legacy (BTC version bytes) are still accepted for backwards compatibility.New deps in
packages/swap-widget/package.json:bs58check@^4.0.0cashaddrjs@^0.3.12@types/bs58check@^2.1.0UTXO chains (BTC, BCH, LTC, DOGE), Cosmos SDK chains (Cosmos Hub, THORChain, MAYAChain), EVM, and Solana on the swap-widget side only. No on-chain transaction signing changes.
Testing
Engineering
Run the new unit suite:
65 tests covering per-chain valid forms, cross-chain rejections, checksum corruption (8 regression cases that the previous regex implementation would have accepted), BIP350 codec/witness-version mismatches, empty input, and
getAddressFormatHintfor each namespace.To reproduce SS-5660 manually in the demo app:
cd packages/swap-widget && pnpm dev0x…)0x…stays selected; the balance fetch firesGET https://mempool.space/api/address/0x…and 400s; if the user proceeds, the swap fails on submitOperations
Functional QA in the swap-widget demo:
bitcoincash:q…) and legacy (1…) should be accepted.M…) and legacy (3…) P2SH forms should be accepted.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests