aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-24 23:48:53 -0700
committerAdam Soltys <[email protected]>2026-06-24 23:48:53 -0700
commitd4db7d545d51cde2f5a98426c1839e0fe0745efa (patch)
tree455ddf9c4d1ffff97d184ab3d19315d2f2728d6c /src/app.js
parentfaceb2eef8abe14302b057d835f0449bcd06bc59 (diff)
downloadcoinosv3-d4db7d545d51cde2f5a98426c1839e0fe0745efa.tar.gz
coinosv3-d4db7d545d51cde2f5a98426c1839e0fe0745efa.zip
Detect stuck low-fee txs and stop actively monitoring them
A pending tx is judged 'stuck' once it has waited ~10 blocks (≈100 min) and its fee rate (fee/vsize) is below the mempool's current floor (minimumFee) — i.e. it's been purged and won't confirm without a bump. _txSummary now records vsize and a firstSeen timestamp (persisted in cache) to support this. Stuck txs are dropped from active monitoring: excluded from the watched-address set and from the fast-poll trigger, so they don't hold a socket slot or keep us in the 6s lane. The slow ~25s reconcile still catches one if the floor later drops and it confirms. History flags them (⚠ stuck) and the detail view explains it — pointing outgoing ones at the existing fee bump. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js10
1 files changed, 8 insertions, 2 deletions
diff --git a/src/app.js b/src/app.js
index 5146414..ed4ca8c 100644
--- a/src/app.js
+++ b/src/app.js
@@ -2372,6 +2372,7 @@ function giftHistoryItem(g) {
function txHistoryItem(tx) {
const incoming = tx.net >= 0;
+ const stuck = !tx.confirmed && wallet.isStuck(tx);
return h(
'div',
{ class: 'item', style: 'cursor:pointer', onClick: () => { ui.txDetail = tx.txid; render(); } },
@@ -2379,9 +2380,11 @@ function txHistoryItem(tx) {
h('div', { class: 'grow' },
h('div', { class: 'row gap6' },
incoming ? t('received') : t('sent'),
- tx.confirmed ? null : h('span', { class: 'tag pending' }, t('pendingTag'))
+ tx.confirmed ? null
+ : stuck ? h('span', { class: 'tag', style: 'background:var(--red-soft);color:var(--red)' }, t('stuckTag'))
+ : h('span', { class: 'tag pending' }, t('pendingTag'))
),
- h('div', { class: 'small faint' }, tx.confirmed ? timeAgo(tx.blockTime) : t('awaitingConfirmation'))
+ h('div', { class: 'small faint' }, tx.confirmed ? timeAgo(tx.blockTime) : stuck ? t('stuckNote') : t('awaitingConfirmation'))
),
h('div', { style: 'text-align:right' },
h('div', { class: incoming ? 'amount-pos' : 'amount-neg' }, (incoming ? '+' : '') + fmtAmount(tx.net)),
@@ -2491,6 +2494,9 @@ function txDetailView(tx) {
tx.confirmed && tx.blockTime ? line(t('date'), new Date(tx.blockTime * 1000).toLocaleString()) : null,
!incoming && tx.fee ? line(t('networkFee'), fmtAmount(tx.fee) + ' ' + unitLabel()) : null
),
+ !tx.confirmed && wallet.isStuck(tx)
+ ? h('div', { class: 'warn-box' }, incoming ? t('stuckIncomingNote') : t('stuckOutgoingNote'))
+ : null,
h('div', { class: 'col gap6' },
h('span', { class: 'lab' }, t('transactionId')),
h('div', { class: 'addr-box', style: 'font-size:13px' }, tx.txid)