Skip to content

release: dev → main (Red ATM kill · avalanche cleanup · deposit rails · pix/p2p fixes)#2245

Merged
jjramirezn merged 80 commits into
mainfrom
dev
Jun 24, 2026
Merged

release: dev → main (Red ATM kill · avalanche cleanup · deposit rails · pix/p2p fixes)#2245
jjramirezn merged 80 commits into
mainfrom
dev

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Production release — promotes devmain (→ peanut.me).

Included (merged to dev)

⚠️ HARD GATE — cross-repo ordering

#2253 reads activation_celebrated_at from the API. This FE release MUST ship after peanut-api-ts #1035 is deployed to prod AND prisma migrate deploy has run there. If the FE ships first: GET /users/me 500s and the modal re-shows to already-unlocked users.

Action items / deploy checklist

Risk

Content/SEO (Red ATM, avalanche, FP/SPEI taxonomy) + small FE fixes (pix phone key, p2p display) + the unlock-modal milestone (#2253). No FE schema change, but #2253 has the hard API-migration ordering above.

Deploy

Merge → Vercel builds prod. Gate on #1035's prod migration first. Monitor prod deploy + smoke after merge.

0xkkonrad and others added 16 commits June 16, 2026 15:58
Bumps src/content f204f50..5cbbe4f (peanut-content main). Headline change:
scrub the Argentina cardless cash-withdrawal ("Red ATM") marketing across
all 5 locales — it was promoted as a live feature but is being pulled. The
RedATM withdrawal flow in the app is untouched; this is marketing copy only.

Competitor/pain-point ATM copy is intentionally preserved (foreign-ATM fee
comparisons, Western Union, Revolut/BLIK).

Content-only: zero code paths touched. Supersedes the handrolled #2226 and
the stale auto/update-content PRs, which were based off main and showed
phantom code diffs against dev.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rogress bar)

Two cosmetic-but-confusing display bugs in the bill-split / request-pot UI,
both reported by Konrad:

