From 334cb867712ed1edaf0898e21d80287c70a1394f Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 30 Jun 2026 12:06:34 +0200 Subject: [PATCH] fix(machine): resolve signer before LNbits config so an unpaired machine reaches the pairing wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit initializeLightningServices() validated VITE_LNBITS_SERVER_PUBKEY (and, in strict mode, rejected a localhost relay) *before* calling resolveSigner. An unpaired machine — no seed, no binding, blank .env — therefore threw a generic config Error that classifyInitError surfaces as the static "ATM Unavailable" screen, never the NoPairingError that routes to the QR-pairing wizard. Pairing is what's meant to provide the transport config, so the pairing check must come first. Move resolveSigner ahead of the strict + server-pubkey validation: an unpaired machine now throws NoPairingError → 'unpaired' → wizard regardless of relay/pubkey provisioning, while a paired machine still hits the config validation it legitimately needs. Surfaced testing the freshly-built Sintra images (live ISO + USB disk image), both of which ship a blank .env by design and booted straight to "ATM Unavailable". bitspire-#70 (part 1 of 2; part 2 = seed carries the LNbits server pubkey). Co-Authored-By: Claude Opus 4.8 --- apps/machine/src/services/lightning.ts | 31 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/machine/src/services/lightning.ts b/apps/machine/src/services/lightning.ts index 5b60b2f..f673b24 100644 --- a/apps/machine/src/services/lightning.ts +++ b/apps/machine/src/services/lightning.ts @@ -398,9 +398,22 @@ export async function initializeLightningServices(options?: { console.log('[Lightning] Relay URL:', CONFIG.relayUrl) console.log('[Lightning] LNbits server pubkey:', CONFIG.lnbitsServerPubkey || '(not configured)') - // Strict mode: validate config is production-ready (no localhost). The - // signing-identity check (a bunker pairing must exist) is enforced by - // resolveSigner below via allowEphemeral=false. + // Resolve the signing identity BEFORE validating the LNbits transport + // config. An unpaired machine must reach the QR-pairing wizard regardless + // of relay/server-pubkey provisioning — pairing is what provides those — so + // resolveSigner (which throws NoPairingError → 'unpaired' → wizard for a + // machine with no seed and no binding) has to run ahead of the config + // checks below. The relay/pubkey validation then only gates a *paired* + // machine that's actually trying to talk to LNbits. See aiolabs/bitspire#70. + // + // In production this is a BunkerSigner over NIP-46 (the ATM holds only a + // transport key; the operator's nsecbunkerd holds the signing key); in dev + // it falls back to an in-process LocalSigner. The Phase-A Signer seam means + // nothing downstream changes. See aiolabs/bitspire#52. + const signer: Signer = await resolveSigner({ allowEphemeral: !options?.strict }) + console.log('[Lightning] ATM pubkey:', signer.pubkey) + + // Strict mode: validate config is production-ready (no localhost). if (options?.strict) { const errors: string[] = [] if (/localhost|127\.0\.0\.1/.test(CONFIG.relayUrl)) { @@ -414,7 +427,9 @@ export async function initializeLightningServices(options?: { } } - // Validate required configuration + // Validate required configuration. Reached only for a paired machine (an + // unpaired one threw NoPairingError above) — it needs the LNbits server + // pubkey to talk to the transport. if (!CONFIG.lnbitsServerPubkey) { throw new Error( '[Lightning] VITE_LNBITS_SERVER_PUBKEY is required. ' + @@ -422,14 +437,6 @@ export async function initializeLightningServices(options?: { ) } - // Resolve the signing identity. In production this is a BunkerSigner over - // NIP-46 (the ATM holds only a transport key; the operator's nsecbunkerd - // holds the signing key); in dev it falls back to an in-process LocalSigner. - // The Phase-A Signer seam means nothing downstream changes. See - // aiolabs/bitspire#52. - const signer: Signer = await resolveSigner({ allowEphemeral: !options?.strict }) - console.log('[Lightning] ATM pubkey:', signer.pubkey) - // Create Nostr client const nostrClient = new NostrClient({ relays: [{ url: CONFIG.relayUrl }],