Merge pull request 'feat(machine): consume get_machine_config over the transport (#70 P1 client)' (#74) from feat/machine-config-consumer into dev

Reviewed-on: #74
This commit is contained in:
padreug 2026-07-02 21:54:33 +00:00
commit 86b35b5415
3 changed files with 79 additions and 0 deletions

View file

@ -505,6 +505,46 @@ export async function initializeLightningServices(options?: {
}
console.log('[Lightning] LNbits wallet:', lnbitsWalletId)
// #70 P1: pull operator pubkey + fee config from LNbits over the authenticated
// transport (spirekeeper#41 `get_machine_config`). A seed-only machine has no
// VITE_OPERATOR_PUBKEYS, so without this it can't trust its fee config and sits
// at "awaiting configuration". Only for the seed-only case — an explicit
// VITE_OPERATOR_PUBKEYS override keeps the env/kind-30078 path untouched.
// Soft-fail: an older spirekeeper (no RPC) or a transport error falls back to
// whatever the operator services can pull from kind-30078.
if (CONFIG.operatorPubkeys.length === 0) {
try {
const mc = await lnbits.getMachineConfig()
if (mc.operator_pubkey) {
CONFIG.operatorPubkeys = [mc.operator_pubkey]
console.log('[Lightning] Operator pubkey(s):', mc.operator_pubkey, '(server-delivered, #70 P1)')
}
if (mc.fee_config && isElectron && window.electronAPI) {
// Persist the server-delivered fee config so atm.ts's awaiting-fees gate
// (getFeeConfig) clears immediately — robust to the replaceable kind-30078
// event not being fetchable from the relay. The live kind-30078
// subscription still handles mid-run fee updates.
const applied = await window.electronAPI.applyFeeConfig(
{
cashInFeeFraction: mc.fee_config.cash_in_fee_fraction,
cashOutFeeFraction: mc.fee_config.cash_out_fee_fraction,
schemaVersion: mc.fee_config.schema_version,
},
mc.created_at,
)
console.log(
'[Lightning] Server-delivered fee config:',
applied.applied ? 'applied' : `skipped (${applied.reason})`,
)
}
} catch (e) {
console.warn(
'[Lightning] get_machine_config unavailable; falling back to env/kind-30078 for operator config:',
(e as Error).message,
)
}
}
// CLINK client — kept in tree but not actively wired into LNbits flows.
// operatorPubkey is the operator allowlist for kind-21003 management
// commands; it has no Lightning.Pub dependency.

View file

@ -37,6 +37,7 @@ import type {
CreateInvoiceBody,
PayInvoiceBody,
WalletInfo,
MachineConfigResponse,
SubscribePaymentsBody,
SubscribeAck,
PaymentPushCallback,
@ -210,6 +211,17 @@ export class LnbitsClient {
return data ?? []
}
/** Pull server-delivered machine config (operator pubkey + fee config) over
* the authenticated transport — spirekeeper's `get_machine_config` RPC
* (bitspire#70 P1). Lets a seed-only ATM configure itself with no per-machine
* env provisioning. Rejects (LnbitsRpcError) if the server hasn't registered
* the RPC (older spirekeeper) — callers should soft-fall-back. */
async getMachineConfig(): Promise<MachineConfigResponse> {
return this.idempotent(() =>
this.sendRpc<MachineConfigResponse>('get_machine_config', {}),
)
}
// ============================================================================
// Invoices
// ============================================================================

View file

@ -274,3 +274,30 @@ export type PaymentPushCallback = (payment: LnbitsPayment) => void
/** Called when the subscription has been closed (by TTL or explicit unsubscribe). */
export type SubscriptionCloseCallback = (reason: 'ttl' | 'unsubscribed') => void
// ============================================================================
// get_machine_config RPC (spirekeeper#41 / bitspire#70 P1)
// ============================================================================
/** Fee-config wire shape inside `get_machine_config` — mirrors spirekeeper's
* `FeeConfigPayload.to_wire_dict()` (snake_case). */
export interface FeeConfigWire {
schema_version: number
cash_in_fee_fraction: number
cash_out_fee_fraction: number
components?: Record<string, number>
}
/** Response of the `get_machine_config` RPC: the operator pubkey + fee config
* (+ fiat, ids) LNbits delivers to a paired ATM over the authenticated
* transport, so a seed-only machine needs no per-machine env provisioning.
* `fee_config` is null until the operator has a super-config. */
export interface MachineConfigResponse {
operator_pubkey: string
fee_config: FeeConfigWire | null
fiat_code: string
machine_npub: string
wallet_id: string
/** Freshness watermark (unix s) for the consumer's fee-config replay guard. */
created_at: number
}