1. Slider "snap to 50% → 49.98%": the slider snaps the thumb to a clean
   percentage on drag, but AmountInput re-derives the thumb position from a
   cent-floored amount ($33.37 pot → 50% = $16.685 → floored $16.68 → 49.98%),
   and the Slider's sync effect then clobbered the snapped value. The amount is
   correct (you can't pay half a cent); only the label drifted. The sync effect
   now keeps the thumb on a snap point through sub-cent drift.

2. Request-pot progress bar blank for pots that collected >= $1,000: the receipt
   passed the comma-grouped display string to Number() (Number("1,234.56") ===
   NaN), blanking the bar. Use the raw numeric totalAmountCollected, matching
   how `goal` already uses transaction.amount.

Display-only; no amounts or money paths change. Complements api-ts #1027 (the
send-link $0 phantom) as part of the same P2P display-correctness sweep.
Card payments couldn't be split. The "Split this bill" CTA + /request prefill +
auto-create already ship for QR pays (gated on isQRPayment); this extends the
gate to card spends so a Peanut-card purchase can seed a bill-split request
(amount = the spend's USD value, comment = "Bill split for {merchant}").

- isCardSpend predicate: CARD_SPEND_AUTH/CLEAR only — refunds + auth reversals
  excluded (you didn't pay those).
- Threaded through useReceiptViewModel; CTA gate is now (isQRPayment || isCardSpend)
  and also excludes failed/declined spends.
- Omit the merchant param when userName is the "Card payment" fallback so the
  comment isn't "Bill split for Card payment".

FE-only — reuses the existing /request creation + POST /requests.
…eRabbit)

A merchant name with reserved characters (e.g. "Tigers & Lions") would break the
`/request?amount=…&merchant=…` query string and corrupt the request prefill.
encodeURIComponent the merchant value. Addresses CodeRabbit's inline finding on
#2235.
fix(p2p): request-pot display bugs — slider 49.98% snap + $1k+ progress bar NaN
normalizePixPhoneNumber returned the raw input untouched when the cleaned value
already started with '+', so '+55-11-99999-9999' kept its separators and stayed
non-canonical — conflicting with the helper's contract and risking PIX-key
mismatches. Always return the separator-free +55 form. From release-PR #2236
CodeRabbit triage; adds the separator-with-+ regression cases the existing
+-prefix tests never exercised.
…zation

fix(pix): canonicalize +55 phone keys that already carry a + prefix
…uilder

The CTA's onClick inlined the /request URL build, including the two bug-prone
bits — the "Card payment" merchant fallback and the URL-encode CodeRabbit caught
on #2235. Extract to buildSplitBillRequestUrl and lock it with unit tests
(encode reserved chars, omit fallback, omit empty merchant). Pure refactor +
tests; no behavior change.
Fast-forward the Red ATM scrub bump from the intermediate 5cbbe4f to
the current peanut-content main tip 358b6a6, so dev matches mono-main
content (and converges with the main-targeted prod bump #2242 — keeps
the next dev->main release from re-reverting the Red ATM removal).

Content-only, no code paths touched.
Symmetric with the merchant encode — the param type allows string, so encode
amount defensively. No behavior change for the numeric amounts callers pass
(encodeURIComponent("12.5") === "12.5"); existing assertions unchanged.
test(split-bill): lock the Split-this-bill CTA URL builder
Mirrors the main-targeted companions (#2242). The 358b6a6 content bump
removes the avalanche deposit + withdraw marketing pages (avalanche is
advertised in SEO + DEPOSIT_RAILS but never wired into rhino.consts —
a false claim). Sync the code so the content gate passes:

- DEPOSIT_RAILS / verify-content RAIL_SLUGS: drop avalanche so the SEO
  route + sitemap stop emitting /deposit/via-avalanche
- redirects.json: 301 old via-avalanche + withdraw/avalanche -> /supported-networks
- .verify-content-baseline: 745 -> 740

ETH untouched (we support it). Deeper deposit-flow avalanche/scroll
cleanup stays with #2216 (overlaps these files -> trivial rebase).
The rail list lived in two places — DEPOSIT_RAILS (exchanges.ts) and a
hand-copied RAIL_SLUGS Set in scripts/verify-content.ts, guarded only by
a 'keep in sync' comment. Removing avalanche meant editing both; the
lists had already drifted before.

Extract to src/data/seo/deposit-rails.ts — an import-free module both
the app (routes + sitemap via exchanges.ts) and the standalone content
gate (verify-content.ts) import. Add/remove a rail in one place now.

No behaviour change: same 9 rails, same classification. (Registering
faster-payments + spei as rails is a follow-up — it's a content-input
regeneration job, not a code add: the from-→via- flip breaks generated
RelatedLinks that must be fixed at the mono input layer and re-mirrored.)
Missing `name` falls back to title-casing; missing `recommended_network`
falls back to 'arbitrum' (pickRecommendedNetwork), not title-casing.
Per CodeRabbit review.
…-atm

chore(content): bump src/content — remove Red ATM from marketing
Faster Payments (UK/GBP) and SPEI (MX/MXN) are live deposit rails but were
served as /deposit/from-{slug} (exchange framing) — inconsistent with
SEPA/ACH/wire. Register both in DEPOSIT_RAILS so the route + sitemap emit
/deposit/via-{slug}, bump src/content to the matching content fix (the 14
RelatedLinks now point to via-), and 301 the old from- URLs to via- for any
indexed/shared links.

Content side: peanut-content 358b6a6 -> 5eb8453 (mono@8a061d5) — flips the
links + adds FP/SPEI to the fiat-rail list in intent-taxonomy.md so
regeneration keeps them as rails. Verified: content gate green, typecheck +
src/data tests pass.
@vercel

vercel Bot commented Jun 18, 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 Jun 24, 2026 10:34pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR bundles six independent functional changes and a product documentation update: extends "Split this bill" to card spend transactions via a new isCardSpend predicate and buildSplitBillRequestUrl helper; fixes Slider snap-stick behavior on sub-cent drift; extracts DEPOSIT_RAILS/RAIL_SLUGS from exchanges.ts into a standalone deposit-rails.ts module with Avalanche redirect rules; removes Avalanche from merchant FAQ and Scroll from supported EVM chains while making EVM overflow dynamic; canonicalizes normalizePixPhoneNumber to always strip separators; and updates public LLM training files with expanded product description.

Changes

Split Bill for Card Spend

Layer / File(s) Summary
isCardSpend predicate and buildSplitBillRequestUrl utility
src/components/TransactionDetails/transaction-predicates.ts, src/components/TransactionDetails/splitBill.utils.ts, src/components/TransactionDetails/__tests__/splitBill.utils.test.ts, src/components/TransactionDetails/__tests__/transaction-predicates.test.ts
Adds isCardSpend matching CARD_SPEND_AUTH/CARD_SPEND_CLEAR kinds, and buildSplitBillRequestUrl encoding amount and conditionally appending an encoded merchant parameter; tests cover both.
ReceiptViewModel isCardSpend field and receipt CTA wiring
src/components/TransactionDetails/useReceiptViewModel.ts, src/components/TransactionDetails/TransactionDetailsReceipt.tsx
Extends ReceiptViewModel with isCardSpend computed via useMemo; receipt shows split bill CTA for isQRPayment || isCardSpend and navigates via buildSplitBillRequestUrl; fixes progress bar to use numeric totalAmountCollected.

Slider Snap-Stick Fix

Layer / File(s) Summary
Snap-point-aware controlled sync and tests
src/components/Global/Slider/index.tsx, src/components/Global/Slider/__tests__/index.test.tsx
Replaces simple inequality sync with snap-point tolerance check; adds tests asserting label stability at 49.98% drift and correct update on genuine moves to 70%.

SEO Deposit Rails Refactor and Avalanche Redirects

Layer / File(s) Summary
deposit-rails.ts extraction and re-export wiring
src/data/seo/deposit-rails.ts, src/data/seo/exchanges.ts, src/data/seo/index.ts
Creates new module exporting DEPOSIT_RAILS map and RAIL_SLUGS set; removes hardcoded definitions from exchanges.ts; updates index.ts re-exports to source DEPOSIT_RAILS from the new module.
verify-content import, Avalanche redirects, and content baseline
scripts/verify-content.ts, redirects.json, src/content, .verify-content-baseline
Imports RAIL_SLUGS from deposit-rails in verify-content script; adds permanent redirects for deposit/via-avalanche and withdraw/avalanche to supported-networks; updates content submodule pointer and regenerates baseline.

Network Support Updates

Layer / File(s) Summary
Avalanche FAQ removal, Scroll removal, and dynamic EVM rendering
src/app/m/[slug]/merchants.ts, src/constants/rhino.consts.ts, src/components/Global/TokenAndNetworkConfirmationModal/index.tsx
Removes Avalanche from BA Digital Nomads FAQ; removes SCROLL from SUPPORTED_EVM_CHAINS with documentation; updates TokenAndNetworkConfirmationModal to dynamically compute visible EVM chains and overflow count instead of hardcoded +4 EVM rendering.

PIX Phone Normalization Fix

Layer / File(s) Summary
normalizePixPhoneNumber canonicalization and tests
src/utils/withdraw.utils.ts, src/utils/__tests__/withdraw.utils.test.ts
Always returns separator-free +55 form for valid phone inputs regardless of whether input already had a + prefix; extends tests with dash/space-separated prefixed inputs.

Product Documentation Update for LLM Training

Layer / File(s) Summary
LLM training files product description and links
public/llms.txt, public/llms-full.txt
Updates llms.txt with comprehensive structured sections (PIX/Argentina payments, Peanut Card, send/receive, deposits, withdrawals, operational basics, coverage, target audience); updates llms-full.txt with full product description including headline differentiators, expanded features, security/verification, fees, coverage, and company links.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • peanutprotocol/peanut-ui#2241: Both PRs introduce and test buildSplitBillRequestUrl in splitBill.utils.ts and update TransactionDetailsReceipt.tsx to use that helper for the "Split this bill" CTA URL.
  • peanutprotocol/peanut-ui#2240: Both PRs modify normalizePixPhoneNumber in src/utils/withdraw.utils.ts (and its tests) to canonicalize inputs with an existing + prefix to separator-free +55... form.
  • peanutprotocol/peanut-ui#2112: Both PRs update the deposit-content validation flow to derive/consume the same DEPOSIT_RAILS/RAIL_SLUGS in scripts/verify-content.ts, backed by src/data/seo/deposit-rails.ts.

Suggested reviewers

  • jjramirezn
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: a production release that kills Red ATM, cleans up Avalanche support, consolidates deposit rails, and fixes PIX/P2P issues.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing production release consolidations including Red ATM removal, Avalanche support elimination, deposit-rails de-duplication, and associated bug fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jun 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Global/Slider/__tests__/index.test.tsx`:
- Around line 9-15: The ResizeObserverStub is being assigned to
globalThis.ResizeObserver at the module level without any cleanup, causing the
mock to persist across all tests and potentially affecting other test files.
Move the ResizeObserverStub class definition outside of any setup/teardown, but
wrap the assignment of ResizeObserverStub to globalThis.ResizeObserver in a
beforeEach hook that sets it up, and add an afterEach hook that restores the
original ResizeObserver value (save it before the beforeEach modifies it). This
ensures the stub is only active during each test execution and the original
value is restored afterward, preventing test pollution.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 385e623e-3e7f-481a-8267-ea46b46b22ba

📥 Commits

Reviewing files that changed from the base of the PR and between 85f178d and cdab7e5.

📒 Files selected for processing (17)
  • .verify-content-baseline
  • redirects.json
  • scripts/verify-content.ts
  • src/components/Global/Slider/__tests__/index.test.tsx
  • src/components/Global/Slider/index.tsx
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx
  • src/components/TransactionDetails/__tests__/splitBill.utils.test.ts
  • src/components/TransactionDetails/__tests__/transaction-predicates.test.ts
  • src/components/TransactionDetails/splitBill.utils.ts
  • src/components/TransactionDetails/transaction-predicates.ts
  • src/components/TransactionDetails/useReceiptViewModel.ts
  • src/content
  • src/data/seo/deposit-rails.ts
  • src/data/seo/exchanges.ts
  • src/data/seo/index.ts
  • src/utils/__tests__/withdraw.utils.test.ts
  • src/utils/withdraw.utils.ts

Comment thread src/components/Global/Slider/__tests__/index.test.tsx
Hugo0 added 2 commits June 18, 2026 15:46
llms.txt was too short and undersold the product. Rewrite (GA-informed:
Argentina + Brazil dominate; mercadopago + pix are the top public pages) to
lead with the differentiators: pay PIX in Brazil with no CPF, pay MercadoPago
in Argentina with no DNI, the Peanut Card, then send/receive/deposit/withdraw,
how-it-works, and where-it-works (live vs roadmap).

Also fixes stale facts in llms-full.txt: smart accounts are on Arbitrum (not
'Base/Ethereum L2'); the card is virtual Visa only (not physical). No backend
provider names; 'ATM cash withdrawal' omitted (Red ATM was just killed from
marketing).
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5761.09 → 5745.45 (-15.64)
Findings: +19 net (+249 new, -230 resolved)

🆕 New findings (249)

  • critical complexity — src/app/(mobile-ui)/qr-pay/page.tsx — CC 296, MI 53.26, SLOC 955
  • critical complexity — src/components/TransactionDetails/TransactionDetailsReceipt.tsx — CC 165, MI 52.8, SLOC 352
  • critical complexity — src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 152, MI 52.4, SLOC 520
  • critical complexity — src/components/AddWithdraw/DynamicBankAccountForm.tsx — CC 140, MI 53.91, SLOC 389
  • critical complexity — src/app/(mobile-ui)/withdraw/page.tsx — CC 122, MI 53.88, SLOC 336
  • critical complexity — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 119, MI 56.62, SLOC 336
  • critical method-complexity — src/components/TransactionDetails/TransactionDetailsReceipt.tsx:72 — CC 113 SLOC 170
  • critical complexity — src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 110, MI 47.15, SLOC 417
  • critical complexity — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 98, MI 54.79, SLOC 327
  • critical complexity — src/components/TransactionDetails/useReceiptViewModel.ts — CC 94, MI 61.68, SLOC 177
  • critical complexity — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 93, MI 57.82, SLOC 326
  • critical complexity — src/utils/bridge.utils.ts — CC 80, MI 58.99, SLOC 187
  • critical complexity — src/hooks/useSumsubKycFlow.ts — CC 79, MI 52.54, SLOC 349
  • critical method-complexity — src/app/(mobile-ui)/qr-pay/page.tsx:97 — QRPayPage CC 74 SLOC 328
  • critical complexity — src/components/Global/AmountInput/index.tsx — CC 69, MI 59.97, SLOC 176
  • critical complexity — src/app/(mobile-ui)/home/page.tsx — CC 66, MI 63.05, SLOC 200
  • critical complexity — src/hooks/useMultiPhaseKycFlow.ts — CC 66, MI 57.72, SLOC 324
  • critical complexity — src/utils/withdraw.utils.ts — CC 63, MI 53.38, SLOC 229
  • critical complexity — src/utils/capability-gate.ts — CC 62, MI 64.19, SLOC 167
  • critical complexity — src/app/receipt/[entryId]/page.tsx — CC 61, MI 48.62, SLOC 148

…and 229 more.

✅ Resolved (230)

  • src/app/(mobile-ui)/qr-pay/page.tsx — CC 295, MI 53.27, SLOC 955
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx — CC 166, MI 52.87, SLOC 349
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — CC 151, MI 52.36, SLOC 520
  • src/components/AddWithdraw/DynamicBankAccountForm.tsx — CC 134, MI 54.23, SLOC 380
  • src/app/(mobile-ui)/withdraw/page.tsx — CC 119, MI 53.93, SLOC 336
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 118, MI 56.72, SLOC 333
  • src/components/TransactionDetails/TransactionDetailsReceipt.tsx:70 — CC 114 SLOC 169
  • src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 98, MI 47.64, SLOC 407
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 97, MI 52.72, SLOC 331
  • src/components/TransactionDetails/useReceiptViewModel.ts — CC 93, MI 61.89, SLOC 174
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 89, MI 57.5, SLOC 313
  • src/app/(mobile-ui)/qr-pay/page.tsx:92 — QRPayPage CC 74 SLOC 329
  • src/hooks/useSumsubKycFlow.ts — CC 74, MI 52.96, SLOC 325
  • src/utils/bridge.utils.ts — CC 68, MI 60.1, SLOC 168
  • src/components/Global/AmountInput/index.tsx — CC 67, MI 59.66, SLOC 172
  • src/hooks/useMultiPhaseKycFlow.ts — CC 66, MI 57.79, SLOC 322
  • src/app/(mobile-ui)/home/page.tsx — CC 65, MI 63.02, SLOC 200
  • src/utils/withdraw.utils.ts — CC 63, MI 53.39, SLOC 229
  • src/app/receipt/[entryId]/page.tsx — CC 60, MI 48.49, SLOC 147
  • src/components/Request/direct-request/views/Initial.direct.request.view.tsx — CC 57, MI 55.57, SLOC 167

…and 210 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/utils/claim-metadata.utils.ts 0.0 7.6 +7.6
src/hooks/useAdvisoryPreempt.ts 0.0 7.0 +7.0
src/utils/og.utils.ts 0.0 6.2 +6.2
src/components/Kyc/AdvisoryPreemptModal.tsx 0.0 5.5 +5.5
src/data/seo/deposit-rails.ts 0.0 4.9 +4.9
src/components/TransactionDetails/splitBill.utils.ts 0.0 4.7 +4.7
src/app/(mobile-ui)/claim/page.tsx 0.4 4.3 +3.9
src/utils/balance.utils.ts 5.7 7.0 +1.2
src/components/Send/link/views/Initial.link.send.view.tsx 10.6 11.7 +1.1
src/components/Claim/Link/views/BankFlowManager.view.tsx 17.4 18.1 +0.7
src/utils/capability-gate.ts 6.9 7.5 +0.7
src/components/TransactionDetails/transaction-predicates.ts 5.5 6.1 +0.6
src/utils/bridge.utils.ts 9.3 9.9 +0.6
src/components/AddWithdraw/DynamicBankAccountForm.tsx 14.6 15.2 +0.6
src/app/(mobile-ui)/withdraw/page.tsx 15.0 15.5 +0.5
src/app/actions/sumsub.ts 10.1 10.7 +0.5

…allowlist

The card moved from a CARD_PIONEER allowlist (US+18 LATAM+10 African) to a
denylist: eligible everywhere except the issuer's ~17 prohibited-issuance
countries (China, India, Russia, Turkey, Vietnam, Iran, Israel, ...). Source of
truth: peanut-api-ts/src/card/geo-eligibility.ts. The earlier llms copy came
from product/card.md which is stale post-rehaul (flagged for update).
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1556 ran, 0 failed, 0 skipped, 21.7s

📊 Coverage (unit)

metric %
statements 53.4%
branches 36.3%
functions 40.9%
lines 53.3%
⏱ 10 slowest test cases
time test
0.6s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every stamp stays within canvas at any count
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 invalid ETH address (missing 0x prefix)
0.1s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Perk claimed shows shake class + go home button
0.1s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in createOnrampForGuest
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ETH address with surrounding spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (too short)
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`.

feat(seo): register Faster Payments + SPEI as deposit rails (via-, not from-)
Hugo0 and others added 2 commits June 18, 2026 18:32
…ale Avalanche FAQ

Carries forward the still-needed deposit-flow half of #2216 (the avalanche
*marketing* half already shipped via #2232):
- rhino.consts: remove SCROLL from SUPPORTED_EVM_CHAINS — Rhino's live config
  (getSupportedConfigs) no longer returns an SDA for it (audit 2026-06-11), and
  a deposit on an unsupported chain is silently lost. The chainId map keeps
  534352->SCROLL so any stray deposit stays identifiable for recovery.
- TokenAndNetworkConfirmationModal: compute the '+N EVM' overflow from the list
  length (was hardcoded '+4') so it stays correct, and drop the now-unused
  RHINO_SUPPORTED_CHAINS import.
- merchants.ts: drop Avalanche from the Argentina funding FAQ (false claim).

Supersedes #2216.
…removal

fix(deposit): stop offering Scroll deposits (silently lost) + drop stale Avalanche FAQ
Hugo0 and others added 11 commits June 23, 2026 15:27
…display formatting

Two DRY follow-ups to the gate fix, both single-sourced in the wallet hook:

1. Messaging — useWallet.spendBlockReason(amount) classifies a blocked spend as
   'settling' (covered by the displayed balance but part is still mid-rebalance:
   <= display, > available-now) vs 'insufficient' (exceeds the displayed total).
   The five legacy gates (send-link, qr-pay, withdraw page/bank/manteca) map that
   to one shared SPEND_BLOCK_MESSAGE instead of four bespoke strings. The
   'settling' copy is deliberately generic ("Your balance is updating...") so it
   never exposes the card-collateral mechanic, and it only shows in the rare
   ~10-45s in-transit window: in the 99% case display == available so only the
   normal "insufficient" path is reachable.

2. Display formatting — send-link, withdraw page/manteca and both request views
   render the hook's formattedSpendableBalance instead of re-deriving locally
   (printableUsdc vs formatAmount diverged: commas vs none). One formatter,
   consistent across screens; orphaned imports removed.

Displayed balance is unchanged: always the full spendable total (smart +
collateral, incl. in-transit). Only the spend gate is strict, and only briefly.
hasSufficientSpendableBalance stays for the features/payments flows; both share a
parseUsdToBaseUnits helper. Tests revived/extended; full unit suite green.
CodeRabbit review on the messaging/format commit:
- Send-link: the Retry button isn't disabled on a balance error (unlike the
  other flows, which disable their submit), so handleOnNext could reach
  createLink with a blocked amount and fail at execution. Re-check
  spendBlockReason at the top of handleOnNext so the block holds at submit too.
- withdraw/manteca: use an explicit `balance !== undefined` guard for the
  displayed balance so a real $0 balance shows "$0.00" instead of being hidden,
  matching the other consolidated flows (the old truthy guard dropped 0n).
2026-06-02 21:24 UTC, PEANUT-API-5P/5M/5N. A user with a UK GBP bank
account tried to offramp USDC → EUR via SEPA. Bridge rejected with
"country is not supported for SEPA" — SEPA only credits EUR-denominated
Eurozone accounts, not GBP/UK.

Root cause was in BankFlowManager.handleCreateOfframpAndClaim:
    const destination = getOfframpCurrencyConfig(
        account.country ?? selectedCountry!.id
    )

When `account.country` is missing, the picker falls back to
`selectedCountry`. If `selectedCountry` doesn't match the saved-account's
actual type (e.g. user picks a random country, or country resolution is
buggy), the helper's "everything unknown → EUR/SEPA" default fires —
even for a clearly GBP/UK account.

The account's own `type` already carries the right answer for every
Bridge destination we support (us/gb/clabe/iban). Adding a tiny helper
`getOfframpConfigFromAccount` that derives currency+rail directly from
account.type closes this class of bug.

- `gb`  → gbp/faster_payments
- `us`  → usd/ach
- `clabe` → mxn/spei
- `iban` → eur/sepa
- `manteca` → throws (must use the Manteca offramp path, not Bridge)
- type missing → falls back to country-based picking (prior behavior)

The helper also accepts BE Prisma-shape suffixes (`BANK_IBAN`,
`BANK_ACCOUNT_GB`, etc.) so it doesn't matter which side of the API the
account row came from.

7 new unit tests in bridge.utils.test.ts cover:
- The GB regression (was EUR/SEPA, now GBP/faster_payments)
- All 4 supported account types map to the right rail
- BE Prisma-suffix shapes (BANK_IBAN, BANK_ACCOUNT_GB)
- Manteca throws to surface a wrong-path call early
- Missing-type fallback to country picking still works

All 71 bridge.utils tests passing.

Companion PR: peanut-api-ts #964 — surfaces Bridge's actual error
message in Sentry + stops Discord-paging on user-input 4xx errors.
Even after this FE fix, other user-input mistakes (e.g. a Bridge
customer status change mid-flow) shouldn't page on-call.
The bank-details form rejected any IBAN whose country didn't equal the
country selected on the previous screen ("IBAN does not match the selected
country"). SEPA routes by IBAN, so the dropdown is cosmetic for a EUR payout —
this gate blocked two legit cases: a German IBAN with Spain selected, and a UK
user withdrawing EUR to a GB IBAN (Ibrahima / bobbyfresco).

Gate on actual support instead: validateBankAccount() hits the BE
allowedCountries check (SEPA/US/CA), so structurally-valid-but-unsupported
IBANs are still rejected — just with an honest "not supported" message rather
than a false country-mismatch. Drops the now-orphaned getCountryFromIban import.
…ropdown

Follow-on to dropping the IBAN-country gate: the add-bank-account payload set
countryCode/countryName/address.country from the country picked on the previous
screen. With the gate gone, a German IBAN entered under "Spain" would reach
Bridge with countryCode=ESP and 400. SEPA routes by IBAN, so derive all three
country fields from the IBAN itself for IBAN accounts — keeping the payload
internally consistent exactly as the old equality gate guaranteed, just sourced
from the IBAN. Unblocks the GB-IBAN-EUR case (GB IBAN -> GBR) too.

Adds unit coverage for getCountryCodeForWithdraw (GB->GBR/DE->DEU/ES->ESP/US->USA,
idempotent on ISO-3) and getCountryFromIban, the derivation the fix relies on.
…IBAN source

CodeRabbit (2 major):
- BankFlowManager: the bankDetails objects (new-account, saved-account, guest)
  dropped `type`, so getOfframpConfigFromAccount() fell back to country routing —
  defeating the derive-from-type fix. Carry `type` through all three (guest path
  derives it from the response shape: iban/clabe/sort_code→gb/account→us).
- DynamicBankAccountForm: derive the IBAN country from a single normalized source
  (cleanedAccountNumber, falling back to the `iban` form value) so countryCode is
  never derived off an empty string → no empty countryCode to Bridge.
T1.4: destinationDetails derived the currency/rail from a country switch
whose default returned an empty rail, so a UK account typed anything but GB
(pre-BANK_GB mistype, or a Prisma-shaped 'BANK_GB') dead-ended on 'External
account ID is missing.'. Derive from the account via getOfframpConfigFromAccount
(GB->GBP), consistent with the claim flow.

T1.2: harden the freshly-added-account fallback — if the add-bank-account
response lacks bridgeAccountId, surface a retryable error instead of
navigating to a confirm screen that dead-ends on 'Bank account is missing.'.
…op settling-at-input)

Gating money-flows at input on an "available-now" subset was wrong: the FE
balance is only ~30s-polled while the spend routing reads the chain live at
submit (#2234), so an input-time available-now gate blocks spends that would
actually succeed — and the ~15s flow naturally lets the ~10-45s collateral
rebalance settle. So gate on the DISPLAYED balance (block only a true shortfall)
and let in-transit spends fail late on live execution.

- useWallet: hasSufficientSpendableBalance now gates on the displayed
  spendableBalance. Deletes spendBlockReason / availableSpendableBalance /
  SPEND_BLOCK_MESSAGE and the whole "settling at input" apparatus (−55 net).
- Refetch on failure (TanStack): on InsufficientSpendableError the routing
  already re-read live smart balance, so the two bundle hooks
  (useSpendBundle / useSignSpendBundle) invalidate [RAIN_CARD_OVERVIEW_QUERY_KEY]
  before throwing — the displayed balance + a retry de-stale immediately instead
  of waiting out the 30s poll.
- Informative failure copy: the post-gate in-transit failure now says "Your
  balance isn't fully available yet — try again in a few seconds" (one shared
  BALANCE_SETTLING_MESSAGE) across send (ErrorHandler), qr-pay, manteca,
  useSendMoney — instead of a misleading "add funds".
- Keeps the display-field fix + the formattedSpendableBalance formatting
  consolidation. Net -39 lines.
… docs, test)

