fix(machine): resolve signer before LNbits config so an unpaired machine reaches the pairing wizard

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 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-30 12:06:34 +02:00
commit 334cb86771

View file

@ -398,9 +398,22 @@ export async function initializeLightningServices(options?: {
console.log('[Lightning] Relay URL:', CONFIG.relayUrl) console.log('[Lightning] Relay URL:', CONFIG.relayUrl)
console.log('[Lightning] LNbits server pubkey:', CONFIG.lnbitsServerPubkey || '(not configured)') console.log('[Lightning] LNbits server pubkey:', CONFIG.lnbitsServerPubkey || '(not configured)')
// Strict mode: validate config is production-ready (no localhost). The // Resolve the signing identity BEFORE validating the LNbits transport
// signing-identity check (a bunker pairing must exist) is enforced by // config. An unpaired machine must reach the QR-pairing wizard regardless
// resolveSigner below via allowEphemeral=false. // 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) { if (options?.strict) {
const errors: string[] = [] const errors: string[] = []
if (/localhost|127\.0\.0\.1/.test(CONFIG.relayUrl)) { 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) { if (!CONFIG.lnbitsServerPubkey) {
throw new Error( throw new Error(
'[Lightning] VITE_LNBITS_SERVER_PUBKEY is required. ' + '[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 // Create Nostr client
const nostrClient = new NostrClient({ const nostrClient = new NostrClient({
relays: [{ url: CONFIG.relayUrl }], relays: [{ url: CONFIG.relayUrl }],