Skip to content

Immediate Failure Propagation in LightningService.waitForReceive() #298

@sadeeqrabiu

Description

@sadeeqrabiu

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:

  1. Poor UX: The promise hangs indefinitely until the timeoutMs threshold (default: 15 seconds) is exceeded.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions