fix(qr): recognize scanned Solana and Tron addresses - #2675
Conversation
The scanner lowercased the scanned payload before handing it to recognizeQr. Base58 carries meaning in its case, so that destroyed the address: an uppercase L is a valid Solana character while a lowercase l is not, and every Tron address starts with an uppercase T. About half of all Solana addresses and every Tron address fell through to the "Unrecognized QR code" modal. recognizeQr was always correct and its own suite even asserts case-sensitivity on purpose. Only the call site was wrong, so the new test covers the component, not the parser. Task: TASK-21111
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthrough
ChangesQR scanner case preservation
Estimated code review effort: 3 (Moderate) | ~15–30 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Code-analysis diffPainscore total: 7119.83 → 7120.29 (+0.46) 🆕 New findings (6)
✅ Resolved (6)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
Recognizing the raw scan alone was too strict in the other direction. QR alphanumeric mode encodes uppercase only and is much denser, so encoders routinely uppercase bech32 addresses, BOLT-11 invoices and hex addresses. Those payloads lost their case to the encoder, so lowercasing them back is safe. A payload holding any lowercase letter kept its original case, so a mixed-case EIP-55 checksum is the user's and viem must stay free to reject a bad one. The old blanket lowercase laundered a corrupted address into a valid-looking payment target; that stops here. Found by /code-review.
|
@coderabbitai review |
✅ Action performedReview finished.
|
The explanation ran longer than the code it explained.
|
@coderabbitai full review |
|
|
@coderabbitai full review |
✅ Action performedFull review finished. |
kushagrasarathe
left a comment
There was a problem hiding this comment.
Reviewed via pr-review rulebook (T2, full-diff read). Fix verified: all recognizeQr branches checked case-safe on raw input (ENS, PIX email/UUID, EMV regexes all case-tolerant); bad-checksum tightening consistent through the clipboard-extract recursion path. Tests pin the component wiring, not the parser. One accepted edge: lowercase-0x + all-caps-hex EVM address now rejected — measured zero in prod. Reminder: base is main, back-merge to dev after landing.
Summary
The QR scanner rejected valid Solana deposit addresses as "Unrecognized QR code".
processQRCodelowercased the scanned payload and passed that copy torecognizeQr. Base58 carries meaning in its case, so the lowercase pass destroyed the address:Lis a valid base58 character, a lowercaselis not. Any address holding anLstopped matching. That is ~53% of addresses (measured over random 44-char base58 strings).T, and the pattern anchors on^T. Lowercasing broke 100% of them, always.Hex addresses were unaffected, which is why this stayed invisible: EVM survives
.toLowerCase()because hex is case-insensitive.The fix
Recognize the raw scan first, and retry lowercased only for an all-uppercase payload:
The two halves encode when case is information and when it is an artifact:
L≠l,T≠tThat last row is a deliberate tightening. The old blanket lowercase laundered a mixed-case address with a broken EIP-55 checksum into a valid-looking payment target — which is the exact corruption EIP-55 exists to catch. It now reaches the unrecognized modal instead of the send flow.
normalizedstill drives the routing below, where hex and ENS are case-insensitive anyway, andrecognizeQrlowercases the branches that need it (Peanut URL, PIX) itself.Task
TASK-21111 — reported twice via support; reporter received the bug bounty.
Evidence
recognizeQrwas never the problem. Its 635-line suite passes raw mixed-case data and even asserts case-sensitivity on purpose ('LNBC…'must not matchBITCOIN_INVOICE). The single call site lowercased one line earlier and had no test at all — so the new test covers the component, not the parser.PostHog, last 60 days of
qr_scanned:null(unrecognized)SOLANA_ADDRESSTRON_ADDRESSTron has never once been recognized in production. One unrecognized scan in that window is a confirmed base58 Solana address holding an
L.Production sanity checks behind the design:
Risks
Low. Frontend only, one expression, no backend or contract surface.
The one behavior change beyond recognition is the bad-checksum tightening described above — measured at zero production instances.
Recognized Solana/Tron scans now reach the existing "not supported yet / get notified" modal instead of the unrecognized one. See Design notes.
Design notes / accepted trade-offs
Routing is unchanged, deliberately. After this fix a scanned Solana address lands on the existing
QR_NOT_SUPPORTEDmodal. Solana and Tron are live withdraw destinations (chainRegistry.consts.ts, Rhino, behindchain-rollout-solana/chain-rollout-tron), so that copy is arguably stale — but wiring scan-to-send would need a withdraw deep-link with chain preselection plus flag gating. That is a feature, not this bug, and the reported user was scanning Peanut's own deposit address, for which "send to it" is not a meaningful destination anyway. Flagging it rather than scope-creeping.Revealed, not introduced — uppercase URL schemes. The
PEANUT_URLbranch strips the protocol with a case-sensitive regex (index.tsx:286), soHTTPS://PEANUT.ME/satoshiroutes to//PEANUT.ME/satoshiand 404s. Same QR-uppercasing reason as above, so it is reachable. Pre-existing and untouched by this diff — filed for a follow-up rather than bundled here.Solana/Bitcoin label ambiguity. A Solana address starting with
1or3(~3.4%) matches the Bitcoin pattern first and is labelled "Bitcoin". Cosmetic, on a modal that says "not supported" either way, and unfixable without real checksum validation. Pre-existing.QA
npm test— 228 suites, 2925 tests, green.New pin:
src/components/Global/QRScannerOverlay/__tests__/base58-case.test.tsx. It discriminates all three candidate designs — only the shipped one passes all 8:Manual: open the scanner, scan any Solana address containing an uppercase
L(e.g. the QR on Add money → Crypto → Solana). Before: "Unrecognized QR code". After: "Solana not supported yet."Screenshots
N/A — no visible change. The modal that renders was already built; this PR only changes which one is reached.
Summary by CodeRabbit