Skip to content

fix: EIP-681 QR scans 404 — recipient guard rejects address@chainId (main hotfix) - #2612

Merged
kushagrasarathe merged 4 commits into
devfrom
hotfix/eip681-address-chain-404-main
Aug 5, 2026
Merged

fix: EIP-681 QR scans 404 — recipient guard rejects address@chainId (main hotfix)#2612
kushagrasarathe merged 4 commits into
devfrom
hotfix/eip681-address-chain-404-main

Conversation

@abalinda

@abalinda abalinda commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Why

Cherry-pick of the EIP-681 fix from #2427, re-targeted at main as a hotfix (original PR is based on dev and carries unrelated dev history).

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 builds /0x…@42161/34.4USDC correctly and the downstream url parser supports recipient@chain — but couldBeRecipient (the Feb-21 /es/argentina SEO-regression guard) tests the EVM-address regex against the full segment including @42161, fails, and notFound()s.

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

What

Two commits cherry-picked from #2427 (b085d10a, a423b1e1):

  • couldBeRecipient: strip the @chain suffix before the address/ENS checks (already stripped for the username check); collapse the dead includes('@') ternary.
  • generateMetadata ([...recipient]/page.tsx): wrap parseAmountAndToken in try/catch — the widened guard lets /0x…@chain/<garbage> reach it; now falls back to default metadata instead of 500ing.

Design notes / accepted trade-offs

Risks / breaking changes

QA

  • New assertions: 0xADDR@42161, percent-encoded %4042161, vitalik.eth@arbitrum accepted; locale-regression guards still pass.
  • Local: 177/178 suites green (the one red suite, deferred-link.test.ts, is a worktree env artifact — @capacitor/preferences missing from the symlinked node_modules; untouched by this diff).

Summary by CodeRabbit

  • New Features

    • Added support for recipient links that include a chain identifier after an address or ENS name, including encoded separators.
  • Bug Fixes

    • Prevented malformed amount or token values in links from breaking page rendering.
    • Default metadata is now generated when link parameters cannot be parsed.

0xkkonrad and others added 4 commits August 5, 2026 13:35
Both country-launch links ship with no inviter. Mapping them in
UTM_CAMPAIGN_TO_BADGE_MAP was not enough: classifyBareCampaign only bypasses
the invite gate for campaigns registered in WAITLIST_SKIP_CAMPAIGNS or
BARE_VANITY_CAMPAIGNS, so a bare ?campaign=naija dead-ended on 'Invalid Invite
Code' instead of awarding the badge. Same bug that left touched_grass
unclaimable.

They go in WAITLIST_SKIP_CAMPAIGNS, not BARE_VANITY_CAMPAIGNS: both badges are
in peanut-api-ts POSTLAUNCH_SKIP_BADGE_CODES, and the vanity set is for badges
with no card-waitlist skip. Filing them as vanity would show generic claim copy
while the backend granted a skip anyway.
…-links

fix(badges): register naija and terere as bare claimable campaigns
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.
…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.
@vercel

vercel Bot commented Aug 5, 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 Aug 5, 2026 2:51pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Recipient detection now supports address and ENS links with optional chain suffixes. Metadata generation now handles malformed amount or token segments by using default metadata values.

Changes

Recipient link handling

Layer / File(s) Summary
Chain-suffixed recipient validation
src/constants/routes.ts, src/constants/__tests__/routes.test.ts
couldBeRecipient removes optional @chain suffixes before validating addresses, ENS names, and usernames. Tests cover numeric and percent-encoded chain identifiers.
Malformed metadata parsing
src/app/[...recipient]/page.tsx
Amount and token parsing now catches invalid segments and generates metadata without those values.

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

Possibly related PRs

Suggested reviewers: hugo0

🚥 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 identifies the EIP-681 QR scan 404 fix caused by recipient validation rejecting address@chainId 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 hotfix/eip681-address-chain-404-main

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

@abalinda
abalinda marked this pull request as ready for review August 5, 2026 14:46
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6346.7 → 6346.94 (+0.24)
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

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2357 ran, 0 failed, 0 skipped, 39.5s

📊 Coverage (unit)

metric %
statements 63.0%
branches 46.8%
functions 52.9%
lines 63.4%
⏱ 10 slowest test cases
time test
4.0s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.0s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.5s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
0.2s src/utils/__tests__/demo-balance.test.ts › auto-refills a stored balance that has no timestamp (legacy install)
0.2s src/utils/__tests__/demo-balance.test.ts › debits and floors at zero
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@abalinda

abalinda commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

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 August 5, 2026 14:54
@abalinda
abalinda requested review from Hugo0 and a lite review from Copilot and removed request for Hugo0 August 5, 2026 14:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Hotfix to main to prevent EIP-681 QR deep links (e.g. /0x…@42161/<amountToken>) from incorrectly 404’ing due to the catch-all recipient route guard rejecting address@chain-shaped segments, and to make metadata generation resilient to malformed amount/token segments.

Changes:

  • Updated couldBeRecipient to validate address@chain / ens@chain by stripping the @chain suffix before applying address/ENS/username checks.
  • Added regression tests covering address@chainId, percent-encoded %40<chainId>, and ens@chain.
  • Wrapped parseAmountAndToken in generateMetadata with try/catch to avoid 500s on malformed amount segments.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/constants/routes.ts Adjusts recipient guard to consider only the base recipient when @chain is present.
src/constants/tests/routes.test.ts Adds assertions to ensure address@chain / ens@chain pass the guard.
src/app/[...recipient]/page.tsx Prevents metadata generation from throwing on malformed amount/token URL segments.

Comment thread src/constants/routes.ts
Comment thread src/constants/__tests__/routes.test.ts
@kushagrasarathe
kushagrasarathe changed the base branch from main to dev August 5, 2026 15:55

@kushagrasarathe kushagrasarathe 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.

Reviewed via pr-review rulebook: T2, all four checklists pass. Guard widening is bounded (chain validation intact downstream), both throw paths covered (generateMetadata try/catch + SemanticRequestPageWrapper .catch). campaign-maps diff is #2610 riding along pending dev back-merge — benign, BE support confirmed on dev and main. Copilot multiple-@ finding: harmless, garbage @-URLs render the documented error state, not a crash. Known advisory eslint red is pre-existing debt, not worsened by this diff.

@kushagrasarathe
kushagrasarathe merged commit 549cc4e into dev Aug 5, 2026
29 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants