diff --git a/apps/machine/src/services/lightning.ts b/apps/machine/src/services/lightning.ts index 92c6af5..82369f3 100644 --- a/apps/machine/src/services/lightning.ts +++ b/apps/machine/src/services/lightning.ts @@ -1005,8 +1005,18 @@ function createATMServices( return } if (cancelled) return + // walletId omitted: payment_hash is the natural primary key for + // "wait for THIS invoice to settle." Under path B + // (NOSTR_TRANSPORT_ROSTER_REQUIRED=true) lnbits routes the payment + // to the operator's wallet, so a subscription scoped to the ATM's + // pre-override wallet_id would AND-filter the settlement out and + // never fire. With wallet_id omitted, lnbits resolves the wallet + // from get_standalone_payment(payment_hash) and ownership-checks + // against the auth'd account โ€” works on both pre/post-override + // wallets. Coordination log 2026-05-31T18:50Z (lnbits) for the + // confirmation, ยง18:35Z for the joint smoke that surfaced the bug. subId = await lnbits.subscribePayments( - lnbitsWalletId, + undefined, { payment_hash: paymentHash, max_seconds: 600 }, (push) => { if (push.payment_hash !== paymentHash) return @@ -1022,7 +1032,8 @@ function createATMServices( return () => { cancelled = true if (subId) { - void lnbits.unsubscribe(lnbitsWalletId, subId).catch(() => {}) + // wallet_id omitted to match the subscribePayments call above. + void lnbits.unsubscribe(undefined, subId).catch(() => {}) } } }, diff --git a/packages/lnbits/src/client.ts b/packages/lnbits/src/client.ts index 0a18c44..b30f469 100644 --- a/packages/lnbits/src/client.ts +++ b/packages/lnbits/src/client.ts @@ -284,21 +284,30 @@ export class LnbitsClient { * shape so services/lightning.ts can swap mechanically. */ async watchInvoice( - walletId: string, + _walletId: string | undefined, paymentHash: string, timeoutMs = 5 * 60_000, ): Promise { + // wallet_id intentionally NOT propagated to subscribe_payments โ€” + // payment_hash is the natural primary key for "wait for THIS invoice." + // Under path B (NOSTR_TRANSPORT_ROSTER_REQUIRED=true) lnbits routes + // settlements to the operator's wallet, so a subscription pinned to + // the caller's pre-override wallet_id would AND-filter the settlement + // out and never fire. With wallet_id omitted, lnbits resolves the + // wallet from get_standalone_payment(payment_hash) and ownership- + // checks against the auth'd account โ€” works pre/post-override. + // Coordination log 2026-05-31T18:50Z (lnbits) for confirmation. return new Promise((resolve, reject) => { let resolved = false const subId = this.subscribePayments( - walletId, + undefined, { payment_hash: paymentHash, max_seconds: Math.ceil(timeoutMs / 1000) }, (payment) => { if (payment.payment_hash !== paymentHash) return if (payment.status !== 'success') return if (resolved) return resolved = true - void this.unsubscribe(walletId, subIdRef).catch(() => {}) + void this.unsubscribe(undefined, subIdRef).catch(() => {}) resolve(payment) }, (reason) => {