diff options
Diffstat (limited to 'src/features/ark.js')
| -rw-r--r-- | src/features/ark.js | 32 |
1 files changed, 27 insertions, 5 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index d6d6e6d..8026ed9 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -1101,6 +1101,7 @@ export function arkFeature(ctx) { // (and fully refunded) attempt re-tries with one extra sat of routing // budget before giving up — askrene fees are exact, liquidity isn't. let tries = 0; + if (p.meta && p.meta.pk) noteZap('inv:' + p.invoice, p.meta.pk); const attempt = async (routingFeeSat) => { const id = await mgr.payLnInvoice(p.invoice, { amountSat: sats, routingFeeSat }); const a = mgr.lnAction(id); @@ -1111,7 +1112,6 @@ export function arkFeature(ctx) { if (a.step === 'done') { ui.arkLnPaid = { amountSat: a.amountSat, meta: p.meta, autopaid: !!(p._autopaid || p.autopaying) }; ui.send = blankSend(); // the next send starts empty, however this card is left - if (p.meta && p.meta.pk) noteZap('inv:' + p.invoice, p.meta.pk); // the user opted this recipient into auto-pay: remember the chosen // budget so future fixed charges from them clear without a tap if (p.setBudget && p.meta && p.meta.budgetKey && ctx.setLnBudget) @@ -1351,7 +1351,7 @@ export function arkFeature(ctx) { g.claimed ? t('giftClaimedTag') : g.revoked ? t('giftRevokedTag') : t('giftUnclaimedTag'))); })(), (() => { const pk = zapNoteFor(m); return pk ? ctx.hook('profileChip', pk) : null; })(), - m.status !== 'complete' && !failed ? h('span', { class: 'tag pending' }, m.status) : null), + m.status !== 'complete' && !failed ? h('span', { class: 'tag pending' }, m.status === 'pending' ? t('arkExitPendingTag') : m.status) : null), h('div', { class: 'small faint' }, timeAgo(m.ts / 1000))), h('div', { style: 'text-align:right' }, // in fiat this is what the payment was worth when it was made @@ -1360,6 +1360,14 @@ export function arkFeature(ctx) { } const exitStartedTs = (a) => Number((a.id || '').split('-')[1]) || Date.now(); + // A Lightning payment (a zap, mostly) still settling: the money has left + // the balance, so history shows it now as the Sent row it will become. + const lnPendingMove = (a) => ({ + id: 'lnpay:' + a.id, type: 'ln-send', status: 'pending', ts: exitStartedTs(a), + amountSat: a.amountSat, invoice: a.invoice, paymentHash: a.paymentHash, + detail: a.feeSat ? `fee ${a.feeSat} sat` : undefined, + inputIds: (a.parts || []).map((p) => p.inputId).concat(a.inputId ? [a.inputId] : []).filter(Boolean), + }); // Only link a tx that is KNOWN to be out there: the claim, a confirmed // hop, or the finished chain. Guessing from lastError made the link // flicker in time with the retry loop. @@ -1473,7 +1481,7 @@ export function arkFeature(ctx) { : ONCHAIN_ARK.has(m.type) ? h('span', { html: BITCOIN_ICON(18) }) : h('span', { html: ARK_ICON(18) }), h('h3', { style: 'margin:0' }, label), - m.status !== 'complete' && !failed ? h('span', { class: 'tag pending' }, m.status) : null), + m.status !== 'complete' && !failed ? h('span', { class: 'tag pending' }, m.status === 'pending' ? t('arkExitPendingTag') : m.status) : null), m.type === 'refresh' ? h('div', { class: m.feeSat > 0 ? 'amount-neg' : 'small faint', style: 'font-size:20px' }, m.feeSat > 0 ? '-' + fmtAmount(m.feeSat) + ' ' + unitLabel() : t('arkDepthFree')) @@ -3671,7 +3679,10 @@ export function arkFeature(ctx) { }, startLnPay(invoice, meta) { return startArkLnPay(invoice, meta); }, // ---- headless seam for the NWC wallet service ---- - arkPayInvoice(invoice, opts) { return payInvoiceHeadless(invoice, opts); }, + arkPayInvoice(invoice, opts = {}) { + if (opts.pk) noteZap('inv:' + invoice, opts.pk); // a one-tap zap's face on its history row + return payInvoiceHeadless(invoice, opts); + }, // The settled outcome of a payment another device of this wallet // started (the ASP refuses a second HTLC for the same invoice). async arkLnOutcome(paymentHash, timeoutMs) { @@ -3858,6 +3869,11 @@ export function arkFeature(ctx) { const renewals = (s.actions || []) .filter((a) => a.type === 'refresh' && a.manual && !['done', 'failed'].includes(a.step)) .map((a) => ({ time: exitStartedTs(a), render: () => arkRefreshActionItem(a) })); + // ...and Lightning payments on their way, until their Sent row exists + const paid = new Set((s.movements || []).filter((m) => m.type === 'ln-send' && m.status === 'complete').map((m) => m.paymentHash)); + const lnPending = (s.actions || []) + .filter((a) => a.type === 'ln-pay' && !['done', 'failed'].includes(a.step) && !paid.has(a.paymentHash)) + .map((a) => { const m = lnPendingMove(a); return { time: m.ts, render: () => arkHistoryItem(m) }; }); // Same-payment duplicates (the old settle race, or a snapshot adopted // whole before the merge learned to dedupe) fold at display time too — // the earliest telling wins, everywhere, however the state got here. @@ -3908,7 +3924,7 @@ export function arkFeature(ctx) { out.push({ ...m, count: 1, vtxoIds: m.vtxoId ? [m.vtxoId] : [] }); } } - return exits.concat(renewals, out.map((m) => ({ time: m.ts, render: () => arkHistoryItem(m) }))); + return exits.concat(renewals, lnPending, out.map((m) => ({ time: m.ts, render: () => arkHistoryItem(m) }))); }, historyDetail() { if (ui.arkExitDetail) { @@ -3921,6 +3937,12 @@ export function arkFeature(ctx) { if (ui.arkReconDetail && typeof ui.arkReconDetail === 'object') return arkReconDetailView(ui.arkReconDetail); if (!ui.arkMoveDetail) return null; const s = arkStateNow(); + if (s && String(ui.arkMoveDetail).startsWith('lnpay:')) { + const a = (s.actions || []).find((x) => 'lnpay:' + x.id === ui.arkMoveDetail); + const done = a && (s.movements || []).find((x) => x.type === 'ln-send' && x.paymentHash === a.paymentHash); + if (done) return arkMoveDetailView(done); + if (a && !['done', 'failed'].includes(a.step)) return arkMoveDetailView(lnPendingMove(a)); + } const m = s ? (s.movements || []).find((x) => x.id === ui.arkMoveDetail) : null; if (m) return arkMoveDetailView(m); ui.arkMoveDetail = null; |
