refactor(machine): wire LnbitsClient alongside LightningPubClient

3b.1 of the LP→LNbits migration: structurally introduce LnbitsClient
into services/lightning.ts without changing any runtime behavior.
All existing call sites still go through LightningPubClient.

  apps/machine/src/services/lightning.ts
    - import LnbitsClient from @bitSpire/lnbits
    - add `lnbitsServerPubkey` to LightningConfig
    - load it from runtime IPC config + VITE_LNBITS_SERVER_PUBKEY
      env var (env wiring proper happens in 3c)
    - module-level `_lnbitsRef: LnbitsClient | null`
    - in initializeLightningServices, instantiate LnbitsClient
      ONLY IF `CONFIG.lnbitsServerPubkey` is set (graceful no-op
      while the env hasn't been wired yet)
    - export `_getLnbitsClient()` for 3b.2+ call sites

  apps/machine/package.json
    - add `@bitSpire/lnbits: workspace:*` dependency

Verified: pnpm typecheck clean (14/14 turbo tasks, machine task
now executes since lnbits is a new dep).

Next: 3b.2 — drop the CLINK/ndebit cash-in flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-13 12:56:01 +02:00
commit 7d0c19ed7f
3 changed files with 47 additions and 0 deletions

View file

@ -24,6 +24,7 @@
"@bitSpire/clink": "workspace:*", "@bitSpire/clink": "workspace:*",
"@bitSpire/hal": "workspace:*", "@bitSpire/hal": "workspace:*",
"@bitSpire/lightning": "workspace:*", "@bitSpire/lightning": "workspace:*",
"@bitSpire/lnbits": "workspace:*",
"@bitSpire/nostr-client": "workspace:*", "@bitSpire/nostr-client": "workspace:*",
"@bitSpire/state-machine": "workspace:*", "@bitSpire/state-machine": "workspace:*",
"@scure/base": "^1.2.0", "@scure/base": "^1.2.0",

View file

@ -24,6 +24,7 @@ import {
} from '@bitSpire/nostr-client' } from '@bitSpire/nostr-client'
import { verifyEvent } from 'nostr-tools' import { verifyEvent } from 'nostr-tools'
import { LightningPubClient } from '@bitSpire/lightning' import { LightningPubClient } from '@bitSpire/lightning'
import { LnbitsClient } from '@bitSpire/lnbits'
import { import {
CLINKClient, CLINKClient,
createOfferSuccess, createOfferSuccess,
@ -63,6 +64,13 @@ interface LightningConfig {
atmPrivateKey: string atmPrivateKey: string
appId: string appId: string
operatorPubkeys: string[] operatorPubkeys: string[]
/**
* LNbits nostr-transport server pubkey. Optional during the LP→LNbits
* migration: when empty, the LnbitsClient is not instantiated and the
* file behaves identically to its LP-only past. Once 3c wires the env
* var, this is required.
*/
lnbitsServerPubkey: string
} }
/** /**
@ -83,6 +91,7 @@ async function loadLightningConfig(): Promise<LightningConfig> {
atmPrivateKey: '', atmPrivateKey: '',
appId: '30270e761f2e30b1737f34ce661df45f521352b408b8ed18fcc09f3f0dec5097', // ATM app ID — regenerated for bitSpire so stale LP server-side associations don't accidentally rehydrate appId: '30270e761f2e30b1737f34ce661df45f521352b408b8ed18fcc09f3f0dec5097', // ATM app ID — regenerated for bitSpire so stale LP server-side associations don't accidentally rehydrate
operatorPubkeys: [], operatorPubkeys: [],
lnbitsServerPubkey: '',
} }
// In Electron, get runtime config from main process // In Electron, get runtime config from main process
@ -105,6 +114,9 @@ async function loadLightningConfig(): Promise<LightningConfig> {
.map((k: string) => k.trim()) .map((k: string) => k.trim())
.filter(Boolean) .filter(Boolean)
: defaults.operatorPubkeys, : defaults.operatorPubkeys,
lnbitsServerPubkey:
(runtimeConfig as { lnbitsServerPubkey?: string }).lnbitsServerPubkey ||
defaults.lnbitsServerPubkey,
} }
} catch (e) { } catch (e) {
console.warn('[Lightning] Failed to get runtime config from Electron:', e) console.warn('[Lightning] Failed to get runtime config from Electron:', e)
@ -120,6 +132,9 @@ async function loadLightningConfig(): Promise<LightningConfig> {
adminToken: import.meta.env.VITE_ADMIN_TOKEN || defaults.adminToken, adminToken: import.meta.env.VITE_ADMIN_TOKEN || defaults.adminToken,
atmPrivateKey: import.meta.env.VITE_ATM_PRIVATE_KEY || defaults.atmPrivateKey, atmPrivateKey: import.meta.env.VITE_ATM_PRIVATE_KEY || defaults.atmPrivateKey,
appId: import.meta.env.VITE_APP_ID || defaults.appId, appId: import.meta.env.VITE_APP_ID || defaults.appId,
lnbitsServerPubkey:
(import.meta.env.VITE_LNBITS_SERVER_PUBKEY as string | undefined) ||
defaults.lnbitsServerPubkey,
operatorPubkeys: import.meta.env.VITE_OPERATOR_PUBKEYS operatorPubkeys: import.meta.env.VITE_OPERATOR_PUBKEYS
? (import.meta.env.VITE_OPERATOR_PUBKEYS as string) ? (import.meta.env.VITE_OPERATOR_PUBKEYS as string)
.split(',') .split(',')
@ -369,6 +384,17 @@ function expireLnurlSession(uniqueHash: string, lightningPub: LightningPubClient
/** Clean up all active LNURL sessions. Called when state machine returns to idle. */ /** Clean up all active LNURL sessions. Called when state machine returns to idle. */
let _lightningPubRef: LightningPubClient | null = null let _lightningPubRef: LightningPubClient | null = null
/** LNbits client reference, set during initializeLightningServices if
* VITE_LNBITS_SERVER_PUBKEY is configured. Parallel to _lightningPubRef
* during the LP→LNbits migration; 3b.2+ swap call sites over. */
let _lnbitsRef: LnbitsClient | null = null
/** Internal accessor — used by 3b.2+ call sites that need to invoke
* the LNbits client from outside `createATMServices`. Returns null
* when the LNbits server pubkey isn't configured. */
export function _getLnbitsClient(): LnbitsClient | null {
return _lnbitsRef
}
function cleanupAllLnurlSessions(): void { function cleanupAllLnurlSessions(): void {
if (!_lightningPubRef) return if (!_lightningPubRef) return
@ -930,6 +956,23 @@ export async function initializeLightningServices(options?: {
_lightningPubRef = lightningPub _lightningPubRef = lightningPub
console.log('[Lightning] Lightning.Pub client initialized') console.log('[Lightning] Lightning.Pub client initialized')
// LNbits client — wired in parallel with Lightning.Pub during the
// migration. In 3b.1 it's instantiated but not yet wired into any
// ATMServices method; 3b.2-3b.4 will gradually move call sites over.
// When VITE_LNBITS_SERVER_PUBKEY isn't set we skip the init so the
// file remains LP-only at runtime.
if (CONFIG.lnbitsServerPubkey) {
const lnbits = new LnbitsClient({
serverPubkey: CONFIG.lnbitsServerPubkey,
relays: [CONFIG.relayUrl],
})
lnbits.initialize(nostrClient, identity)
_lnbitsRef = lnbits
console.log('[Lightning] LNbits client initialized (parallel to LP)')
} else {
console.log('[Lightning] VITE_LNBITS_SERVER_PUBKEY not set — LNbits client disabled')
}
// Create CLINK client // Create CLINK client
const clink = new CLINKClient({ const clink = new CLINKClient({
nostrClient, nostrClient,

3
pnpm-lock.yaml generated
View file

@ -32,6 +32,9 @@ importers:
'@bitSpire/lightning': '@bitSpire/lightning':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/lightning version: link:../../packages/lightning
'@bitSpire/lnbits':
specifier: workspace:*
version: link:../../packages/lnbits
'@bitSpire/nostr-client': '@bitSpire/nostr-client':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/nostr-client version: link:../../packages/nostr-client