Skip to content

fix: keep fee quotes and withdrawal amounts consistent (TASK-19427) - #3372

Open
jjramirezn wants to merge 29 commits into
devfrom
codex/TASK-19427-fees-v2-ui
Open

jjramirezn wants to merge 29 commits into
devfrom
codex/TASK-19427-fees-v2-ui

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Rates and fees now preserve the amount and selected currency through saved accounts, new destinations and country method selection. A dollar entry reaches review as dollars; its rounded bank estimate never becomes a new amount to spend. Bank withdrawals enforce the minimum in the payout currency; saved Brazil accounts enter PIX with an editable USD estimate.

Conversion reviews show the rate and receipt amount without a misleading zero-fee or sponsored row. USD wire withdrawals retain the server’s fee and net-payout display; ACH remains free. Marketing says “Rate shown upfront.” This PR changes display and validation; Bridge collection is separate.

Tracking: TASK-19427 · Fees v2. Also covers TASK-21369, TASK-22297, TASK-22235, TASK-22294, TASK-20721, TASK-22309, TASK-22257 and TASK-21104.

Validation at d7c3712a5 (September 28): 797 suites and 10,764 tests pass (5 skipped). Production build, formatting, typecheck, API contract and focused lint checks pass. The merge preserves the latest rounding, quote-refresh, server lock-TTL, wire-fee and navigation fixes. Required CI passes at this head. Chip’s gate accepts the base-only merge as no-own-changes, without repeating the full review. The preceding feature commit (9dafa3a2f) passed CI and Chip/security review without findings; this merge adds only the newer recipient-identity fix from dev.

Risk: shared converter changes affect the app, landing page and MDX embeds. Release with quote UI #3448, after API #1709. The paired UI supplies an estimated bank payout for dollar entry and requires review after a stale quote. Neither PR uses Fixed Outputs or collects a new FX margin. The converter uses the withdrawal quote for the bank-currency minimum and rounds its USD equivalent up to the cent. A cached display rate cannot enable the CTA when the withdrawal quote fails. Failed quotes block submission. ⚠️ Capacitor, iOS Safari and keyboard behavior need a device check before release.

Help is in mono #229; legal wording is in mono #238.

Mobile captures at 53b9c9e01, 375×667. Synthetic fixture quotes: EUR20/USD20 and ARS10,000/USD10. No payment was submitted; these show rendering, not settlement.

Bank conversion Manteca conversion
Bank conversion shows the rate and estimated receipt, with no zero-fee claim Manteca review shows the quoted amounts and rate without a sponsored fee row

Earlier minimum, PIX, marketing and saved-account captures remain available, labeled by commit. Delete the assets branch after merge.

@notion-workspace

Copy link
Copy Markdown

@vercel

vercel Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 28, 2026 6:46pm UTC

Request Review

@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: peanutprotocol/peanut-ui/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: a574804e-ce07-4c8c-8dd1-a8837d4d275e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

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

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 9073.87 → 9084.66 (+10.79)
Findings: +2 net (+156 new, -154 resolved)

🆕 New findings (156)

  • critical complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 234, MI 50.86, SLOC 849
  • critical complexity — src/features/payments/flows/qr-pay/useQrPayFlow.ts — CC 227, MI 51.83, SLOC 903
  • critical complexity — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 152, MI 56.65, SLOC 422
  • critical complexity — src/components/Global/TokenSelector/TokenSelector.tsx — CC 135, MI 59.56, SLOC 385
  • critical complexity — src/features/withdraw/useWithdrawRootFlow.ts — CC 114, MI 48.66, SLOC 393
  • critical complexity — src/features/withdraw/useBridgeOfframpFlow.ts — CC 109, MI 45.79, SLOC 487
  • critical complexity — src/components/Global/ExchangeRateWidget/index.tsx — CC 86, MI 55.15, SLOC 221
  • critical method-complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx:130 — MantecaBankWithdrawFlow CC 65 SLOC 297
  • critical complexity — src/features/withdraw/views/WithdrawMethodView.tsx — CC 64, MI 58.19, SLOC 231
  • critical complexity — src/utils/exchangeRateWidget.utils.ts — CC 55, MI 61.88, SLOC 107
  • critical complexity — src/features/withdraw/views/WithdrawBankReviewView.tsx — CC 54, MI 51.33, SLOC 105
  • critical complexity — src/dev/fixtures/registry.ts — CC 14, MI 23.46, SLOC 2021
  • high hotspot — src/app/(mobile-ui)/qr-pay/page.tsx — 145 commits, +1977/-3491 lines since 6 months ago
  • high hotspot — src/app/(mobile-ui)/withdraw/manteca/page.tsx — 118 commits, +1453/-877 lines since 6 months ago
  • high hotspot — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — 80 commits, +1061/-697 lines since 6 months ago
  • high hotspot — src/dev/fixtures/registry.ts — 74 commits, +2687/-388 lines since 6 months ago
  • high hotspot — src/utils/sentry.utils.ts — 63 commits, +1180/-417 lines since 6 months ago
  • high hotspot — src/components/Global/TokenSelector/TokenSelector.tsx — 53 commits, +950/-808 lines since 6 months ago
  • high method-complexity — src/features/withdraw/views/WithdrawBankReviewView.tsx:84 — CC 43 SLOC 73
  • high method-complexity — src/features/payments/flows/qr-pay/useQrPayFlow.ts:712 — CC 39 SLOC 235

