fix(machine): subscribe to single-invoice settlement by payment_hash only
Under path B (NOSTR_TRANSPORT_ROSTER_REQUIRED=true), lnbits's roster-lookup override routes create_invoice to the operator's wallet, but the subsequent subscribe_payments was scoping its filter to the ATM's pre-override wallet_id. The dispatcher AND-filters payment_hash + wallet_id, so the settlement on the operator wallet was invisible to the subscription — bitspire stayed in "Watching invoice" forever, dispense never fired. Omit wallet_id on the single-invoice watcher: lnbits already resolves the wallet from get_standalone_payment(payment_hash) and ownership-checks against the auth'd account. Works pre/post- override; payment_hash is the natural primary key for "wait for THIS invoice" anyway. Cash-out subscription site at services/lightning.ts:1008-1010 (production caller) + watchInvoice convenience helper at packages/lnbits/src/client.ts:286-313 both flipped. LNURL-withdraw subscription at services/lightning.ts:720 (filter: tag+link_id) is the symmetric case but pending lnbits confirmation that the tag+link_id branch of _resolve_owner_wallet_id exists alongside the payment_hash branch. Coordination: ~/dev/coordination/log.md 2026-05-31T18:35Z (joint smoke surfaced the bug), 18:40Z (bitspire diagnosis), 18:50Z (lnbits narrowed the fix shape + confirmed path-2 works against deployed lnbits today). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
721c82e487
commit
cb8ad3d813
2 changed files with 25 additions and 5 deletions
|
|
@ -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(() => {})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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<LnbitsPayment> {
|
||||
// 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) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue