chore: backmerge withdrawal fee safeguards into dev (TASK-22662) - #3229
Conversation
Peanut stops sponsoring the withdrawal fee on Ethereum, Tron and Solana, and Rhino enables the charge on their account. The confirm screen already reads the fee from Rhino's authenticated quote, so the number appears on its own the moment Rhino starts charging — and disappears again if a network goes back to being sponsored. Nothing in the client decides which networks charge. What this adds is the part the screen was getting wrong either way: The "i" beside Network fee now says fees differ by network and names one that is free, so the row is where a user learns that picking another network is an option. The charged variant also stops claiming the fee is "already included in the amount you pay" — a withdrawal is quoted pay-mode, so the fee comes out of what the recipient receives, never on top of what the sender pays. A quote whose fee takes the whole amount is now blocked. Rhino's route minimum exists to stop the bridge rejecting a small deposit, not to keep the fee below the amount, so a small withdrawal to an expensive network could otherwise be confirmed with nothing left to deliver.
Four holes, all on the same guard. The gate measured the fee against `?amount=`, which stays editable after review, while the broadcast spends the amount pinned to the charge. Raising the URL amount cleared the gate and then moved the original one, delivering nothing. It reads the pinned amount now, and the spend re-checks the fee immediately before broadcasting — a render-time value cannot guard a click that happens later. Retry sits on its own branch and carried none of the gates, so a failed send was a way past them. It now refuses for the same reasons the confirm button does. That hole predates the fee: it already let a retry through the Rhino route minimum, which strands the deposit at the SDA. Solana's floor is $1. Rhino still accepts $0.50, but a delivery Peanut no longer sponsors costs about that much, so anything under a dollar arrives as dust. product/networks.md said $1 already; nothing enforced it. A failed quote no longer explains itself. The row shows a dash, and both tooltip strings are untrue there — one promises free delivery, the other describes a fee nobody quoted.
The gates I just put on Retry also caught the one state where Retry is not trying to spend anything. When the on-chain leg lands and only recordPayment fails, the page keeps executedSpendRef so Retry replays the bookkeeping and never re-broadcasts — but the wallet has emptied by then, so the original spend reads as unaffordable and the balance gate disabled the only recovery. A full-balance withdrawal hits this every time, and the charge is left PENDING and missing from Activity with no way back. The gates now step aside once the spend has landed, which is what handleConfirmWithdrawal already does for the amount and fee checks. Before the spend they hold exactly as they did.
…-from-quote feat(withdraw): show the network fee Rhino quotes for the delivery
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks 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 |
Code-analysis diffPainscore total: 8033.98 → 8035.61 (+1.63) 🆕 New findings (22)
…and 2 more. ✅ Resolved (21)
…and 1 more. 📈 Painscore deltas (top movers)
|
|
/chip review |
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Chip review — no blocking findings — this is not an approval
The fee safeguards match the quote contract and product schedule, but the new proactive fee blocker bypasses localization.
Findings
-
MINOR · src/app/(mobile-ui)/withdraw/crypto/page.tsx:885 · Localize the new fee-blocking message
With the app in es-419 or pt-BR, a cross-chain quote whose fee consumes the withdrawal enters this branch and shows this hardcoded English blocker, even though the same fee failure already has localized strings. Move this message behindt(...)(adding placeholders for network and fee if those details should remain) so supported locales do not fall back to English. -
MINOR · src/utils/cross-chain-fee.utils.ts:71 · [claude-opus] Solana's $1 floor is missing from the product-truth summary lines that support reads
This PR makes the app enforce a $1 floor on Solana (SOLANA_MIN_WITHDRAW_USD, keyed assolanain CHAIN_MIN_WITHDRAW_USD; enforced at charge creation in src/app/(mobile-ui)/withdraw/crypto/page.tsx:345 and for Rhino-bridged link claims via src/utils/claim-min-guard.ts:22).
The code is right — /home/chip/mono/product/networks.md:34 already says solana minimum_withdrawal: "$1.00", and the fee entry ($0.50 + 0.07%, unsponsored since 2026-09-16) is exactly why the floor exists. But three places in product truth still enumerate the enforced minimums as $0.50/$5/$10 with no Solana entry, and those are the lines a support agent or a generated help page reads:
- product/networks.md:7 — "Crypto withdrawal minimums are per network, as the app enforces them ($0.50 default; Ethereum $5; Tron $10)"
- product/networks.md:363 — limits table row "Crypto send (any other network) | Per network ($0.50 default; Ethereum $5; Tron $10)"
- product/send-links.md:25 — "Floor is the destination network's: $0.50 default, $5 Ethereum, $10 Tron (see networks.md)" — and claim-min-guard.ts uses the same table, so Solana link claims now floor at $1 too.
Effect: a user is told $0.50 is enough for a Solana withdrawal or link claim and the app rejects it. Fix in mono (update-content skill, not this PR): add Solana $1 to all three enumerations so they agree with networks.md:34.
Checked clean
- Exact head, base, trusted author, and detached worktree identity
- Quoted fee, pay amount, receive amount, expiry, and no-broadcast guards against the backend Rhino quote contract
- Solana, Ethereum, and Tron fee/minimum behavior against canonical product network and pricing sources
- Record-only retry behavior after funds move and CTA gate coverage
- Relevant unit, typecheck, lint, format, screen-test, and authorship checks were green; visual/deploy checks were still running
Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.
Third opinion by claude-opus: 1 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.
Exact head: ee2547b13351 · Context: repo, product, api-sibling · Took 10m
Summary
Backmerge production PR #3224 into dev. Withdrawal confirmations show the quoted network fee, reject fees that consume the withdrawal, preserve record-only retries, and enforce Solana's $1 floor.
Task
TASK-22662 — continuation of the main-to-dev backmerge work.
Source fix: #3224.
Integration and risk
Main head:
c8ea1c49fa8dca71be50178b60fc0dbf7bb996a3. The merge is conflict-free. This preserves dev changes and adds no behavior beyond production. The changed withdrawal gates affect money paths. No backend contract change or migration.Validation
Full local unit suite: 628 suites and 7,766 tests passed; seven tests skipped. Typecheck and repository formatting passed after syncing dev. The production build passed before the final dev sync; full tests and typecheck were repeated after the sync. CI and current-head automated review must pass before merge.
Visual evidence and documentation
Production screenshots and regression evidence: #3224. Fresh dev screenshots: none; this is an unchanged backmerge of the shipped implementation and its tests. Review the source screenshots alongside the dev integration.
Product facts already reflect the production change. Source PR #3224 records separate customer-content follow-ups for fee statements; this backmerge does not include content changes.
Existing documentation follow-up, verified against mono
origin/main(9d76c2da): customer pages undercontent/help/{fees-pricing,withdraw-crypto},content/withdraw/{ethereum,tron,solana},content/pricing, andcontent/supported-networksstill contain blanket free-withdrawal claims. Terms §7.1 (content/legal/terms/en.md) still promises blockchain fee coverage. Product sources already reflect the exceptions. Content and legal-owner edits remain separate from this backmerge.Accepted follow-ups from current-head review
Chip, Kimi, and Claude reported no blocking findings at
ee2547b13351ab40da65fbbb32c93eb80c12e815. Preserve the shipped implementation in this merge-only PR. The inherited English-only fee blocker needs a separate localization fix. Product minimum summaries inproduct/networks.mdandproduct/send-links.mdalso need Solana's $1 floor. These follow-ups, plus the existing customer/legal wording debt, are recorded on TASK-22662. They are not fixed by this PR.