…and 136 more.

✅ Resolved (154)

  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 234, MI 50.86, SLOC 850
  • src/features/payments/flows/qr-pay/useQrPayFlow.ts — CC 217, MI 52.26, SLOC 880
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 148, MI 56.78, SLOC 419
  • src/components/Global/TokenSelector/TokenSelector.tsx — CC 129, MI 59.73, SLOC 382
  • src/features/withdraw/useBridgeOfframpFlow.ts — CC 107, MI 45.89, SLOC 484
  • src/features/withdraw/useWithdrawRootFlow.ts — CC 107, MI 49.1, SLOC 381
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:125 — MantecaBankWithdrawFlow CC 65 SLOC 299
  • src/components/Global/ExchangeRateWidget/index.tsx — CC 60, MI 57.87, SLOC 188
  • src/features/withdraw/views/WithdrawMethodView.tsx — CC 59, MI 59.11, SLOC 214
  • src/features/withdraw/views/WithdrawBankReviewView.tsx — CC 53, MI 51.35, SLOC 105
  • src/dev/fixtures/registry.ts — CC 10, MI 21.42, SLOC 1980
  • src/app/(mobile-ui)/qr-pay/page.tsx — 144 commits, +1971/-3490 lines since 6 months ago
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — 117 commits, +1447/-876 lines since 6 months ago
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — 79 commits, +1047/-689 lines since 6 months ago
  • src/dev/fixtures/registry.ts — 72 commits, +2625/-386 lines since 6 months ago
  • src/utils/sentry.utils.ts — 62 commits, +1175/-411 lines since 6 months ago
  • src/components/Global/TokenSelector/TokenSelector.tsx — 52 commits, +918/-789 lines since 6 months ago
  • src/features/withdraw/views/WithdrawBankReviewView.tsx:84 — CC 42 SLOC 73
  • src/features/payments/flows/qr-pay/useQrPayFlow.ts:679 — CC 39 SLOC 235
  • src/utils/exchangeRateWidget.utils.ts — CC 38, MI 64.05, SLOC 77

…and 134 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/features/withdraw/useBankWithdrawMinimum.ts 0.0 7.0 +7.0
src/utils/exchangeRateWidget.utils.ts 5.5 6.8 +1.3
src/components/Global/ExchangeRateWidget/index.tsx 12.7 13.9 +1.2
src/components/Marketing/heroMarquee.ts 0.0 1.1 +1.1
src/features/withdraw/components/WithdrawCurrencyList.tsx 6.5 7.3 +0.8
src/features/withdraw/routes.ts 4.7 5.5 +0.8
src/features/withdraw/useWithdrawRootFlow.ts 14.7 15.5 +0.8
src/features/withdraw/views/WithdrawMethodView.tsx 10.2 11.0 +0.8
src/components/Marketing/mdx/Hero.tsx 3.8 4.5 +0.8
src/hooks/useGetExchangeRate.tsx 4.5 5.3 +0.8
src/features/payments/flows/qr-pay/useQrPayFlow.ts 18.9 19.6 +0.7
src/features/payments/flows/qr-pay/views/QrPayFormView.tsx 9.7 10.3 +0.7
src/components/LandingPage/landingStrings.ts 7.0 6.2 -0.8

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • ✅ unit: 10769 ran, 0 failed, 0 skipped, 3.8m

📊 Coverage (unit)

metric %
statements 82.6%
branches 72.7%
functions 77.0%
lines 83.8%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
3.9s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
3.2s src/features/withdraw/__tests__/useBankWithdrawMinimum.test.tsx › a refresh that fails blocks again — the retained quote is not current — and recovery restores it
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

One minimum-gating defect: the indicative widget quote can disagree with the execution-side withdrawal floor.

Findings

  • MAJOR · src/utils/exchangeRateWidget.utils.ts:174 · Use the withdrawal quote when gating Bridge minimums
    For USD→MXN at a display rate of 17, this sets the CTA floor to $3 (ceil(50/17)). If the Bridge rate request fails while /fx/rate serves its documented reference fallback, useGetExchangeRate returns 1 and both the withdrawal amount step and submit validation require $50. A $3 withdrawal therefore passes the widget gate but is rejected immediately afterward; differing live sell rates can cause the same mismatch near a boundary. Derive the gate from the same execution-side rate and failure state as withdrawal validation, or share an authoritative minimum between the two flows.

Checked clean

  • Verified the exact head and base SHA, PR metadata, and a clean detached worktree; did not read comments.
  • Reviewed quote state, swaps, CTA amount precision, saved-account handoff, currency search, localization, fixtures, and the relevant tests.
  • Checked the sibling API FX fallback and withdrawal rate/amount validation; no separate auth, credential, or injection change found.
  • At this head, unit, typecheck, ESLint, format, and native-export checks succeeded.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion by claude-opus: 0 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: 76979932b5f6 · Context: repo, product, peanut-api-ts · Took 14m

