Skip to content

fix(withdraw): show non-EVM (Tron/Solana) token selection in the selector button#2490

Merged
jjramirezn merged 4 commits into
mainfrom
hotfix/token-selector-non-evm-label
Jul 23, 2026
Merged

fix(withdraw): show non-EVM (Tron/Solana) token selection in the selector button#2490
jjramirezn merged 4 commits into
mainfrom
hotfix/token-selector-non-evm-label

Conversation

@abalinda

@abalinda abalinda commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Picking USDT-Tron / USDT+USDC-Solana in the withdraw token drawer looked like it did nothing: the tap did select the token (context updates unconditionally, the drawer highlight even shows it), but the collapsed selector button resolves its label via areEvmAddressesEqual — always false for base58 addresses — so it kept reading "Select a token". Users read that as "the coin can't be selected".

One-line fallback to a case-insensitive literal match. areEvmAddressesEqual stays first for its native-proxy aliasing on EVM tokens (0xeee…0x000…).

Pairs with peanutprotocol/peanut-api-ts#1230, which unblocks the actual request/charge creation for these destinations (EVM-only schema pattern + base58 case-corruption on storage). Without the BE fix, proceeding past the (now visible) selection still 400s at setup review.

Also fixes a pre-existing React violation hit while QA-ing this flow: the page called router.push('/withdraw') during render when no amount was set, which hard-errors Next 16's dev overlay on any direct entry/refresh of /withdraw/crypto. The redirect now runs in an effect — same behavior, one frame later.

Risks

Display-only. The literal fallback cannot resolve a different token than before: distinct registry tokens never differ only by address case, and native aliasing is still handled by the EVM comparator first.

QA

  • typecheck + jest green vs dev-tip baseline (pre-existing countryCurrencyMapping suite failure is unrelated).
  • Manual: withdraw → search or network-first → Tron/USDT → button shows the selection; same for Solana USDC/USDT. (Note: non-EVM tokens intentionally don't appear in the default popular list — search or network-first only.)

🤖 Generated with Claude Code

Design notes / adversarial review

  • Adversarially verified the full click chain (tap → context write → drawer close → selectedTokenData derivation → CTA → onReview): selection state always worked (handler unconditional; Sentry payloads prove base58 reached the BE); the collapsed-button label was the only broken link. No auto-reset effect exists that could clear the selection.
  • The exact fallback can never resolve a different token than before: EVM case variance is fully handled by areEvmAddressesEqual; for base58 the registry is the single source, so exact equality is both sufficient and a canary for any future case corruption in the selection path.

Hotfix to main (re-targeted from dev per Aleks — supersedes #2488; repo rules block force-push, hence the fresh branch). Creates main→dev back-merge debt after merge.

Summary by CodeRabbit

  • Bug Fixes
    • Improved token selection display for non-EVM networks (e.g., Tron and Solana) by correctly matching the selected token address so the selector stays on the valid choice.
    • Prevented the token selector from falling back to “Select a token” after a successful selection.
    • Improved withdrawal-page behavior when the withdrawal amount is missing, using a safer post-render redirect while showing the loading state.

Selecting USDT-Tron / USDT+USDC-Solana in the withdraw token drawer updated
context but the collapsed button resolved its label via areEvmAddressesEqual,
which is always false for base58 addresses — so the button kept reading
'Select a token' and the selection looked like it never happened. Fall back
to a case-insensitive literal match (keeping areEvmAddressesEqual for its
native-proxy aliasing on EVM tokens).
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6242.76 → 6243.14 (+0.38)
Findings: 0 net (+11 new, -11 resolved)

🆕 New findings (11)

  • critical complexity — src/components/Global/TokenSelector/TokenSelector.tsx — CC 126, MI 60.85, SLOC 331
  • critical complexity — src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 85, MI 52.73, SLOC 355
  • medium react-long-component — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 555 lines — split it
  • medium high-mdd — src/components/Global/TokenSelector/TokenSelector.tsx:74 — TokenSelector: MDD 128.7 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: MDD 92.9 (uses across many lines from declarations)
  • medium hotspot — src/app/(mobile-ui)/withdraw/crypto/page.tsx — 28 commits, +300/-178 lines since 6 months ago
  • medium high-mdd — src/components/Global/TokenSelector/TokenSelector.tsx:235 — : MDD 22.0 (uses across many lines from declarations)
  • low high-dlt — src/components/Global/TokenSelector/TokenSelector.tsx:235 — : DLT 21 (calls 21 distinct functions — high context load)
  • low high-mdd — src/components/Global/TokenSelector/TokenSelector.tsx:294 — buildTokensForChainArray: MDD 12.8 (uses across many lines from declarations)
  • low high-mdd — src/components/Global/TokenSelector/TokenSelector.tsx:298 — : MDD 12.9 (uses across many lines from declarations)
  • low high-mdd — src/components/Global/TokenSelector/TokenSelector.tsx:301 — processToken: MDD 10.7 (uses across many lines from declarations)

✅ Resolved (11)

  • src/components/Global/TokenSelector/TokenSelector.tsx — CC 125, MI 60.87, SLOC 331
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 83, MI 52.34, SLOC 351
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 548 lines — split it
  • src/components/Global/TokenSelector/TokenSelector.tsx:74 — TokenSelector: MDD 124.8 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: MDD 91.1 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — 27 commits, +288/-173 lines since 6 months ago
  • src/components/Global/TokenSelector/TokenSelector.tsx:225 — : MDD 22.0 (uses across many lines from declarations)
  • src/components/Global/TokenSelector/TokenSelector.tsx:225 — : DLT 21 (calls 21 distinct functions — high context load)
  • src/components/Global/TokenSelector/TokenSelector.tsx:284 — buildTokensForChainArray: MDD 12.8 (uses across many lines from declarations)
  • src/components/Global/TokenSelector/TokenSelector.tsx:288 — : MDD 12.9 (uses across many lines from declarations)
  • src/components/Global/TokenSelector/TokenSelector.tsx:291 — processToken: MDD 10.7 (uses across many lines from declarations)

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2062 ran, 0 failed, 0 skipped, 34.0s

📊 Coverage (unit)

metric %
statements 60.1%
branches 43.9%
functions 49.0%
lines 60.5%
⏱ 10 slowest test cases
time test
3.6s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.1s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
0.1s src/utils/__tests__/demo-balance.test.ts › auto-refills a wallet older than the TTL on cold start
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 23, 2026 11:44am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The crypto withdrawal page now performs missing-amount navigation in an effect, while TokenSelector adds direct raw-address matching for non-EVM token selections.

Changes

Withdraw redirect

Layer / File(s) Summary
Effect-based withdrawal redirect
src/app/(mobile-ui)/withdraw/crypto/page.tsx
Replaces render-time navigation with a guarded useEffect and preserves the loading view during redirection.

Token selection

Layer / File(s) Summary
Non-EVM token lookup fallback
src/components/Global/TokenSelector/TokenSelector.tsx
Adds direct raw address matching alongside EVM address comparison when resolving selected token details.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main non-EVM token selection fix in the selector button.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hotfix/token-selector-non-evm-label

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

abalinda added 2 commits July 23, 2026 12:17
router.push during render is a React violation ('Cannot update Router while
rendering WithdrawCryptoPage') — Next 16's dev overlay hard-errors on any
direct entry or refresh of /withdraw/crypto, which blocked QA of the
non-EVM withdraw flow. Same behavior, one frame later, via useEffect.
@abalinda

Copy link
Copy Markdown
Contributor Author

@coderabbitai review bro

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@abalinda On it — reviewing #2490 now. 🐇

✅ 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.

@abalinda

Copy link
Copy Markdown
Contributor Author

@coderabbitai review now pls

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

@abalinda Reviewing PR #2490 now. 🐇

✅ 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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/components/Global/TokenSelector/TokenSelector.tsx`:
- Around line 180-188: Update the token lookup in the selected-token flow around
areEvmAddressesEqual so the fallback compares t.address and selectedTokenAddress
with exact case-sensitive equality. Preserve areEvmAddressesEqual for EVM alias
matching and retain the existing tokenDetails lookup behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32483471-ddfe-4c32-b76e-33d4ef54d94e

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd5969 and 209d011.

📒 Files selected for processing (2)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx
  • src/components/Global/TokenSelector/TokenSelector.tsx

Comment thread src/components/Global/TokenSelector/TokenSelector.tsx Outdated
@abalinda
abalinda marked this pull request as ready for review July 23, 2026 09:48
Comment thread src/components/Global/TokenSelector/TokenSelector.tsx Outdated
@kushagrasarathe

Copy link
Copy Markdown
Contributor

Review — code quality / DRY / bug risk / security

Trivial and safe. The literal fallback is correct: both operands come from the same registry and toLowerCase() is comparison-only (never stored), so there's no base58-corruption risk here — distinct registry tokens never differ only by address case, and native-proxy aliasing still resolves via areEvmAddressesEqual first.

The render→effect redirect fix is also correct — needsAmountRedirect is in the dep array and PeanutLoading still renders synchronously so there's no flash.

One note: this bundles two logically-independent fixes (selector label + render-phase-nav) in one PR. Both small and in the same route, disclosed in the description — fine, just flagging.

Also: touches withdraw/crypto/page.tsx in the same file as #2491 (different regions — redirect block vs confirm handler). No textual conflict expected, but whichever merges second may need a rebase.

Base58 is case-significant and both operands come from the same registry —
a case-insensitive compare could only mask an upstream case-corruption bug,
never fix a legitimate mismatch. areEvmAddressesEqual keeps covering EVM
checksum-case variance and native-proxy aliasing.
@abalinda
abalinda requested a review from jjramirezn July 23, 2026 11:38
@abalinda

abalinda commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@kushagrasarathe thanks — two updates since your pass: (1) the fallback moved from case-insensitive to exact equality (0a44cd6) per jota + CodeRabbit; your same-source-registry observation is exactly why exact is sufficient, and it additionally keeps the label honest as a canary if any path ever corrupts the selected address's casing. (2) On bundling: agreed it's two logical fixes — kept together because the redirect bug blocked QA of this exact flow; both disclosed in the description. Noted the #2491 overlap (different regions of page.tsx) — whichever merges second rebases.

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

APPROVE

Trivial, display-only, and the latest commit improved it beyond my original note.

  • The 0a44cd64 change from a case-insensitive compare to exact === is strictly more correct: base58 is case-significant and both operands come from the same registry, so a case-insensitive match could only mask an upstream corruption bug, never fix a legit mismatch. Good call.
  • Native-proxy/checksum aliasing still resolves via areEvmAddressesEqual first — EVM behavior unchanged.
  • The render→effect redirect fix is correct (needsAmountRedirect in deps, PeanutLoading still renders synchronously, no flash).
  • 209d0114 reverted an accidental src/content submodule bump — clean.

CI green apart from the pre-existing repo-wide no-explicit-any eslint failure (unrelated to this PR).

Note for the merge gate: @jjramirezn's earlier CHANGES_REQUESTED is what 0a44cd64 addresses — it still needs their re-review (or dismissal) to unblock, since my approval doesn't clear their request.

@jjramirezn
jjramirezn merged commit 03ba3bd into main Jul 23, 2026
23 of 25 checks passed
innolope-dev added a commit that referenced this pull request Jul 24, 2026
Brings in the merged PRs missing from the release: #2490 (token-selector
non-EVM label), #2491 (withdraw collateral-charge completion), #2499 (footer
legal-entity line).

Conflict in withdraw/crypto/page.tsx resolved to keep mobile-release's
localization while adopting #2491's fix: pass chargeId into sendMoney so
collateral-routed withdraws settle the charge server-side. Re-added the
@sentry/nextjs captureMessage import the localization pass had dropped, and
adapted crypto-withdraw-confirm.test.tsx to the localized page (context path,
next-intl + useFriendlyError mocks). Dropped the dead intentId var (unused on
main too).
innolope-dev added a commit that referenced this pull request Jul 24, 2026
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