Skip to content

fix(withdraw): complete the user-facing charge on collateral-routed crypto withdraws - #2484

Closed
abalinda wants to merge 31 commits into
mainfrom
fix/withdraw-collateral-charge-completion
Closed

fix(withdraw): complete the user-facing charge on collateral-routed crypto withdraws#2484
abalinda wants to merge 31 commits into
mainfrom
fix/withdraw-collateral-charge-completion

Conversation

@abalinda

@abalinda abalinda commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Crypto withdraws funded from Rain card collateral (wallet at $0 after auto-sweep — the norm for card users) succeed on-chain but stay stuck PENDING and never appear in Activity, while the balance visibly drops. Users read this as lost funds — 98 stuck charges / 55 users in the last 14 days, all CRYPTO_WITHDRAW (Notion P2 task).

Root cause: the withdraw page was the only charge-backed spend flow that neither passed chargeId into sendMoney nor called recordPayment for collateral-routed same-chain spends. The user-facing charge intent therefore never completed. History's read side was already built expecting the charge to be the canonical row: it hides never-paid charges as "abandoned drafts" and hides the rain-prepare intent as a "phantom" — so a successful withdrawal was completely invisible.

The fix mirrors the proven direct-send / contribute-pot / semantic-request contract:

  • Pass chargeDetails.uuid as chargeId — on collateral-only the backend uses the charge as the preparation and settles it server-side in /submit (trusted completion; live on prod since 08004d52).
  • Always call recordPaymentmixed spends rely on it (the kernel's plain usdc.transfer(recipient) is validator-matchable); for collateral-only the backend routes it through the same trusted-completion path idempotently — the designed recovery net when /submit's post-mining bookkeeping fails (the backend comment explicitly names FE recordPayment as that recovery).
  • Refresh 3 stale comments (useSpendBundle, useSendMoney, services/rain) that instructed callers to SKIP recordPayment on collateral-only — the guidance that seeded this bug.

Design notes / accepted trade-offs

  • recordPayment failures on collateral-routed same-chain paths are tolerated (Sentry warning + success view without payment details): funds have already moved, and for collateral-only the backend has already settled the charge. A mixed-path double failure leaves the charge PENDING exactly as pre-fix — never worse than today.
  • Mixed same-chain spends only record with a mined receipt hash. If the receipt wait timed out, the kernel path returns only the userOp hash — the validator can never match that, and retry-exhaustion would flip a successful withdrawal to a false FAILED. We skip the record (pre-fix behavior, PENDING) with a Sentry breadcrumb instead. collateral-only is unaffected: its hash is a backend-broadcast EVM tx and its charge settles via the trusted path, receipt or not.
  • Cross-chain behavior is byte-identical to origin/dev: recordPayment always fired there and still hard-fails on error.
  • Bonus safety: passing chargeId closes a pre-fix double-broadcast hazard — retrying after "submit succeeded but FE errored" now gets a 409 (Charge already completed) from /prepare instead of silently preparing + broadcasting a second collateral withdrawal.
  • Backfill of the 98 historical stuck charges is a separate ops task, not this PR.

Risks / breaking changes

  • FE-only. No BE change required — the charge-backed prepare + trusted completion + idempotent recordPayment re-entry contract is live on prod main.
  • Blast radius: crypto-withdraw confirm flow only. smart-only and cross-chain paths verified unchanged (independent review sweep of every sendMoney/spend caller found no other flow with this bug shape).

QA

  • npm test: 148 suites green. New suite crypto-withdraw-confirm.test.tsx (6 tests): chargeId reaches sendMoney; mixed same-chain records the mined hash; collateral soft-fail still lands on the success view; mixed-without-receipt skips the record; cross-chain rethrow preserved; smart-only hard-fail preserved. The bug-pinning tests verified to fail against pre-fix origin/dev.
  • typecheck + prettier clean.
  • To verify manually (sandbox): with wallet USDC at $0 and funded card collateral, withdraw same-chain USDC to an external address → the withdrawal must appear as COMPLETED in Activity and the charge must not stay PENDING.

Summary by CodeRabbit

  • Bug Fixes
    • Improved crypto withdrawal confirmation so charges reliably complete and the flow advances correctly across collateral, mixed, cross-chain, and smart-only routing.
    • Added safeguards so mixed withdrawals lacking a mined transaction hash don’t trigger incorrect payment recording.
    • If post-transaction payment recording recovery fails (for collateral routes), users can still reach the success/status view; true failure paths still surface errors appropriately.
  • Tests
    • Added regression tests covering payment recovery, transaction-hash handling, and analytics/error-state behavior.
  • Documentation
    • Updated inline guidance for withdrawal/payment reconciliation behavior to match the new completion semantics.

kushagrasarathe and others added 27 commits July 17, 2026 14:47
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
Hotfix debt from the 2026-07-16/17 card-issuance outage. main carries the
callback-ref fix that dev lacks; without this back-merge the next dev → main
release would revert prod to the broken wrapper.
…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
…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
…ithdraws

Same-chain crypto withdraws funded from Rain card collateral succeeded
on-chain but never completed their charge intent — the page skipped
recordPayment and never passed chargeId, so the charge rotted PENDING
until the reaper failed it. History hides both rows (phantom +
abandoned-draft filters), so users watched their balance drop with no
Activity entry (98 charges / 55 users in 14 days).

Mirror the direct-send contract: pass chargeDetails.uuid into sendMoney
(the backend uses the charge as the prep and settles it in /submit's
trusted completion) and always recordPayment — mixed spends rely on it,
collateral-only re-enters trusted completion idempotently as the
recovery net. recordPayment failures after a collateral-routed
same-chain spend degrade to the success view instead of a false
'failed': the funds have already moved.

Also refresh the three stale hook/service comments that instructed
callers to SKIP recordPayment on collateral-only — the guidance that
seeded this bug.
@vercel

vercel Bot commented Jul 22, 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 22, 2026 11:49pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 995321ff-65d2-4c77-ad5f-9e602eeb4f69

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed60ee and a5fcccd.

📒 Files selected for processing (3)
  • src/app/(mobile-ui)/withdraw/crypto/__tests__/crypto-withdraw-confirm.test.tsx
  • src/hooks/wallet/useSpendBundle.ts
  • src/hooks/wallet/useWallet.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/wallet/useSpendBundle.ts
  • src/app/(mobile-ui)/withdraw/crypto/tests/crypto-withdraw-confirm.test.tsx

📝 Walkthrough

Walkthrough

Updates crypto withdrawal confirmation so collateral-routed charges receive the charge UUID, payment recording is attempted with route-specific safeguards, and failures are handled differently across collateral, mixed, cross-chain, and smart-only flows. Adds regression tests covering these scenarios.

Changes

Crypto withdrawal reconciliation

Layer / File(s) Summary
Charge completion contract
src/hooks/wallet/useSendMoney.ts, src/hooks/wallet/useSpendBundle.ts, src/services/rain.ts, src/hooks/wallet/useWallet.ts
Documentation now describes idempotent recordPayment recovery after charge completion and clarifies chargeId behavior for collateral-only, smart-only, and mixed strategies.
Confirmation reconciliation
src/app/(mobile-ui)/withdraw/crypto/page.tsx
Same-chain sendMoney calls receive the charge UUID; mixed withdrawals without mined hashes skip payment recording, while collateral-only recording failures allow success progression and other failures propagate.
Route-specific regression coverage
src/app/(mobile-ui)/withdraw/crypto/__tests__/crypto-withdraw-confirm.test.tsx
Tests cover charge propagation, mined transaction hashes, missing mixed-spend receipts, collateral recovery, and failure behavior for cross-chain and smart-only routes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WithdrawCryptoPage
  participant sendMoney
  participant recordPayment
  participant Sentry
  WithdrawCryptoPage->>sendMoney: Submit withdrawal with charge UUID
  sendMoney-->>WithdrawCryptoPage: Return transaction and strategy
  WithdrawCryptoPage->>recordPayment: Record mined transaction payment
  recordPayment-->>WithdrawCryptoPage: Resolve or reject
  WithdrawCryptoPage->>Sentry: Capture skipped or downgraded recording diagnostic
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: jjramirezn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: completing user-facing charges for collateral-routed crypto withdrawals.
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.
✨ 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 fix/withdraw-collateral-charge-completion

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


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

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6233.75 → 6235.1 (+1.35)
Findings: 0 net (+26 new, -26 resolved)

🆕 New findings (26)

  • critical complexity — src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 81, MI 52.39, SLOC 355
  • medium react-long-component — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 569 lines — split it
  • medium high-mdd — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: MDD 105.1 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useSpendBundle.ts:108 — useSpendBundle: MDD 59.3 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: DLT 53 (calls 53 distinct functions — high context load)
  • medium high-mdd — src/hooks/wallet/useSpendBundle.ts:123 — : MDD 46.5 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useSendMoney.ts:50 — useSendMoney: MDD 39.0 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useWallet.ts:53 — useWallet: MDD 38.5 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/crypto/page.tsx:278 — : MDD 38.2 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/wallet/useSpendBundle.ts:108 — useSpendBundle: DLT 32 (calls 32 distinct functions — high context load)
  • medium method-complexity — src/services/rain.ts:328 — rainRequest CC 28 SLOC 61
  • medium hotspot — src/app/(mobile-ui)/withdraw/crypto/page.tsx — 26 commits, +333/-197 lines since 6 months ago
  • medium hotspot — src/hooks/wallet/useSpendBundle.ts — 26 commits, +963/-603 lines since 6 months ago
  • medium hotspot — src/services/rain.ts — 24 commits, +830/-96 lines since 6 months ago
  • medium high-mdd — src/services/rain.ts:328 — rainRequest: MDD 23.2 (uses across many lines from declarations)
  • medium structural-dup — hooks/wallet/useSignSpendBundle.ts:272 — 22 duplicate lines / 98 tokens with hooks/wallet/useSpendBundle.ts:282
  • medium method-complexity — src/app/(mobile-ui)/withdraw/crypto/page.tsx:278 — CC 19 SLOC 102
  • low high-dlt — src/hooks/wallet/useSpendBundle.ts:123 — : DLT 24 (calls 24 distinct functions — high context load)
  • low high-mdd — src/hooks/wallet/useSendMoney.ts:113 — onError: MDD 18.5 (uses across many lines from declarations)
  • low high-dlt — src/app/(mobile-ui)/withdraw/crypto/page.tsx:278 — : DLT 17 (calls 17 distinct functions — high context load)

…and 6 more.

✅ Resolved (26)

  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — CC 78, MI 53.1, SLOC 335
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 528 lines — split it
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: MDD 92.3 (uses across many lines from declarations)
  • src/hooks/wallet/useSpendBundle.ts:103 — useSpendBundle: MDD 58.3 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage: DLT 52 (calls 52 distinct functions — high context load)
  • src/hooks/wallet/useSpendBundle.ts:118 — : MDD 45.5 (uses across many lines from declarations)
  • src/hooks/wallet/useSendMoney.ts:49 — useSendMoney: MDD 39.0 (uses across many lines from declarations)
  • src/hooks/wallet/useWallet.ts:53 — useWallet: MDD 38.0 (uses across many lines from declarations)
  • src/hooks/wallet/useSpendBundle.ts:103 — useSpendBundle: DLT 32 (calls 32 distinct functions — high context load)
  • src/services/rain.ts:327 — rainRequest CC 28 SLOC 61
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:278 — : MDD 23.6 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — 24 commits, +253/-158 lines since 6 months ago
  • src/hooks/wallet/useSpendBundle.ts — 24 commits, +949/-596 lines since 6 months ago
  • src/utils/general.utils.ts — 24 commits, +134/-152 lines since 6 months ago
  • src/services/rain.ts:327 — rainRequest: MDD 23.2 (uses across many lines from declarations)
  • hooks/wallet/useSignSpendBundle.ts:272 — 22 duplicate lines / 98 tokens with hooks/wallet/useSpendBundle.ts:275
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:278 — CC 16 SLOC 82
  • src/hooks/wallet/useSpendBundle.ts:118 — : DLT 24 (calls 24 distinct functions — high context load)
  • src/hooks/wallet/useSendMoney.ts:112 — onError: MDD 18.5 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:278 — : DLT 16 (calls 16 distinct functions — high context load)

…and 6 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/app/(mobile-ui)/withdraw/crypto/page.tsx 17.2 17.8 +0.6

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2020 ran, 0 failed, 0 skipped, 25.3s

📊 Coverage (unit)

metric %
statements 59.9%
branches 43.6%
functions 48.9%
lines 60.2%
⏱ 10 slowest test cases
time test
2.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.8s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
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/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.2s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle maximum length (17 digits) US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

Adversarial review find: on a receipt-wait timeout the kernel path
returns only the userOp hash. Feeding that to the validator for an
untagged mixed charge can never match a tx — retry exhaustion would
flip the successful withdrawal to FAILED, worse than the pre-fix stuck
PENDING. Skip the record with a Sentry breadcrumb in that case
(collateral-only stays safe: backend-broadcast tx hash + trusted-path
settlement). Also pin cross-chain rethrow + this skip in tests.
@abalinda

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
✅ 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/app/`(mobile-ui)/withdraw/crypto/__tests__/crypto-withdraw-confirm.test.tsx:
- Around line 351-367: Extend the cross-chain mixed withdrawal test coverage
around confirm to mock a successful sendTransactions result with a userOpHash
but receipt: null, then verify the unmined transaction path handles
recordPayment correctly without using a mined transaction hash. Preserve the
existing assertions for failure reporting and ensure the test specifically
exercises the mixed cross-chain scenario.
🪄 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: 28985bd7-aa79-44f3-8fea-d3babc889156

📥 Commits

Reviewing files that changed from the base of the PR and between 15df185 and 1ed60ee.

📒 Files selected for processing (5)
  • src/app/(mobile-ui)/withdraw/crypto/__tests__/crypto-withdraw-confirm.test.tsx
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx
  • src/hooks/wallet/useSendMoney.ts
  • src/hooks/wallet/useSpendBundle.ts
  • src/services/rain.ts

abalinda added 2 commits July 23, 2026 02:40
…ce + isDiffToken routing

Structured review pass: the bug-seeding 'skip recordPayment' guidance
survived in two more comments (useSpendBundle prepare call-site,
useWallet sendMoney return doc) — the exact rot this PR exists to purge.
Also close the two coverage gaps a scoping regression could slip
through: mixed same-chain tolerance and token-boundary-only routing.
@abalinda

Copy link
Copy Markdown
Contributor Author

Superseded by #2491 — same 5 commits cherry-picked onto main so the PR contains only the fix (this branch was cut from dev, which dragged ~37 unrelated dev files into the diff and would have shipped unreleased dev work to prod on merge). Closing.

@abalinda abalinda closed this Jul 23, 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.

5 participants