user-stories

US-02 — QR Code for Receiving Payments

docs/user-stories/02-QR-Receive.mdtype: user-storyupdated: 2026-05-03

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_links row with single-use token).

Happy path

  1. User goes to /receive, picks an asset (AssetChipGrid).
  2. Routed to /receive/[asset]. Server resolves the wallet for (account, asset, network); if missing, calls Fireblocks.getOrCreateAddress(...) and persists.
  3. 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.
  4. Counter-party scans, sends to the address.
  5. Indexer records the deposit transaction; /wallet reflects the new balance and a transaction row.
  6. Optional: user receives a deposit.received email 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 /wallet within 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

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_links table + token route
  • Schema: wallets, payment_links
  • Domain doc: ../03-Domains#Receive