Located in LightningService.ts:
async waitForReceive(operationId: string, timeoutMs: number = 15000) {
return new Promise<LnReceiveState>((resolve, reject) => {
let unsubscribe: () => void
const timeoutId = setTimeout(() => {
reject(new Error('Timeout waiting for receive'))
}, timeoutMs)
unsubscribe = this.subscribeLnReceive(
operationId,
(res) => {
if (res === 'claimed') { // Only resolves on 'claimed'
clearTimeout(timeoutId)
unsubscribe()
resolve(res)
}
},
(error) => {
clearTimeout(timeoutId)
unsubscribe()
reject(error)
},
)
})
}
Why This Is a Bug
The promise inside waitForReceive only resolves if the state strictly equals 'claimed'.
However, as defined in packages/core/src/types/wallet.ts, the LnReceiveState type union can also emit a { canceled: { reason: string } } object state on failure:
export type LnReceiveState =
| 'created'
| { waiting_for_payment: { invoice: string; timeout: number } }
| { canceled: { reason: string } } // Emitted on failure
| 'funded'
| 'awaiting_funds'
| 'claimed'
When a canceled event is emitted via the subscribe_ln_receive RPC stream, the success callback ignores it completely since it is not 'claimed'. As a result:
- Poor UX: The promise hangs indefinitely until the
timeoutMs threshold (default: 15 seconds) is exceeded.
- Obscured Diagnostics: Instead of seeing the actual error reason from the gateway driver (e.g.
"invoice expired"), the client is presented with a generic "Timeout waiting for receive" error.
Expected Behavior
When the subscribeLnReceive stream emits a { canceled: { reason: string } } state, the subscription should unsubscribe immediately and reject the promise with the specific cancellation reason.
Located in LightningService.ts:
Why This Is a Bug
The promise inside
waitForReceiveonly resolves if the state strictly equals'claimed'.However, as defined in
packages/core/src/types/wallet.ts, theLnReceiveStatetype union can also emit a{ canceled: { reason: string } }object state on failure:When a
canceledevent is emitted via thesubscribe_ln_receiveRPC stream, the success callback ignores it completely since it is not'claimed'. As a result:timeoutMsthreshold (default: 15 seconds) is exceeded."invoice expired"), the client is presented with a generic"Timeout waiting for receive"error.Expected Behavior
When the
subscribeLnReceivestream emits a{ canceled: { reason: string } }state, the subscription should unsubscribe immediately and reject the promise with the specific cancellation reason.