feat(machine): open a verified Bolt Card session at tap-to-enter
Entry now spends the tap's single-use SUN once, on the card server's new /session endpoint (aiolabs/boltcards feat/card-session-endpoint), instead of parsing the lnurlw locally and deferring every check to Complete. The server proves a genuine, non-replayed card and returns the wallet balance plus the hit-keyed LUD-03 withdraw and LUD-06 pay second steps — the same single-use bearer /scan and /pay hand out — so Complete still needs no second tap and the ATM holds no p/c for the visit. - electron/boltcard-session.ts: /scan → /session URL derivation, response parsing, 404 → 'card server does not support sessions'. - lnurl-withdraw / lnurl-pay: the second steps are now callable on their own (executeWithdrawCallback, resolveInvoiceFromPayStep); the tap paths are unchanged and reuse them. - IPC: lnurl:open-card-session, lnurl:withdraw-session, lnurl:pay-session. - store: handleBoltCardEntry opens the session then authorizes the server-returned external_id; the payment handlers take a source (raw tap or session); a withheld withdraw step declines with the server's reason. The boltcard AccessScan no longer carries the lnurlw. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
ce4b5a5dc6
commit
735132b032
12 changed files with 639 additions and 63 deletions
|
|
@ -42,8 +42,13 @@ import {
|
|||
type StoredBunkerBinding,
|
||||
} from './state-store.js'
|
||||
import { initializeHal, type HalInstance } from './hal-service.js'
|
||||
import { executeLnurlWithdraw } from './lnurl-withdraw.js'
|
||||
import { resolveCardInvoice } from './lnurl-pay.js'
|
||||
import {
|
||||
executeLnurlWithdraw,
|
||||
executeWithdrawCallback,
|
||||
type WithdrawStep,
|
||||
} from './lnurl-withdraw.js'
|
||||
import { resolveCardInvoice, resolveInvoiceFromPayStep, type PayStep } from './lnurl-pay.js'
|
||||
import { openCardSession, type OpenCardSessionResult } from './boltcard-session.js'
|
||||
import { startNfcReader, type NfcStatus } from './nfc-service.js'
|
||||
|
||||
// ESM equivalent of __dirname
|
||||
|
|
@ -503,6 +508,37 @@ ipcMain.handle(
|
|||
}
|
||||
)
|
||||
|
||||
// Bolt Card tap-to-enter (ADR-003): open a verified session for a tapped card.
|
||||
// Spends the tap's SUN once and returns balance + fiat + the withdraw/pay
|
||||
// second steps the session reuses at Complete. See boltcard-session.ts.
|
||||
ipcMain.handle(
|
||||
'lnurl:open-card-session',
|
||||
async (_event, args: { lnurlw: string }): Promise<OpenCardSessionResult> => {
|
||||
return openCardSession(args.lnurlw)
|
||||
}
|
||||
)
|
||||
|
||||
// Session variants of the two Complete paths: no tap, no p/c — just the
|
||||
// hit-keyed second step the session already holds.
|
||||
ipcMain.handle(
|
||||
'lnurl:withdraw-session',
|
||||
async (
|
||||
_event,
|
||||
args: { withdraw: WithdrawStep; bolt11: string; amountMsat?: number }
|
||||
): Promise<{ ok: boolean; reason?: string }> => {
|
||||
return executeWithdrawCallback(args.withdraw, args.bolt11, { amountMsat: args.amountMsat })
|
||||
}
|
||||
)
|
||||
ipcMain.handle(
|
||||
'lnurl:pay-session',
|
||||
async (
|
||||
_event,
|
||||
args: { pay: PayStep; amountMsat: number }
|
||||
): Promise<{ ok: boolean; bolt11?: string; reason?: string }> => {
|
||||
return resolveInvoiceFromPayStep(args.pay, args.amountMsat)
|
||||
}
|
||||
)
|
||||
|
||||
// State persistence IPC handlers
|
||||
ipcMain.handle('state:load-cassettes', () => loadCassettes())
|
||||
ipcMain.handle('state:set-cassettes', (_event, cassettes) => setCassettes(cassettes))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue