Skip to content

fix: EIP-681 QR scans 404 — recipient guard rejects address@chainId#2427

Open
abalinda wants to merge 20 commits into
devfrom
fix/eip681-address-chain-404
Open

fix: EIP-681 QR scans 404 — recipient guard rejects address@chainId#2427
abalinda wants to merge 20 commits into
devfrom
fix/eip681-address-chain-404

Conversation

@abalinda

@abalinda abalinda commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Why

Scanning any real-world EIP-681 payment QR (e.g. ethereum:USDC@42161/transfer?address=0x…&uint256=3.44e7) dead-ends on the 404 page. The scanner parses it correctly and builds /0x…@42161/34.4USDC, and the url parser downstream fully supports recipient@chain — but couldBeRecipient (the Feb-21 /es/argentina SEO-regression guard) tests the EVM-address regex against the full segment including the @42161 suffix, fails, and notFound()s.

Repro: Konrad's scan 07-15 13:54 UTC (PostHog qr_scanned, qr_type EIP_681, Crisp session_50bb92f0).

What

  • couldBeRecipient: strip the @chain suffix before the address/ENS checks (it was already stripped for the username check). Chain validation still happens downstream. Also collapsed the dead includes('@') ternary (split('@')[0] is equivalent).
  • generateMetadata ([...recipient]/page.tsx): wrapped parseAmountAndToken in try/catch — it throws on malformed amount segments and the widened guard makes /0x…@chain/<garbage> URLs reach it (was pre-existing for /username/<garbage>); now falls back to default metadata instead of 500ing.

Design notes / accepted trade-offs

Testing

  • New assertions: 0xADDR@42161, percent-encoded %4042161, vitalik.eth@arbitrum → accepted; all locale-regression guards still pass.
  • Full local suite: 127/127 suites, 1834 tests green. Prettier clean. eslint: the 1 pre-existing error in routes.ts:143 is untouched (advisory backlog, not worsened).

🤖 Generated with Claude Code

abalinda and others added 19 commits July 13, 2026 18:53
…rozone

Bridge now supports SEPA for Albania, Moldova, Montenegro, North Macedonia
and Serbia (TASK-20580) — surface the bank rail as available instead of
'Soon' for deposit and withdraw, and show the standard EUR-accounts-only
warning for the four that keep a domestic currency.

Bulgaria adopted the euro in Jan 2026: badge EUR instead of BGN and drop
the stale non-EUR SEPA warning.
The map already carried US/GB/CH and now the 2025/26 SEPA joiners; the old
label invited a 'clean up non-EEA entries' edit that would disable live
rails.
Signed-off-by: ab <78670703+abalinda@users.noreply.github.com>
feat: enable SEPA bank rail for AL/MD/ME/MK/RS + treat Bulgaria as Eurozone
…rnels

Three accounts carry validNonceFrom > currentNonce (left by the 2025-09-18
root-validator migration wave); Kernel v3.1 rejects every enable-mode card
approval below that floor with InvalidNonce, so auto-balance fails hourly
forever and no re-grant can help — the account state itself is the poison.
A fourth account is undeployed with a v0.0.3-initCode approval (AA14).

Hidden support page (not linked anywhere): diagnoses the account on-chain,
sends the matching repair userOp (invalidateNonce(floor+1), or the migration
no-op deploy), confirms against re-read chain state, then re-grants — the
existing session-approve route stores the fresh approval and kicks off a
funding run immediately.
- diagnose keyed on the async zerodev address (mount-only effect left the
  page dead on a cold load from a support DM — the exact target flow)
- repair() re-diagnoses live state before sending, so a retry after a
  confirm-poll timeout can't re-send an already-consumed invalidateNonce
- guard the Kernel v3.1 invalidation cap (floor > currentNonce+10 has no
  valid target — fail with clear copy instead of burning doomed passkey taps)
- step 2 respects the card-overview loading state instead of flashing
  'no active card — contact support' at users on the success path
- 'Check again' affordance after a failed diagnosis; dismissed passkey
  sheets are quiet no-ops, matching the grant path; drop the dead lastError
  render condition and the type export from the hook file
…e-repair

feat(card): /fix-card-signature — guided repair for enable-bricked kernel accounts
A user cancelled a funded bank deposit while trying to report a problem:
the cancel button sits next to the support link and fired instantly with
no confirmation (PostHog: 'Issues with this transaction?' at 09:03:00,
'Cancel deposit' 11s later), making the arriving wire unmatchable.

The drawer also showed the FULL deposit reference while Add Money shows
the intentionally shortened 10-char form (some banks cap reference
fields; Bridge matches on the partial) — the two-different-codes
confusion that sent the user hunting in the drawer to begin with.
…a vaul drawer

Root cause of the phantom deposit cancels: vaul sets pointer-events:none
on body while the transaction drawer is open, and the SupportDrawer's
backdrop/panel never re-enabled pointer events — the whole support
overlay was click-transparent, so taps on it fell through to the receipt
underneath, where 'Cancel deposit' sits adjacent to the support link and
fired irreversibly (PostHog: support link 15:05:47.7 → fall-through
click on Cancel deposit 15:05:48.8 → BE cancel 15:05:49.4).

Also per code review: single shortDepositReference() helper for the
6 reference sites across both screens, separate confirm-modal visibility
from the armed action (noun flashed mid-fade), drop no-op icon props.
… twice

CodeRabbit finding: two taps during the modal fade both see the armed
action and start concurrent cancellations. Ref guard, not state — a
same-frame double-tap lands before any re-render.
…rmation

fix(deposits): stop support-chat clicks from cancelling deposits + confirm-before-cancel + uniform reference
Bumps src/content to peanut-content hotfix 32f5b91 (off current prod
pointer f3c0ef66): removes personal phone number + swaps help@ -> support@
in the card legal docs. Isolated from the in-flight tos-v1 rework so it
passes content verification and ships to peanut.me now.
…tact-hotfix

content: publish card-legal contact hotfix (remove phone number, help@ → support@)
…3a05f7)

Bumps src/content on production (main) from 32f5b91 → e3a05f7
(peanut-content latest = mono@83166e9). Single-file submodule
pointer change. Publishes content already merged + mirrored from mono that the
dev-targeted auto-PRs never promote to main.
…202607151650

content: publish legal docs overhaul to production (tos-v1)
The Feb-21 SEO guard (couldBeRecipient) only whitelisted bare EVM
addresses, so the QR scanner's EIP-681 path — which builds
/0x…@42161/34.4USDC and is fully supported by the downstream url
parser — dead-ended on a 404 for every real-world EIP-681 payment QR.
Strip the @chain suffix before the address/ENS checks; chain validation
still happens downstream.
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 15, 2026 6:05pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

couldBeRecipient now strips @chain suffixes before recipient recognition, allowing address and ENS deep-link formats. Tests cover raw and percent-encoded separators.

Changes

Recipient deep-link recognition

Layer / File(s) Summary
Strip chain suffixes before recipient checks
src/constants/routes.ts, src/constants/__tests__/routes.test.ts
couldBeRecipient applies EVM address, ENS, and username checks to the suffix-stripped base, with tests for address and ENS chain deep links.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: hugo0, jjramirezn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing recipient-route handling for EIP-681 address@chainId deep links.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/eip681-address-chain-404

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 Jul 15, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6122.01 → 6122.26 (+0.25)
Findings: 0 net (+6 new, -6 resolved)

🆕 New findings (6)

  • high complexity — src/app/[...recipient]/page.tsx — CC 49, MI 44.51, SLOC 135
  • high method-complexity — src/app/[...recipient]/page.tsx:18 — generateMetadata CC 42 SLOC 121
  • medium high-mdd — src/app/[...recipient]/page.tsx:18 — generateMetadata: MDD 46.3 (uses across many lines from declarations)
  • medium structural-dup — app/[...recipient]/page.tsx:157 — 32 duplicate lines / 107 tokens with app/invite/page.tsx:55
  • medium complexity — src/constants/routes.ts — CC 14, MI 61.45, SLOC 46
  • low missing-return-type — src/app/[...recipient]/page.tsx:188 — Page: exported fn missing return type annotation

✅ Resolved (6)

  • src/app/[...recipient]/page.tsx — CC 49, MI 44.57, SLOC 134
  • src/app/[...recipient]/page.tsx:18 — generateMetadata CC 42 SLOC 120
  • src/app/[...recipient]/page.tsx:18 — generateMetadata: MDD 44.3 (uses across many lines from declarations)
  • app/[...recipient]/page.tsx:153 — 32 duplicate lines / 107 tokens with app/invite/page.tsx:55
  • src/constants/routes.ts — CC 15, MI 60.99, SLOC 47
  • src/app/[...recipient]/page.tsx:184 — Page: exported fn missing return type annotation

@abalinda abalinda marked this pull request as draft July 15, 2026 17:49
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1837 ran, 0 failed, 0 skipped, 22.2s

📊 Coverage (unit)

metric %
statements 58.2%
branches 42.2%
functions 47.0%
lines 58.3%
⏱ 10 slowest test cases
time test
2.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.2s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.2s 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 › keeps stickers off the username pill (final pass respects the keep-out)
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 (invalid characters)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid US account with spaces
0.1s src/hooks/query/__tests__/user.test.tsx › calls setAuthToken when the response includes a refreshed token
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid US account with spaces 2
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@abalinda

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@abalinda abalinda marked this pull request as ready for review July 15, 2026 17:52
@abalinda abalinda marked this pull request as draft July 15, 2026 17:55
…ed amount segments

The widened recipient guard makes /0x…@chain/<garbage-amount> URLs reach
generateMetadata, where an unguarded parseAmountAndToken throw 500'd the
render (pre-existing for /username/<garbage>, now wider). Fall back to
default metadata instead.
@abalinda

Copy link
Copy Markdown
Contributor Author

Retargeted to dev per Hugo's request. Verification notes:

Is this already fixed on dev? No — web is still broken. couldBeRecipient on dev is byte-identical to main's broken version (zero dev-only commits touch routes.ts). What c3d8163 fixed is the native routing only: recipientPayUrl() returns /send?recipient=… on Capacitor, but on web it returns /${path} — the same /0x…@42161/… URL that hits the unchanged guard and 404s. This PR is still needed; it composes fine with the native helper (same path string, now valid on web too).

Conflicts: none — verified with git merge-tree against origin/dev (clean tree, 0 conflicts).

Heads-up before merging: this branch is based on main's tip, and main is currently 18 commits ahead of dev (content publishes + #2416). Merging this PR into dev therefore also performs that main→dev back-merge. All 18 are legitimately destined for dev, but if you'd rather keep this PR's diff to the 2 fix commits, say the word and I'll rebase it onto dev (needs a force-push).

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.

4 participants