user-stories
US-02 — QR Code for Receiving Payments
US-02 — QR Code for Receiving Payments
User opens the receive page for a chosen asset, gets a deposit address rendered as a QR code, shares it (copy / show on screen / one-time link), and the deposit lands in their wallet.
Persona
Any signed-in user — both users.kind = 'individual' and 'company'. KYC approved is not required for receiving (only sending), so this is the lowest-friction surface in the product.
Goal
"Show me a QR code for my USDC-on-ETH wallet so the person sending me money can scan it from their phone."
Preconditions
- User is signed in and not blocked.
- The chosen asset/network has a wallet provisioned for this account (lazy-provisioned on first visit).
- For the one-time link variant: the device generating the link can write to the link store (
payment_linksrow with single-use token).
Happy path
- User goes to
/receive, picks an asset (AssetChipGrid). - Routed to
/receive/[asset]. Server resolves the wallet for(account, asset, network); if missing, callsFireblocks.getOrCreateAddress(...)and persists. - Page renders three deliverables for the same address:
- QR code (PNG, contains canonical URI like
ethereum:0xabc…?amount=…when amount is preset; otherwise the bare address). - Copy address button.
- One-time link button → mints a single-use token, deep-links to a public landing page that shows the same QR with the recipient's name.
- QR code (PNG, contains canonical URI like
- Counter-party scans, sends to the address.
- Indexer records the
deposittransaction;/walletreflects the new balance and a transaction row. - Optional: user receives a
deposit.receivedemail if notifications are enabled.
Alternate flows
- Pre-fill amount: user enters a fiat or crypto amount before generating the QR. The QR encodes the amount per BIP-21 / EIP-681 conventions (sender's wallet may auto-fill).
- Specify memo / reference: included in the URI for assets that support it (e.g., XRP destination tag, XLM memo). Not all assets supported in MVP.
- Re-share: user can re-open the page later; the address is persistent per
(account, asset, network). - Multiple chains for same asset (e.g., USDC on ETH / Polygon / Tron): user picks the chain explicitly; we never auto-pick.
Edge cases & failure modes
- Wrong-network deposit (e.g., USDC on Tron sent to an ETH address): funds are unrecoverable in MVP. UI warns explicitly with a "scan must be on ETH network" banner.
- Memo-required asset, memo missing: deposit may credit to the parent vault, not the user. Ops investigation is manual today.
- One-time link reused: second scan shows "Link already used"; address itself remains valid for direct sends.
- Address rotation: not supported in MVP — same address per account/asset/network for life. Privacy tradeoff documented.
- QR collision: each QR is unique to address + amount + memo; no collision risk because addresses are globally unique.
Acceptance criteria
- QR renders ≤500 ms after asset selection.
- Copy-to-clipboard works on all major browsers.
- One-time link mints a token that is invalidated on first use.
- Network warning is visible above the QR, not below the fold.
- Deposits to the address show in
/walletwithin one indexer cycle. - No KYC gate on receiving.
Out of scope
- Address rotation per receive (would require a swept-vault model — Beta hardening)
- ENS / human-readable address resolution on receive
Drawbacks
- Wrong-network sends are unrecoverable; we rely on UI warnings, not on-chain checks.
- Static address per account is a privacy tradeoff (chain analysts can cluster).
- One-time link state is in
payment_links, not encrypted — token leak = address disclosure (no funds at risk, but a tracking signal).
Open questions
- ../09-Open Questions#PII storage (one-time-link token storage)
- Whether to add an opt-in rotating address mode at GA — pending vault-cost analysis under ../09-Open Questions#Custody.
Implementing surfaces
- UI:
app/my/receive/page.tsx,app/my/receive/[asset]/page.tsx,app/my/send-receive/page.tsx - Address resolution:
lib/wallet/index.ts(or equivalent — provider-agnostic interface) - One-time link:
payment_linkstable + token route - Schema:
wallets,payment_links - Domain doc: ../03-Domains#Receive