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:
parent
82a9e79d0e
commit
0391dbaeb0
4 changed files with 47 additions and 13 deletions
|
|
@ -36,16 +36,23 @@ VITE_LNBITS_SERVER_PUBKEY=
|
|||
# aiolabs/withdraw#1 / commit e9d911e.)
|
||||
|
||||
# =============================================================================
|
||||
# ATM Identity
|
||||
# ATM Identity — spire pairing seed (NIP-46 bunker; aiolabs/bitspire#52)
|
||||
# =============================================================================
|
||||
|
||||
# The spire pairing seed produced by the operator dashboard (spirekeeper):
|
||||
# spire-seed:v1:<base64url>
|
||||
# It carries a one-shot NIP-46 connect token + the spire's signing pubkey +
|
||||
# the bunker URL. On first boot the ATM redeems the token, generates its own
|
||||
# transport key, and persists the binding to state.db; thereafter it resumes
|
||||
# from the binding (the seed can stay set — it's matched by fingerprint).
|
||||
# A changed seed re-pairs (and re-publishes the cassette-state hello).
|
||||
VITE_SPIRE_SEED=
|
||||
|
||||
# pragma: allowlist secret
|
||||
# ATM's Nostr private key (hex format, 64 characters). This signing
|
||||
# key IS the credential — LNbits derives the account from it on first
|
||||
# contact (issue aiolabs/lnbits#9 alignment).
|
||||
# DEV ONLY fallback — a raw Nostr private key (hex, 64 chars) for running
|
||||
# without a bunker. Ignored when VITE_SPIRE_SEED or a stored binding exists.
|
||||
# Generate with: openssl rand -hex 32
|
||||
# If not set, generates ephemeral identity on each restart (dev only).
|
||||
VITE_ATM_PRIVATE_KEY=
|
||||
# VITE_ATM_PRIVATE_KEY=
|
||||
|
||||
# =============================================================================
|
||||
# Operator Identity
|
||||
|
|
|
|||
|
|
@ -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 }],
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"dev": "concurrently -n vite,electron \"vite\" \"pnpm run electron:dev\"",
|
||||
"dev:vite": "vite",
|
||||
"electron:dev": "tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && electron dist-electron/main.js",
|
||||
"build": "vue-tsc --noEmit && vite build && tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && npx esbuild electron/fund-atm.ts --bundle --platform=node --format=cjs --outfile=dist-electron/fund-atm.bundle.cjs",
|
||||
"build": "vue-tsc --noEmit && vite build && tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && npx esbuild electron/fund-atm.ts --bundle --platform=node --format=cjs --external:better-sqlite3 --outfile=dist-electron/fund-atm.bundle.cjs",
|
||||
"build:electron": "pnpm build && electron-builder",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue