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
+21 -2
View File
@@ -621,9 +621,28 @@ async function send() {
} else if (method === 'lightning') {
if (!dest.value.trim()) { error.value = t('web5.pasteInvoice'); return }
// Waits out slow multi-hop routing and only reports failure when LND
// itself declares the payment failed — never on a timeout.
const res = await rpcClient.payLightningInvoice({ payment_request: dest.value.trim() })
// itself declares the payment failed — never on a timeout. The moment
// the payment goes pending (~8s), the success pane appears in
// "settling…" mode instead of freezing the modal through the poll —
// the poll keeps running and upgrades the pane when LND settles.
const res = await rpcClient.payLightningInvoice(
{ payment_request: dest.value.trim() },
(hash) => {
successInfo.value = {
amount: paidAmount,
methodLabel: 'Settling…',
hash,
note: 'Payment is on its way through the network. This pane updates the moment it settles — safe to close.',
}
confirming.value = false
processing.value = false
emit('sent')
},
)
if (res.status === 'failed') {
// If the settling pane is up, replace it with the failure — LND
// declared this payment failed for real.
successInfo.value = null
error.value = res.failure_reason || t('web5.sendFailed')
return
}