Verified findings from the high-effort review pass:

- Send handleOnNext: gate only once balance has loaded (`balance !== undefined`),
  else a tap before the query resolves false-rejected with "not enough balance"
  (hasSufficientSpendableBalance returns false on undefined).
- useSendMoney.onError: invalidate ['balance', address] after the optimistic
  rollback — the rollback was discarding the fresh live balance useSpendBundle
  fetched mid-flight, leaving the smart portion stale until the next 30s poll.
- contribute-pot RequestPotActionList: gate the loading-flash on
  isFetchingSpendableBalance (smart + Rain overview), not isFetchingBalance
  (smart only) — the latter flashed a false "insufficient" for split-funds users
  while the overview loaded.
- Extracted the gate to a pure, exported `isDisplayBalanceSufficient` and unit-
  tested the gate-on-display contract (CONTRIBUTING: hooks that gate need a test).
- Fixed stale JSDoc that still claimed the gate runs on available-now
  (formattedSpendableBalance note, computeAvailableSpendable/DisplaySpendable),
  and scoped the "shared copy" comment to send/pay/withdraw.

Not changed (by design / accepted): gate widened to fail-late for the
features/payments flows (intended); manteca float-rounding at the boundary
fails-safe; the cosmetic toast+inline double-surface on a sendMoney settling
failure (follow-up).
…unts

