From 50237a51cf92fd96130ba4865c1bb1bdd6831baf Mon Sep 17 00:00:00 2001 From: Randall Naar Date: Mon, 14 Sep 2026 11:37:17 -0400 Subject: [PATCH] Added confirmation polling to transaction page. --- README.md | 8 ++ client/src/app.js | 16 ++- client/src/components/status-badge.js | 4 +- client/src/views/tx.js | 19 +-- test/app.test.js | 163 ++++++++++++++++++++++++++ 5 files changed, 200 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b60dd927..78703ece 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,14 @@ transactions also refresh on new blocks rather than on a timer. A newly observed tip resets pending block template polling and delays the next request by 15 seconds so the electrs cache can refresh. +While an unconfirmed transaction page is focused, mempool summary and fee +estimates refresh on the standard cadence to keep its ETA, mempool depth, and +fee analysis current. The transaction confirmation status does not use another +fixed-rate poll: each newly observed tip requests `/tx/:txid/status`. Once the +transaction is confirmed, those unconfirmed-transaction refreshes stop and the +containing block metadata is loaded for the block details shown below the +transaction. + The dashboard requests both `/mempool/recent` and `/mempool` because they serve different UI contracts. `/mempool/recent` supplies the recent transaction list; `/mempool` supplies aggregate backlog fields such as transaction count, virtual diff --git a/client/src/app.js b/client/src/app.js index 326608f4..a6553974 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -405,7 +405,14 @@ export default function main( ).startWith(null).scan((S, mod) => mod(S)) // Single TX - , tx$ = reply('tx').merge(goTx$.mapTo(null)).startWith(null) + , tx$ = O.merge( + reply('tx').map(tx => _ => tx), + reply('tx-status', true).map(r => tx => + tx && tx.txid == r.request.txid + ? { ...tx, status: r.body } + : tx), + goTx$.mapTo(_ => null) + ).startWith(_ => null).scan((tx, update) => update(tx), null) , txBlock$ = reply('tx-block').merge(goTx$.mapTo(null)).startWith(null) // Predecessor metadata for confirmed block interval calculations @@ -665,6 +672,13 @@ export default function main( // fetch single tx (including confirmation status) , goTx$.map(txid => ({ category: 'tx', method: 'GET', path: `/tx/${txid}` })) + // A transaction's confirmation can only change when the chain tip changes. + // Reuse the existing tip poll instead of running another fixed-rate poll. + , !pollingEnabled ? O.empty() : subsequentTipHeight$ + .withLatestFrom(view$, tx$, (_, view, tx) => ({ view, tx })) + .filter(({ view, tx }) => view == 'tx' && tx && tx.status && !tx.status.confirmed && hasFocus()) + .map(({ tx }) => ({ category: 'tx-status', method: 'GET', path: `/tx/${tx.txid}/status`, txid: tx.txid, bg: true })) + // fetch the block containing a confirmed tx , tx$.filter(tx => tx && tx.status && tx.status.confirmed && tx.status.block_hash) .map(tx => ({ category: 'tx-block', method: 'GET', path: `/block/${tx.status.block_hash}` })) diff --git a/client/src/components/status-badge.js b/client/src/components/status-badge.js index f9918a3d..dd37509d 100644 --- a/client/src/components/status-badge.js +++ b/client/src/components/status-badge.js @@ -11,8 +11,8 @@ export const StatusBadge = ( ); -export const StatusDot = () => ( -