Skip to content

fix(send): route QR-scanned usernames to BTC-default recipients via LNURL (ENG-399)#646

Merged
islandbitcoin merged 1 commit into
mainfrom
jabariennis/eng-399-qrnfc-username-sends-fail
Jun 20, 2026
Merged

fix(send): route QR-scanned usernames to BTC-default recipients via LNURL (ENG-399)#646
islandbitcoin merged 1 commit into
mainfrom
jabariennis/eng-399-qrnfc-username-sends-fail

Conversation

@islandbitcoin

Copy link
Copy Markdown
Contributor

Summary

A bare Flash username scanned via QR that resolves to a recipient whose default wallet is BTC/Breez fails when the sender's wallet is USD: the shared parser treats it as PaymentType.Intraledger, and the USD send then calls intraLedgerUsdPaymentSend against the recipient's BTC wallet — failing with the generic support error.

Manual bare-username entry already handles this (PR #602) via a module-private maybeResolveManualUsernameToLnurl, which detects the BTC-default recipient and re-routes the payment through their lightning address (username@lnAddressHostname). This PR brings the QR path to parity.

Changes

  • Extract maybeResolveManualUsernameToLnurl into payment-destination/resolve-username-to-lnurl.ts so every entry point shares one routing decision (the ticket explicitly calls out the duplicated-logic risk). No behavior change for manual entry — it now imports the shared helper.
  • Apply it in the QR-scan processInvoice using the same maybeResolve… ?? parseDestination fallthrough as manual entry. The helper returns null for anything that isn't a BTC-default-recipient username, so all other QR destinations are unaffected.
  • Tests__tests__/payment-destination/resolve-username-to-lnurl.spec.ts covers all five branches: non-intraledger pass-through, USD-recipient pass-through, self-payment, successful LNURL re-route (handle@hostname), and the unreachable-lightning-address (LnurlError) case.

Scope (why only QR)

Of the four parseDestination entry points, QR is the only one that carries a bare username into a send:

  • NFC (modal-nfc.tsx) is receive/withdraw-only — it reads lnurl payloads only and explicitly rejects Send-direction destinations (nfcOnlyReceive).
  • Card (card.tsx) always carries a Flashcard LNURL, never a bare username.

Editing those would be incorrect/risky, so they're intentionally untouched. The durable, all-entry-point fix is the backend resolveSendDestination resolver — ENG-399 Phase 2 — filed as a separate follow-up.

Validation

⚠️ main's CI is currently red for toolchain reasons unrelated to this PR — the tsconfig TS5095 error (fixed on feat/fygaro by #642) and the ttypescript/ts-jest breakage (ENG-421/425). main inherits these fixes when #621 lands. Validated locally instead:

Acceptance criteria

  • QR-scanned username to a BTC-default recipient routes through LNURL (USD send succeeds)
  • Routing logic shared, not duplicated per entry point
  • Other QR destinations unaffected (helper returns null to fall through)
  • Unit tests cover the routing policy
  • Phase 2 backend resolver (resolveSendDestination) — tracked separately

Refs ENG-399 (Phase 1).

🤖 Generated with Claude Code

…NURL (ENG-399)

A bare Flash username scanned via QR that resolves to a recipient whose
default wallet is BTC/Breez previously failed when the sender's wallet was
USD: the shared parser treated it as PaymentType.Intraledger and the USD
send hit intraLedgerUsdPaymentSend against the recipient's BTC wallet,
failing with the generic support error.

Manual entry already handled this (PR #602) via a module-private
maybeResolveManualUsernameToLnurl. This:

- Extracts that helper into payment-destination/resolve-username-to-lnurl.ts
  so every entry point can share the routing decision (the ticket flags the
  duplicated-logic risk).
- Applies it in the QR-scan processInvoice via the same
  `maybeResolve... ?? parseDestination` pattern as manual entry, so a
  BTC-default recipient is re-routed through `username@lnAddressHostname`.
- Adds unit tests for the helper covering: non-intraledger pass-through,
  USD-recipient pass-through, self-payment, successful LNURL re-route, and
  the unreachable-lightning-address error.

Scope: QR is the only entry point that carries a bare username into a send.
NFC is receive/withdraw-only (rejects Send-direction, reads lnurl payloads
only) and the card flow always carries a Flashcard LNURL, so neither is
changed. The durable cross-entry-point fix is the backend resolveSendDestination
resolver (ENG-399 Phase 2), filed separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear

linear Bot commented Jun 18, 2026

Copy link
Copy Markdown

ENG-399

@islandbitcoin islandbitcoin self-assigned this Jun 18, 2026
@islandbitcoin islandbitcoin requested a review from Nodirbek75 June 19, 2026 19:11
@islandbitcoin islandbitcoin merged commit 133bfe8 into main Jun 20, 2026
3 of 6 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.

1 participant