Comment thread src/utils/exchangeRateWidget.utils.ts Outdated
const countryPath = countryCurrencyMappings.find((currency) => currency.currencyCode === destinationCurrency)?.path
const countryIso2 = (countryPath && getCountryFromPath(countryPath)?.iso2) || ''
return {
amount: bankWithdrawMinUsd(countryIso2, exchangeRate > 0 ? String(exchangeRate) : null),

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.

MAJOR: Use the withdrawal quote when gating Bridge minimums

For USD→MXN at a display rate of 17, this sets the CTA floor to $3 (ceil(50/17)). If the Bridge rate request fails while /fx/rate serves its documented reference fallback, useGetExchangeRate returns 1 and both the withdrawal amount step and submit validation require $50. A $3 withdrawal therefore passes the widget gate but is rejected immediately afterward; differing live sell rates can cause the same mismatch near a boundary. Derive the gate from the same execution-side rate and failure state as withdrawal validation, or share an authoritative minimum between the two flows.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5a047b5. The converter, amount step and bank submit now share the provider-rate minimum gate. Bridge quote failures and failed background refreshes expose no usable rate; the 1:1 failure fallback is removed. A missing display estimate cannot discard a ready USD minimum.

Regressions cover display17/Bridge16.5 (minimum4), display17/Bridge17 (minimum3), unavailable/pending provider rates, missing display rates, saved CLABE accounts, exact boundaries and submit guards. Full740suites/9847tests, typecheck, formatting, production build and unchanged ESLint baseline pass.

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 6 screens moved

8 of 246 shots changed · 238 identical · baseline 6bec40c → head d7c3712

worst % screen widths
69.92% early-user 320, 430
58.57% avatar-picker 320, 430
0.45% guest-invite 320
0.34% home-card-info-failed 320
0.30% empty-history 320
0.23% empty-home 430
new screens (3)
  • rates-and-fees
  • rates-and-fees-below-minimum
  • rates-and-fees-unavailable

job summary · before/after/diff images — artifact

Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data.

@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

The prior Bridge-rate mismatch is fixed. A saved BRL withdrawal can still pass the wrong minimum, and the required screen-wordiness check fails on this head.

Findings

  • MAJOR · src/utils/exchangeRateWidget.utils.ts:189 · Apply the saved BRL withdrawal minimum to the CTA
    With a positive balance and USD→BRL at 5 BRL/USD, $0.20 passes this 1 BRL PIX gate. The CTA now opens /withdraw with amount=0.2 and saved accounts first; selecting a saved Brazil Manteca/PIX account opens /withdraw/manteca without method=pix, seeds $0.20, and reaches MantecaBankWithdrawFlow, which enforces the $1 MIN_MANTECA_WITHDRAW_AMOUNT. The enabled CTA therefore leads straight to a rejected amount. Gate against the actual saved-account route, or route saved PIX destinations through the PIX send flow whose minimum is used here.

  • MAJOR · src/i18n/app/messages/en.json:699 · Clear the required screen-wordiness regression
    The required screen-wordiness check succeeds on the supplied base but fails on this head: profile/exchange-rate/page.tsx scores 86 words with no baseline entry against the 60-word budget. This PR expands balanceNote and adds rateNote and the minimum message. Shorten the screen copy below the budget, or update the baseline for deliberate growth with a rationale, so the required CI gate passes.

Checked clean

  • Verified the exact head/base and clean detached worktree; read PR metadata but no issue or review comments.
  • P1 is fixed at this head: converter, amount step, and bank submit use the shared Bridge-rate minimum; failed and background-failed provider quotes expose no usable rate.
  • Traced the BRL CTA through saved Manteca account routing and the bank-withdraw amount guard; the new-destination PIX route uses a different, appropriate floor.
  • Reviewed the new rate-hook tests, widget behavior, saved-account handoff, localization and fixtures; unit, typecheck, format, ESLint and native-export checks passed.
  • The required screen-wordiness check passed on the base and failed at this head with the Rates screen at 86 words against a 60-word budget.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 5a047b52f70b · Context: repo, product, peanut-api-ts · Took 12m


if (destinationCurrency === 'BRL') {
const pixFloorUsd = exchangeRate > 0 ? MIN_PIX_AMOUNT_BRL / exchangeRate : Infinity
return pixFloorUsd >= MIN_MANTECA_QR_PAYMENT_AMOUNT

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.

MAJOR: Apply the saved BRL withdrawal minimum to the CTA

With a positive balance and USD→BRL at 5 BRL/USD, $0.20 passes this 1 BRL PIX gate. The CTA now opens /withdraw with amount=0.2 and saved accounts first; selecting a saved Brazil Manteca/PIX account opens /withdraw/manteca without method=pix, seeds $0.20, and reaches MantecaBankWithdrawFlow, which enforces the $1 MIN_MANTECA_WITHDRAW_AMOUNT. The enabled CTA therefore leads straight to a rejected amount. Gate against the actual saved-account route, or route saved PIX destinations through the PIX send flow whose minimum is used here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Saved Brazil destinations now use the PIX-key flow, including accounts stored with only BR/BRA country codes. New destinations preserve the amount too. The USD amount is validated and converted once using the PIX flow’s live Manteca sell rate, rounded down to BRL cents, and left editable. Existing PIX minimums and the final provider quote remain authoritative.

Regression tests exercise saved and new handoffs, Send origin, country-code-only records, malformed seeds, fixed QR exclusions, live-rate changes, and the real AmountInput’s rounding, editing and clearing. The bank withdrawal minimum is no longer applied to these PIX destinations.

Comment thread src/i18n/app/messages/en.json Outdated
"exchangeRate": {
"title": "Exchange rate & fees",
"balanceNote": "Your Peanut balance is shown in USD. Where local-currency bank transfers are supported, you can withdraw in that currency.",
"balanceNote": "Your Peanut balance is held in USD. These are the currencies a bank transfer can arrive in. In some countries, such as Sweden or Switzerland, transfers arrive in euros rather than the local currency.",

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.

MAJOR: Clear the required screen-wordiness regression

The required screen-wordiness check succeeds on the supplied base but fails on this head: profile/exchange-rate/page.tsx scores 86 words with no baseline entry against the 60-word budget. This PR expands balanceNote and adds rateNote and the minimum message. Shorten the screen copy below the budget, or update the baseline for deliberate growth with a rationale, so the required CI gate passes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Shortened the balance, estimate and minimum notes in the existing app locales. The screen now scores 58 against the 60-word budget. The budget and baseline were not raised. The copy retains the USD balance, euro payout exception, estimated rate, possible included conversion costs, fee review and minimum amount/currency.

@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

One major amount-handoff regression remains. The four previously raised findings are fixed at this head.

Findings

  • MAJOR · src/app/(mobile-ui)/profile/exchange-rate/page.tsx:116 · Preserve the Rates amount through Bridge bank selection
    For USD→MXN with 10 entered, this CTA appends amount=10 to /withdraw?currencyCode=MXN. Selecting a saved CLABE opens the new destination-currency amount step with destinationAmount='', so its MXN field is blank and AmountInput clears the raw USD amount; selecting a new Bridge destination routes to its form without forwarding amount at all. Thus the CTA's promised amount handoff works for PIX/Manteca but is lost for Bridge payouts. Convert the USD seed to an editable destination amount using the Bridge quote for a saved bank account, and carry the seed through the new-account path (or retain a USD-entry mode), with an end-to-end regression from the widget CTA.

Checked clean

  • P1 fixed: the widget, amount step, and submit read the shared Bridge minimum gate; failed provider rates block progression.
  • P2 fixed: saved Brazilian destinations route through PIX and seed qr-pay using its live Manteca rate.
  • P3 fixed: the screen-wordiness check succeeds at this head without increasing its budget.
  • P4 fixed: usable marketing quotes show the estimate note without a payout-route claim.
  • The current es-AR screenshot capture fails during a next/font build in unchanged Layout code; unit, format, lint, typecheck, screen-wordiness, and native-export checks succeed. The capture failure is not a code finding.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 6d07822b4ac8 · Context: repo, sibling_repo, ci · Took 14m

// A withdrawal starts from USD, so "You send" is the `?amount=` every
// /withdraw/* screen reads. Taken from the tap, not this page's URL
// copy, which the widget writes only after its debounce (TASK-22294).
if (redirectRoute.startsWith('/withdraw') && sourceAmount !== null) {

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.

MAJOR: Preserve the Rates amount through Bridge bank selection

For USD→MXN with 10 entered, this CTA appends amount=10 to /withdraw?currencyCode=MXN. Selecting a saved CLABE opens the new destination-currency amount step with destinationAmount='', so its MXN field is blank and AmountInput clears the raw USD amount; selecting a new Bridge destination routes to its form without forwarding amount at all. Thus the CTA's promised amount handoff works for PIX/Manteca but is lost for Bridge payouts. Convert the USD seed to an editable destination amount using the Bridge quote for a saved bank account, and carry the seed through the new-account path (or retain a USD-entry mode), with an end-to-end regression from the widget CTA.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 98bb6fb, included in current head 15237e6. A saved Bridge account now retains the USD entry mode and converts the current editable amount using its quote; new bank-account routes preserve the seed. Seven integration cases cover saved CLABE, delayed/failed quotes, rate refresh, minimum validation and new Mexico/euro destinations. Full local suite: 759 suites, 10,089 tests pass; build and typecheck pass.

The staged API schema and generated types exactly match origin/dev. The generic large-data hook flags this existing schema; it contains API definitions, not a customer export.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

One new-destination amount handoff remains broken for Brazil; the five earlier findings are fixed at this head.

Findings

  • MAJOR · src/features/withdraw/views/WithdrawMethodView.tsx:184 · Carry the Rates amount through Brazil’s two-rail picker
    For USD→BRL with amount=5, choosing a new Brazil destination does not take this single-rail branch: Brazil has two live withdrawal methods (PIX and To Bank), so soleLiveRailForCountry returns null. The preceding branch navigates to /withdraw/brazil without amount; choosing PIX there then opens /withdraw/manteca without the seed, and the QR-pay amount field starts blank. Preserve amount on the country-picker hop and forward it to the selected rail. The new test mocks Brazil as having one rail, so it misses the real path.

Checked clean

  • P1 fixed: widget, amount step, and bank submit use the Bridge-rate minimum and fail closed on unusable rates.
  • P2 fixed for saved Brazil destinations: they route to PIX, where the live quote and PIX minimum govern payment.
  • P3 fixed in the reviewed copy and unchanged 60-word budget; local wordiness script could not run because this detached worktree has no typescript dependency.
  • P4 fixed: a usable marketing quote shows the estimate/fee note independently of route eligibility.
  • P5 fixed for saved and new Bridge destinations: the USD seed reaches the amount step through the saved-account or bank-form path.
  • Reviewed converter state, quote failure paths, and current-head check-run conclusions.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 15237e65865c · Context: repo · Took 11m

// redirect in AddWithdrawCountriesList; the rail's own method= stays.
const extra = new URLSearchParams()
if (isBankFromSend && methodParam) extra.set('sendMethod', methodParam)
if (urlAmount) extra.set('amount', urlAmount)

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.

MAJOR: Carry the Rates amount through Brazil’s two-rail picker

For USD→BRL with amount=5, choosing a new Brazil destination does not take this single-rail branch: Brazil has two live withdrawal methods (PIX and To Bank), so soleLiveRailForCountry returns null. The preceding branch navigates to /withdraw/brazil without amount; choosing PIX there then opens /withdraw/manteca without the seed, and the QR-pay amount field starts blank. Preserve amount on the country-picker hop and forward it to the selected rail. The new test mocks Brazil as having one rail, so it misses the real path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 5a58df7. The country method picker now carries amount and send origin to the selected method, and Back preserves them. Tests cover the real Brazil catalogue and two live methods, including PIX and bank transfer. At the current catalogue Brazil has only PIX live (bank transfer is marked soon), so the specific two-live-method example was prospective; the amount-loss defect on the general method-picker path was real and is fixed.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

One bank-amount regression remains when returning from review after editing a Rates-seeded withdrawal in the destination currency.

Findings

  • MAJOR · src/features/withdraw/useWithdrawRootFlow.ts:498 · Preserve a bank-currency edit when returning from review
    With a Rates seed of USD 10 and a saved MXN account, switch the amount field to MXN and enter 200 at a quote of 17 MXN/USD. The field reports USD 11.76 into amount and MXN 200 into destinationAmount. After Continue and Back, this condition treats the derived amount as a USD seed again; the remounted field converts 11.76 USD to MXN 199.92 and reports that over the MXN 200 the user entered. The next review therefore quotes a changed payout. Track which denomination the user last edited (or clear the USD-seed marker on a bank-currency edit), and restore destinationAmount as the bank field on return.

Checked clean

  • P1: Bridge minimum uses the provider-rate minimum hook and blocks unavailable rates; no repeat finding.
  • P2: saved Brazil accounts route to PIX with the USD seed and live Manteca conversion; no repeat finding.
  • P3: shortened locale copy passes the unchanged screen-wordiness gate; no repeat finding.
  • P4: usable quoted marketing estimates show the rate note independently of route claims; no repeat finding.
  • P5: saved and new Bridge destinations retain the Rates USD seed through the first amount handoff; no repeat finding.
  • Current-head navigation and quote-refresh changes, QR/Manteca lock deadlines, and matching API expiresInMs contract were reviewed.
  • Unit, typecheck, and screen-wordiness checks succeeded. The es-419 capture job failed while next/font built an unchanged Layout file; its font-loader error did not implicate a changed line.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 1149bef20d6a · Context: repo, other-repo · Took 14m

refetchRate: bankRate.refetch,
destinationAmount,
// the field's first value and unit: the USD seed in USD, else the bank amount
initialAmount: bankUsdEntry ? urlAmount : destinationAmount,

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.

MAJOR: Preserve a bank-currency edit when returning from review

With a Rates seed of USD 10 and a saved MXN account, switch the amount field to MXN and enter 200 at a quote of 17 MXN/USD. The field reports USD 11.76 into amount and MXN 200 into destinationAmount. After Continue and Back, this condition treats the derived amount as a USD seed again; the remounted field converts 11.76 USD to MXN 199.92 and reports that over the MXN 200 the user entered. The next review therefore quotes a changed payout. Track which denomination the user last edited (or clear the USD-seed marker on a bank-currency edit), and restore destinationAmount as the bank field on return.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

One prior bank-amount return-path defect remains; the other six prior findings are fixed at this head.

Findings

  • MAJOR · src/features/withdraw/useWithdrawRootFlow.ts:498 · Restore the bank-currency edit after returning from review
    Start from Rates with ?amount=10, choose a saved EUR account, switch the amount field to EUR, and enter 20 EUR. The input stores destinationAmount=20 but also rewrites the retained USD amount with its conversion. After Continue and browser Back, bankUsdEntry is still true, so this line initializes the remounted field from that USD value and selects USD instead of the 20 EUR the user edited. A refreshed quote can then re-report a different EUR target into destinationAmount. Persist the chosen denomination, or clear the USD seed when the user switches to the bank currency and restore the saved bank amount on return.

Checked clean

  • P1 fixed: the widget, amount step, and bank review use the provider-rate minimum gate and block unavailable quotes.
  • P2 fixed: saved Brazil destinations enter the PIX-key flow with the amount; the bank withdrawal minimum is not applied there.
  • P3 fixed: shortened copy passes the screen-wordiness gate at this head.
  • P4 fixed: usable marketing quotes show the estimate-and-fee note independently of route-specific delivery claims.
  • P5 fixed: saved Bridge accounts and new bank forms carry the Rates USD seed to the amount step.
  • P6 fixed: the two-rail country picker carries the amount and Send origin to the selected rail, including on return from the bank form.
  • Bank-conversion reviews omit the zero-fee row; USD ACH retains it, consistent with the product pricing source.
  • Unit, typecheck, formatting, screen, native-export and press-contract checks succeeded at the exact head; git diff --check found no whitespace errors.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: ecfcdffdc72d · Context: repo, product · Took 9m

refetchRate: bankRate.refetch,
destinationAmount,
// the field's first value and unit: the USD seed in USD, else the bank amount
initialAmount: bankUsdEntry ? urlAmount : destinationAmount,

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.

MAJOR: Restore the bank-currency edit after returning from review

Start from Rates with ?amount=10, choose a saved EUR account, switch the amount field to EUR, and enter 20 EUR. The input stores destinationAmount=20 but also rewrites the retained USD amount with its conversion. After Continue and browser Back, bankUsdEntry is still true, so this line initializes the remounted field from that USD value and selects USD instead of the 20 EUR the user edited. A refreshed quote can then re-report a different EUR target into destinationAmount. Persist the chosen denomination, or clear the USD seed when the user switches to the bank currency and restore the saved bank amount on return.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 10141b7, included in ec178ef. The existing amount URL seed now identifies USD entry; changing to bank currency clears it. Returning from confirmation restores the selected currency and destination amount. Regression tests use the real withdrawal provider and AmountInput: EUR20 remains EUR20 after Back and a 0.9→0.95 rate update; USD12 remains USD12 after Back and a rate change. A currency round trip also retains the final choice.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

No findings at this head; all eight prior findings are fixed.

Checked clean

  • P1 fixed: Bridge minimums use the provider-rate gate across the widget, amount step, and bank review; unavailable rates block.
  • P2 fixed: saved Brazil accounts use the PIX-key flow with the USD seed and PIX validation.
  • P3 fixed: shortened copy passes the screen-wordiness check at this head.
  • P4 fixed: usable marketing quotes show the estimate-and-fee note without route-specific claims.
  • P5 fixed: saved Bridge accounts and new bank forms carry the Rates USD seed to the amount step.
  • P6 fixed: the two-rail picker forwards amount and Send origin to the selected rail, and preserves the amount on return from the bank form.
  • P7 and P8 fixed: switching to bank currency clears the USD seed; remount restores the bank amount, and rate refresh changes only the derived USD amount. USD choice and currency round trips are also covered.
  • Conversion reviews omit the zero-fee row; USD ACH retains it, consistent with the product pricing source.
  • The press-contract check fails in next/font while building, with the same error on the exact base SHA; this PR changes no font, layout, or dependency files. Unit was still in progress when checked; native-export, typecheck, format, and screen-wordiness passed.
  • git diff --check found no whitespace errors.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: ec178ef99815 · Context: repo, product · Took 8m

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

No actionable findings at this head.

Checked clean

  • Exact head and base match the supplied pull request metadata; reviewed the new USD bank-review handoff and surrounding submit path.
  • P1-P6 no longer reproduce in the current minimum, PIX, copy, quote-note, and routing paths; P7-P8 no longer reproduce after the currency-specific URL handoff and Back behavior.
  • Focused tests could not run locally because Jest is not installed in the detached worktree; checked CI has no failed jobs so far, with several jobs still running.

Security review: did not run — openrouter-http-402. This review is one reviewer short.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 445bb0773dfa · Context: repo · Took 7m

Dev's rate-matched quote and estimated payout stay the rule: bank payout minimums compare in the bank's currency, the USD unit toggle lives in amountCurrency, and USD payouts show the speed fee. The fee branch keeps its Rates & fees USD seed (amount alone opens the field in USD), same-flush unit handling, the unchanged-URL write guard, no zero-fee claim on conversions, and the widget minimum, now the bank-currency minimum in USD rounded up to the cent.

--no-verify: the hook's size gate refuses src/types/api.openapi.json (1.1 MiB), the tracked API contract, identical to origin/dev and not data. Every other hook check (signing, data files, secret scan, prettier, eslint on staged files) was run on this index with only that file exempted and passed.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

One previously fixed Bridge minimum mismatch recurs after the dev merge.

Findings

  • MAJOR · src/features/withdraw/useBankWithdrawMinimum.ts:33 · Gate the Rates minimum with the withdrawal quote
    The Rates CTA still computes its MXN/GBP/COP USD floor from useGetExchangeRate, whose successful result is cached for five minutes, while the amount step and bank review now validate against a separately refreshed /bridge/offramp/quote. For a 50 MXN minimum, a cached rate of 17 advertises and permits $2.95; if the withdrawal quote has moved to 16.5, that amount pays only 48.675 MXN and Continue is disabled. A direct exchange-rate success can also admit the CTA while the freshness-checked offramp quote is unavailable. Reuse the same withdrawal-quote rate/availability for the CTA minimum, or make the CTA floor explicitly provisional and revalidate before promising that amount will proceed.

Checked clean

  • Exact PR head and dev base verified; compared their merge base and checked the previous clean review against this head.
  • P2-P8 no longer reproduce: saved Brazil routes through PIX, wordiness and quote-note gates hold, and the Rates amount and bank-currency selection survive the method and review handoffs.
  • Reviewed the Bridge exchange-rate and offramp-quote API contracts: the widget uses the direct rate while the withdrawal uses an independently refreshed, freshness-checked quote.
  • Current-head typecheck, lint, format and relevant guards passed; unit and preview-related CI checks were still in progress at review time.

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: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 53b9c9e0122f · Context: repo, sibling · Took 10m

// country has the $1 floor
const bankMinimum = getMinimumAmount(countryIso2)
const needsRate = bankMinimum > BRIDGE_OFFRAMP_MIN_USD
const { exchangeRate, isError } = useGetExchangeRate({

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.

MAJOR: Gate the Rates minimum with the withdrawal quote

The Rates CTA still computes its MXN/GBP/COP USD floor from useGetExchangeRate, whose successful result is cached for five minutes, while the amount step and bank review now validate against a separately refreshed /bridge/offramp/quote. For a 50 MXN minimum, a cached rate of 17 advertises and permits $2.95; if the withdrawal quote has moved to 16.5, that amount pays only 48.675 MXN and Continue is disabled. A direct exchange-rate success can also admit the CTA while the freshness-checked offramp quote is unavailable. Reuse the same withdrawal-quote rate/availability for the CTA minimum, or make the CTA floor explicitly provisional and revalidate before promising that amount will proceed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9dafa3a. Rates now uses the same currency-only offramp quote as the withdrawal. A cached display rate cannot supply the minimum or enable the CTA when that quote fails. Regression cases cover cached 17 versus fresh 16.5 (50 MXN requires $3.04), failed quote with cached display success, and refresh recovery. All 10,756 unit tests, typecheck and production build pass.

The Rates & fees minimum for GB, MX and CO converted with the Bridge display rate, cached for five minutes, while the withdrawal converts at the 30-second offramp quote. A cached 17 allowed $2.95 for 50 MXN when the current 16.5 needs $3.04, and a working display rate let the CTA through while the quote was unavailable (Chip review 5342603409).

useBankWithdrawMinimum now reads useBridgeOfframpQuote for the country's bank currency. A failed quote, or a failed refresh of a retained one, is unavailable; an unusable rate is unavailable; no quote yet is pending. The $1-floor countries make no request, and the USD cent ceiling is unchanged.

@chip-peanut-bot chip-peanut-bot 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.

Chip review — no blocking findings — this is not an approval

No findings at the reviewed head.

Checked clean

  • P1 and P9: Rates bank minimum now uses the currency-only Bridge offramp quote; cached display rates cannot set the floor, and failed or unusable quotes block the CTA.
  • P2-P6: Brazil PIX handoff, screen copy, marketing estimate note, saved-account amount, and country-picker amount remain corrected at this head.
  • P7-P8: AmountInput and withdrawal flow preserve bank-currency and USD edits across review Back and rate changes.
  • Current-head completed CI checks have no failures; screenshot and press-contract checks were still running at review time. Local wordiness execution was unavailable because detached worktree dependencies are not installed.

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: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 9dafa3a2f72f · Context: repo, product, lexicon · Took 9m

Brings the recipient identity fix (#3527) for semantic requests; no overlap with the fee changes.

This branch was successfully deployed

1 active deployment
Preview — d7c3712a Deployed Sep 28, 2026 by vercel[bot]
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.

1 participant