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
+11 -2
View File
@@ -547,6 +547,7 @@ const showScanModal = ref(false); const showSendModal = ref(false); const showRe
async function devFaucet() { try { await rpcClient.call({ method: 'dev.faucet', params: { amount_sats: 1_000_000 } }); await loadWeb5Status() } catch { /* ignore */ } }
const walletConnected = ref(false); const walletOnchain = ref(0); const walletLightning = ref(0); const walletEcash = ref(0); const walletFedimint = ref(0)
let walletInfoFailures = 0
const walletArk = ref(0)
const walletTransactions = ref<WalletTransaction[]>([])
@@ -630,8 +631,16 @@ async function loadWeb5Status() {
// call, which is what makes the card feel like an app launch.
const balances = Promise.allSettled([
rpcClient.call<{ balance_sats: number; channel_balance_sats: number }>({ method: 'lnd.getinfo', timeout: 5000 })
.then(res => { walletOnchain.value = res.balance_sats || 0; walletLightning.value = res.channel_balance_sats || 0; walletConnected.value = true })
.catch(() => { walletConnected.value = false }),
.then(res => { walletOnchain.value = res.balance_sats || 0; walletLightning.value = res.channel_balance_sats || 0; walletConnected.value = true; walletInfoFailures = 0 })
.catch(() => {
// A single slow poll must NOT flip the card to "disconnected" and
// hide balances the user already knows — busy nodes routinely blow
// the 5s budget mid-payment or during IO storms (framework-pt user
// report: balances vanished while a payment settled). Only call it
// disconnected after three consecutive failures (~30s of silence).
walletInfoFailures += 1
if (walletInfoFailures >= 3) walletConnected.value = false
}),
rpcClient.call<{ balance_sats: number }>({ method: 'wallet.ecash-balance', timeout: 5000 })
.then(res => { walletEcash.value = res.balance_sats ?? 0 }).catch(() => { /* keep last-known */ }),
rpcClient.call<{ balance_sats: number }>({ method: 'wallet.fedimint-balance', timeout: 5000 })