feat(machine): Bolt Card (NFC) tap-to-receive on cash-in #84
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/boltcard-nfc-cashin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Tap-to-receive for the buy flow — the receive counterpart to #83's cash-out tap-to-pay.
The problem
A Bolt Card only ever emits its
lnurlwwithdraw voucher (a spend credential) — the wrong direction to deposit into it. So a plain tap can't push sats to the card, andlnurl-authdoesn't help either (NTAG424 does AES SUN, not the secp256k1 signing lnurl-auth needs).The approach
Use the same tap as an authenticated identity (
external_id+ the SUNp/c, verified server-side exactly as/scandoes) to resolve the card wallet's lnurlp / Lightning Address, then have the ATM pay an invoice for the payout amount over its existing nostr transport. Settlement reuses the tested cash-in completion path (payInvoice→PAYMENT_RECEIVED).Card issuance is unchanged — same NDEF, same keys, same
external_id. Receive is a server-side reading of the same tap; already-issued cards work as-is.What's in this PR (ATM side — complete, hardware-independent)
electron/lnurl-pay.ts(new) —resolveCardInvoice(lnurlw, amountMsat): resolve card → pay target → LUD-16/LUD-06 → returns a BOLT11. Accepts three resolver response shapes (Lightning Address / inlinepayRequest/lnurlppointer).scanUrlToResolver()is the single HTTPS-today / Nostr-tomorrow transport seam. Never throws. 13 tests.lnurl:pay-card(main.ts) — runs the HTTPS in the main process to dodge renderer CORS;preload.ts+electron.d.tsexposeresolveCardInvoice().stores/atm.ts—handleBoltCardReceive(); the single NFC listener now routes the same physical tap by flow: cash-outdisplayingInvoicepulls, cash-indisplayingQRreceives. Status/cleanup extended to the QR screen;simulateBoltCardReceive()for dev.CashInView.vue— NFC status line + dev paste-lnurlw "Tap" input.docs/boltcard-receive-resolver.md— spec for the one custom LNbits endpoint this depends on.Depends on (LNbits
omni-private, not in this repo)A custom
GET /boltcards/api/v1/pay/{external_id}?p=&c=resolver — a/scansibling that verifies the same SUN and returns the card wallet's Lightning Address. Spec indocs/boltcard-receive-resolver.md. Until it exists, the tap path is testable via the dev input / mocked fetch; the withdraw-QR cash-in path is unaffected.Notes
uses:1and the tap gates re-entry + leavesdisplayingQRon success. Documented as acceptable-for-now in the spec.external_id + SUNidentity over the ATM's existing nostr connection, dropping the clearnet HTTPS call — localized toscanUrlToResolver/the resolve step.Verification
pnpm typecheck(renderer + electron) cleanvitest run electron/— 55 passed (13 new inlnurl-pay.test.ts)🤖 Generated with Claude Code