BigInt(Math.floor(Infinity * 1e6)) throws a RangeError; a pasted/oversized amount
(parseFloat -> Infinity) must fail the gate, not crash the render. isNaN didn't
catch Infinity; use Number.isFinite. + tests.
fix(withdraw): UK GBP + GB-IBAN-EUR + intra-SEPA bank withdrawals (PR B — frontend)
Hugo0 and others added 3 commits June 24, 2026 00:05
…nd, precision)

Max-effort worst-case review found two real bugs I'd introduced plus robustness gaps:

- [money] Orphan charges: the features/payments flows (direct-send, contribute-pot,
  semantic-request) createCharge BEFORE sendMoney, so widening their gate to the
  displayed total let an in-transit amount pass, create a backend charge, then fail
  late — an unpaid charge per retry. Restore the meaningful split:
  hasSufficientSpendableBalance now gates on AVAILABLE-NOW (those charge-first flows),
  while the no-pre-charge flows (send-link, qr-pay, withdraw) gate on the DISPLAYED
  total via the renamed pure helper isAmountWithinBalance(amount, balance).
- [stuck] qr-pay Pay + manteca Withdraw buttons dead-ended after a settling failure:
  the settling message made isBlockingError / disabled true with no way to clear, while
  the copy says "try again". Exempt BALANCE_SETTLING_MESSAGE so the retry button stays live.
