chore(machine): fund-atm resumes from binding; VITE_SPIRE_SEED docs/env

fund-atm resolves its signer by resuming the bunker binding from state.db
(the connect token is already spent by the main app, so it can't re-pair);
falls back to a dev nsec via VITE_ATM_PRIVATE_KEY. better-sqlite3 marked
external in the esbuild bundle. .env.example + CLAUDE.md document
VITE_SPIRE_SEED as the prod identity, VITE_ATM_PRIVATE_KEY as dev-only.

(fund-atm is slated for deprecation in favour of the operator funding the
wallet directly via the LNbits UI — kept working for now.)

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-19 00:15:59 +02:00
commit 0391dbaeb0
4 changed files with 47 additions and 13 deletions

View file

@ -13,8 +13,15 @@
*/
import { readFileSync } from 'node:fs'
import { NostrClient, LocalSigner, loadIdentityFromHex } from '@bitSpire/nostr-client'
import {
NostrClient,
LocalSigner,
loadIdentityFromHex,
resumeFromBinding,
type Signer,
} from '@bitSpire/nostr-client'
import { LnbitsClient } from '@bitSpire/lnbits'
import { initDatabase, getBunkerBinding } from './state-store.js'
// @ts-ignore — qrcode is a transitive dep (via qrcode.vue), no types needed
import QRCode from 'qrcode'
@ -56,15 +63,34 @@ async function main() {
const lnbitsServerPubkey = env['VITE_LNBITS_SERVER_PUBKEY']
const atmPrivateKey = env['VITE_ATM_PRIVATE_KEY']
if (!relayUrl || !lnbitsServerPubkey || !atmPrivateKey) {
if (!relayUrl || !lnbitsServerPubkey) {
console.error('Missing required config in', envPath)
console.error('Need: VITE_RELAY_URL, VITE_LNBITS_SERVER_PUBKEY, VITE_ATM_PRIVATE_KEY')
console.error('Need: VITE_RELAY_URL, VITE_LNBITS_SERVER_PUBKEY')
process.exit(1)
}
console.error(`Generating invoice for ${amountSats} sats...`)
const signer = new LocalSigner(loadIdentityFromHex(atmPrivateKey))
// Resolve the signer. Prod: resume the bunker binding from state.db (the
// ATM's transport key — the connect token was already redeemed by the main
// app, so we can't re-pair here). Dev: a local nsec via VITE_ATM_PRIVATE_KEY.
let signer: Signer
if (atmPrivateKey) {
signer = new LocalSigner(loadIdentityFromHex(atmPrivateKey))
} else {
initDatabase()
const binding = getBunkerBinding()
if (!binding) {
console.error('ATM is not paired (no bunker binding in state.db) and no')
console.error('VITE_ATM_PRIVATE_KEY set. Pair the ATM via the main app first.')
process.exit(1)
}
signer = await resumeFromBinding({
clientSecretHex: binding.clientSecretHex,
spirePubkey: binding.spirePubkey,
bunkerUrl: binding.bunkerUrl,
})
}
const nostrClient = new NostrClient({
relays: [{ url: relayUrl }],