feat(lightning): instant pay feedback, balances never vanish mid-payment
Demo images / Build & push demo images (push) Failing after 4m20s

Framework-pt report: a paid invoice stalled the UI with no success
shown, and lightning/total balances disappeared until it settled.
Three compounding causes, three fixes:

- Backend payinvoice's synchronous wait drops 120s → 8s. Fast payments
  (the majority) still settle in one round trip; slow multi-hop routes
  return pending + payment_hash quickly and the caller's 3s poll takes
  over — instead of the modal freezing for up to two minutes.
- payLightningInvoice gains an onPending hook: SendBitcoinModal and the
  scan modal now flip to a visible "Settling…" success pane the moment
  the payment goes pending (safe to close), and the ongoing poll
  upgrades it to Paid — or replaces it with LND's real failure.
- One slow lnd.getinfo poll (5s budget) flipped the Home wallet card to
  "disconnected", hiding balances the user already knew. Three
  consecutive failures are now required (~30s) before the card gives up;
  last-known balances keep rendering throughout.

rpc-client tests 75/75.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-29 10:47:39 -04:00
co-authored by Claude Fable 5
parent b7310ecfaf
commit 8bea3707ca
5 changed files with 59 additions and 12 deletions
+6 -1
View File
@@ -445,7 +445,7 @@ class RPCClient {
async payLightningInvoice(params: {
payment_request: string
amount_sats?: number
}): Promise<{
}, onPending?: (payment_hash: string) => void): Promise<{
status: 'succeeded' | 'failed' | 'pending'
payment_hash: string
amount_sats: number
@@ -470,6 +470,11 @@ class RPCClient {
}
if (!hash) return { status: 'pending', payment_hash: '', amount_sats: amount }
// Let the caller unblock its UI right now ("settling…") — the backend
// answers pending after ~8s, and freezing a modal through the poll loop
// below was the "paid but the interface stalled" report.
try { onPending?.(hash) } catch { /* caller UI must not break polling */ }
// Poll to a terminal state: every 3s for up to 2 minutes.
for (let i = 0; i < 40; i++) {
await new Promise((r) => setTimeout(r, 3000))