- [precision/crash] Gate now parses the amount with parseUnits (exactly what the spend
  uses) instead of float Math.floor(amount*1e6): kills the boundary divergence AND fails
  closed (returns false, never a BigInt(Infinity) RangeError) on adversarial input.
- [load] withdraw page no longer false-blocks ("insufficient") during the balance-load
  window (maxDecimalAmount=0); guarded on a loaded balance, re-validates when it lands.
- [robustness] ErrorHandler matches the typed error name, not just the message string.
- [cleanup] removed the orphaned PEANUT_WALLET_TOKEN_DECIMALS import in bank/page.

Tests: pure isAmountWithinBalance (incl. Infinity/overflow), useSendMoney onError refetch.
Full unit suite green.
…ring load

CodeRabbit on the adversarial-fix commit:
- Send-link (Major): the balance effect cleared errorState on every sufficient-
  balance pass, wiping a submit-time failure message (e.g. the settling copy) the
  moment loading returned to idle. Now it only clears OUR balance-gate error
  (INSUFFICIENT_BALANCE_MESSAGE), never a handleOnNext failure message.
- withdraw page (Minor): isContinueDisabled used maxDecimalAmount (=0 while the
  balance loads), disabling Continue during the load window. Guard it on a loaded
  balance, matching the validateAmount fix.
…lance

fix(request): show spendable balance incl. card collateral
feat(bridge): pass claimer details for travel rule in guest claims
jjramirezn and others added 3 commits June 24, 2026 15:03
Add-money made the user pick the method twice: 'Bank Transfer' at the entry
(AddMoneyMethodSelection), then again on /add-money/[country] (which renders
AddWithdrawCountriesList, the per-country method picker). The withdraw flow
already avoids this via withdrawBankUrl (country click → bank step directly);
add-money had no equivalent and used addMoneyCountryUrl → method picker.

Add addMoneyBankUrl (mirrors withdrawBankUrl) and use it in handleCountryClick
(only renders in the bank branch, so method is already 'bank'). Country click
now goes straight to the bank step on web and native. Tests for both modes.
Update the assertion that codified the old double-select behavior
(/add-money/[country]) to the new direct-to-bank navigation.
…k-selection

fix(add-money): skip the redundant second bank/method selection
Hugo0 and others added 2 commits June 24, 2026 14:53
The amount field stopped auto-focusing when entering some flows (reported on
add money): it used React's `autoFocus` prop, which only fires at the exact
moment the input mounts and silently no-ops when the input mounts after a
client-side navigation / step transition. Replace it with an explicit
inputRef.focus() in a useEffect gated on the same desktop-only condition
(shouldAutoFocus = deviceType === WEB), so focus lands regardless of mount
timing. Mobile is unchanged (autofocus stays off by design). Shared component —
restores focus across send / withdraw / qr-pay / add-money without altering the
mobile path.
fix(amount-input): reliably autofocus the amount field on desktop
@jjramirezn jjramirezn merged commit 39065c0 into main Jun 24, 2026
22 of 25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants