fix(withdraw): show non-EVM (Tron/Solana) token selection in the selector button#2488
fix(withdraw): show non-EVM (Tron/Solana) token selection in the selector button#2488abalinda wants to merge 35 commits into
Conversation
Name mismatch (Bridge BE01) is the top cause of returned SEPA/ACH deposits, but the beneficiary/account-holder name was shown as a plain row while amount and reference got prominent warnings. Add a warning so users send from an account in their own name and enter the recipient name exactly as shown.
Fold the name-match guidance into the existing 'Double check before sending' checklist card instead of a separate warning card, so there is one source of pre-send do-nots (no drift, no signal dilution from a fourth alarm card). Omit it for MX/SPEI, where paying from a third-party account is a supported flow and the own-name rule does not apply.
BE01 validates the creditor (recipient) name too, not just the sender account. Add the recipient-name-exact line alongside the sender rule in the same non-MX branch.
fix: warn about name mismatch on bank deposit details
…lent again
The Jul-16/17 card outage ran 19h and was found by a customer in Dhaka, not by
us. The reason is structural, not bad luck: a WebSDK that never launches throws
nothing, requests nothing and logs nothing. Sentry cannot see code that does not
run, so the only trace was throughput quietly going to zero. This closes that
class of failure rather than the one instance of it.
Watchdog: if the modal is open and the SDK has not launched within 20s, show the
error UI instead of the infinite spinner. Deliberately keyed on `visible` ALONE —
every silent path (null container, token that never arrives, script that never
loads) ends with the init effect simply not running, so anything keyed on those
deps would also never run. Watching the one fact the user experiences ("I opened
it and nothing happened") catches the whole class, including causes we have not
thought of yet.
Telemetry: kyc_sdk_launched / kyc_sdk_launch_timeout / kyc_sdk_init_failed. The
timeout event carries hadAccessToken / sdkScriptLoaded / hadContainer, which are
the three facts that distinguish the silent stalls from each other. Makes
"opened but never launched" alertable instead of archaeology.
Also corrects the mechanism comments shipped in #2441: the one-commit delay comes
from headlessui's <Transition> promoting tree state inside an effect, NOT the
Portal (isolation-tested: dialog-only mounts the container synchronously;
transition-only does not). The fix was right, the explanation was not, and a
wrong comment misleads the next reader.
Tests: the watchdog fires and surfaces the error UI when the SDK never launches,
and does not fire once it has.
feat(kyc): watchdog + telemetry so a stuck Sumsub SDK can never be silent again
The in-app card is composited layer-by-layer in code, so it drifted from the finalised Rain art (what Apple Wallet shows): it rendered a mascot with no PEANUT wordmark and an inverted-white Visa with no tier. Swap the top row to the PEANUT lockup (mascot + black wordmark) and a black VISA with a Platinum tier line. Closes TASK-20469.
The composited hand used -rotate-15 + h-full + a negative top inset, which shoved it up so the fingertips crowded the Visa and the gesture tilted off from the finalised Rain art. Anchor it upright at the bottom (h-90%) so the fingertips clear the top logos and the arm sweeps to the bottom-right corner.
Two corrections after seeing the real card: - The PEANUT wordmark was rendering white (PEANUT_LOGO is the dark-bg variant). Use PEANUTMAN + PEANUT_LOGO_BLACK for the black lockup the finalised art shows. - The bottom-anchored hand covered the eye toggle and the revealed PAN/CVV. Slide it diagonally off the bottom-right corner while details are shown or being fetched (loading skeletons included), and move the masked/error eye inline with the number so it stays in a hand-free zone. The hand slides back when the card is re-masked.
Lowering the per-transaction limit left the card holding more than its new target: the auto-balancer only ever tops up (rebalance-decision no-ops when collateral >= limit), so the excess sat in the collateral contract indefinitely. After the limit PATCH succeeds, sign a FORCED collateral-only withdrawal of the excess back to the user's smart wallet - the PATCH-first ordering keeps the auto-balancer from racing the withdrawal by topping the collateral back up. UX: the user only sees the passkey prompt. The unified displayed balance is identical before/after (both buckets are folded into one), and the intent kind AUTO_REBALANCE maps to INTERNAL_TRANSFER, which history hides. Failures are non-fatal by design: the limit change already stuck, and re-saving the limit retries the return. forceStrategy on useSignSpendBundle exists because live-balance routing would pick smart-only (a self-transfer no-op) whenever the smart wallet covers the amount - the exact trap the lock-card flow comments around.
CodeRabbit review: the forced-path insufficient branch skipped the CARD_WITHDRAW_FAILED capture resolveSpendStrategy emits, leaving a gap in the withdraw funnel for the excess-return flow; and the forced branch had no direct tests.
…mit-decrease feat(card): return excess collateral to wallet on limit decrease
Backend now marks every Bridge rail blocked for UK residents with reason code 'uk_resident_blocked'. Surface it honestly instead of the generic 'we couldn't unlock this / contact support' (ID-failure) framing: - InitiateKycModal gains a 'region-unavailable' variant — 'Not available in the UK', regulatory copy, single 'Got it' dismiss (no verify, no contact-support) - add-money + withdraw bank pages pick that variant when the gate's reason is uk_resident_blocked Card block rides on the existing eligibility machinery (BE denylist).
- title 'Not available for UK residents' (conveys residence, not the destination country — a UK resident is blocked even withdrawing to SEPA) - drop the card mention from the bank-flow copy - CTA 'Withdraw funds' -> /withdraw so users can exit via crypto - wire the region-unavailable variant into AddWithdrawCountriesList (the country-selection modal), not just the add-money/withdraw bank pages
fix(card): match in-app card art to finalised Visa Platinum lockup
…ailable check (Hugo review) The 'reason.code === uk_resident_blocked' -> region-unavailable ternary was copy-pasted across the add-money/withdraw bank pages and the country-list. Centralize in one helper alongside getKycModalVariant.
The bare-badge /invite screen labeled its primary CTA "Claim your badge" while actually routing to signup, and framed login as an alternative to claiming rather than the other path to it. Only logged-out visitors ever see this variant (logged-in users are auto-claimed), so the buttons now say what they do: Sign up / Log in, with the description explaining that either one claims the badge.
Keeps all per-variant strings (title, description, ctaLabel, loginLabel) in one place instead of burying one ternary in the JSX.
feat(kyc): honest UK-resident block on Bridge bank flows (TASK-20729)
…copy fix: honest signup/login CTAs on vanity badge claim screen
SimpleFi (early Argentina QR provider) is deprecated and its creation path was removed in peanut-api-ts#1218. Drop the FE-side remnants: the payment-app logo asset + export, the DEPRECATED_SIMPLEFI avatar branch in getAvatarUrl, and the SIMPLEFI keys in test mocks. Historical rows are safe: 247 transaction intents with provider DEPRECATED_SIMPLEFI still arrive on the wire, so the Provider union keeps the value (mirrors DEPRECATED_SQUID), their kind=QR_PAY still dispatches to the qrPay strategy (locked by the vendored qr_pay-deprecated_simplefi render-snapshot case), and getAvatarUrl now falls back to undefined -> generic default avatar (new test).
Grafana has been decommissioned at Peanut; support agents now use the prod DB + PostHog per the updated ops playbook (mono 2d873bc). Drop the dead links the FE still generated: the Crisp session-data grafana_dashboard entry (useCrispUserData / crisp.ts / useCrispProxyUrl / crisp-proxy page), the GRAFANA_DASHBOARD_BASE_URL constant, and the dev invites-graph click-to-Grafana handler (click now just selects the node). The Crisp session-data push itself is unchanged beyond the deleted entry.
…afana chore: remove decommissioned SimpleFi + Grafana remnants
The Welcome-@anon handbook's real pitfalls (comms rules, urgency hierarchy, task ownership, DoD, sick/weekend policy, security, invoicing) as a 23-question quiz, so new joiners actually retain them. Single self-contained file in public/ per the quiz paved path: zero deps, zero build-graph impact. Linked from /dev with a plain <a> (public/ files aren't app routes). Prettier-ignored like dev-quiz: hand-tuned inline CSS/JS.
Safari drops transient user activation across the awaited toBlob, so clipboard.write always rejected and COPY silently became a download — construct the ClipboardItem synchronously around the Promise<Blob>. Guard the null-blob path (createObjectURL(null) threw uncaught and killed both buttons), defer revokeObjectURL past the download fetch, skip petal-rain repaints while the tab is hidden, and derive the boot banner count from QUESTIONS.length.
The add-money flow asked "Bank or Crypto" twice — once on a standalone method screen, then again on the per-country screen after picking a country. Customers reported this as buggy/confusing (TASK-20033). Collapse the redundant first screen: /add-money now lands directly on the country list (with a crypto shortcut + the offramp-migrate entry), and the country-aware per-country screen remains the single place the method is chosen. The per-country screen already knows each country's real methods and coming-soon states, so no country regresses.
…parity
- useEffect now depends on countryFromQuery + (stable) resetOnrampFlow, so
stale onramp state is cleared on native back-nav to the root, not only on
mount (CodeRabbit + code-review). Also clears the exhaustive-deps warning.
- drop the unreachable countryFromQuery branch in handleBack (the ?country
sub-views early-return their own NavHeader).
- fire DEPOSIT_METHOD_SELECTED{method_type:'crypto'} on network select so
root crypto deposits match the bank event in the deposit-method funnel.
- AddWithdrawCountriesList add-flow back-nav → '/add-money' (drop the now-dead
?method=bank param).
- cover the offramp-badge landing card (present with badge, absent without).
…ed intent
Manteca entry points (MantecaAddMoney, qr-pay, withdraw/manteca,
MantecaFlowManager) instantiate useMultiPhaseKycFlow({}) and pass 'LATAM'
only as an override to handleInitiateKyc, so completion/abandon captures
read the undefined hook-level regionIntent: LATAM successes fired as
kyc_approved with region_intent None and abandons as kyc_abandoned. That
made manteca_kyc_completed undercount and paged a false 'Funnel dead:
manteca-kyc' alert (2026-07-22). Track the last initiated intent in a ref
and let it win over the prop for all lifecycle captures.
…s-attribution fix(analytics): attribute manteca KYC lifecycle events to the initiated intent
Switch from Option A (which deleted the Bank/Crypto method screen and left an
ambiguous country list — users couldn't tell bank was an option) to Option B:
- Keep the up-front Bank/Crypto method screen (bank is clearly visible).
- After the user picks Bank + a country, go STRAIGHT to that country's deposit
screen instead of showing a second "From Bank / From Crypto" list:
- AR/BR -> /add-money/<country>/manteca (Manteca surfaces Pix/Mercado Pago)
- other bank-supported countries -> /add-money/<country>/bank
Every add method for a given country converges on one of these two screens
anyway, so the per-country list was pure repetition where bank is live.
- Coming-soon countries (bank not enabled) keep the per-country screen — there
it's still useful: it shows the "soon" bank state and the crypto fallback.
Net effect: the user clicks "bank" once, up front; the duplicate second
selection is gone. This reverts the Option A page/view/test churn — the diff is
now just handleCountryClick routing (+ effect-deps cleanup) and its tests.
feat: Peanut Welcome Club onboarding quiz (static HTML in public/)
…hod-selection fix(add-money): remove the duplicate method selection, not the method screen (TASK-20033)
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).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code-analysis diffPainscore total: 6235.04 → 6235.24 (+0.2) 🆕 New findings (7)
✅ Resolved (7)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
Superseded by #2490 — re-targeted to |
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— alwaysfalsefor 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.
areEvmAddressesEqualstays first for its native-proxy aliasing on EVM tokens (0xeee…↔0x000…).Pairs with peanutprotocol/peanut-api-ts#1229, 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.
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
countryCurrencyMappingsuite failure is unrelated).🤖 Generated with Claude Code
Design notes / adversarial review
selectedTokenDataderivation → 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.areEvmAddressesEqualis a strict superset of case-insensitive equality for valid EVM addresses (adds native-proxy aliasing), so the fallback can never resolve a different token than the comparator would.