diff options
| author | Adam Soltys <[email protected]> | 2026-09-27 23:45:07 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-27 23:45:07 -0700 |
| commit | 360c39400e219b9b4863e3ae6716f4e73b788045 (patch) | |
| tree | d796b01d6f3eaf39be286b9978aa9e4a36f9a497 /src | |
| parent | 8a3af8c8afa9c86bc84f2821faaf6530eeb81a03 (diff) | |
| download | coinosv3-360c39400e219b9b4863e3ae6716f4e73b788045.tar.gz coinosv3-360c39400e219b9b4863e3ae6716f4e73b788045.zip | |
Expired dust gets a quiet line and a Forget button, not a red warning.
A coin past its expiry and under the 330-sat floor can be renewed by
nobody and exited by nobody; the home page said "the server can still
renew it" in red over 21 sats. Now a muted line points at Manage, where
the coin's row offers Forget: held as spent, quietly, so it stops
counting and stops warning. Larger expired coins keep the warning.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
Diffstat (limited to 'src')
| -rw-r--r-- | src/ark/manager.js | 15 | ||||
| -rw-r--r-- | src/features/ark.js | 15 | ||||
| -rw-r--r-- | src/i18n.js | 4 |
3 files changed, 31 insertions, 3 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js index b828d7e..311c793 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -362,6 +362,21 @@ export class ArkManager { return true; } + // Give up a coin that has expired and is too small to renew (under the + // 330-sat output floor) or that the server has refused as expired: it is + // the server's now, in effect. Held as spent, quietly, so it stops + // counting and stops warning. + forgetVtxo(id) { + const v = this._vtxo(id); + if (!v || v.state !== 'spendable') return false; + const tip = this._tipH || 0; + if (!(v.expiryRejected || (tip && v.expiryHeight && v.expiryHeight <= tip))) return false; + v.state = 'spent'; + v.forgotten = true; + this._save(); + return true; + } + balance() { const tip = this._tipH || 0; const sum = (st) => this.state.vtxos diff --git a/src/features/ark.js b/src/features/ark.js index eae6b75..1b40417 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -2328,7 +2328,10 @@ export function arkFeature(ctx) { h('tbody', {}, ...spend.map((v) => { const f = feeNowOf(v); const xf = exitFeeOf(v); - const due = !!(v.expiryHeight && tip && v.expiryHeight - tip <= 0); + const due = !!(v.expiryHeight && tip && v.expiryHeight - tip <= 0) || !!v.expiryRejected; + // expired and under the floor: nothing can renew it, so the + // last column offers to give it up + const lost = due && v.amountSat < 330; const toggle = () => { sel.has(v.id) ? sel.delete(v.id) : sel.add(v.id); render(); }; return h('tr', { class: 'clickable', @@ -2338,7 +2341,9 @@ export function arkFeature(ctx) { h('td', { class: 'num' }, fmtSats(v.amountSat), h('span', { class: 'small faint' }, ' sats')), h('td', { class: 'num' + (due ? ' warn' : '') }, expiresOf(v)), h('td', { class: 'num' }, xf == null ? '—' : fmtSats(xf), xf == null ? null : h('span', { class: 'small faint' }, ' sats')), - h('td', { class: 'num' }, renewChip(f))); + h('td', { class: 'num' }, lost + ? h('button', { class: 'btn-sm', title: t('arkCoinForgetTitle'), onClick: (e) => { e.stopPropagation(); if (mgr.forgetVtxo(v.id)) { sel.delete(v.id); toast(t('arkCoinForgotten')); render(); } } }, t('arkCoinForget')) + : renewChip(f))); }), // the coins in a round: greyed, no checkbox, their column says why ...pendingCoins.map((v) => h('tr', { class: 'coin-renewing', style: 'opacity:.6' }, @@ -3818,9 +3823,13 @@ export function arkFeature(ctx) { if (!wallet.watchOnly && ark && ark.balance) { let expiredSat = 0; try { expiredSat = ark.balance().expiredSat || 0; } catch {} - if (expiredSat > 0) lines.push({ + // Expired dust (under the 330-sat floor) cannot be renewed or exited + // by anyone: no red warning over 21 sats, a quiet line and a Forget + // button on the Manage page instead. + if (expiredSat >= 330) lines.push({ text: t('arkExpiredNotice', { amount: fmtSats(expiredSat) + ' sats' }), err: true, manage: true, }); + else if (expiredSat > 0) lines.push({ text: t('arkExpiredDust', { amount: fmtSats(expiredSat) + ' sats' }), manage: true }); } const depthNotice = exitDepthNotice(); if (depthNotice) lines.push({ text: depthNotice, manage: true }); diff --git a/src/i18n.js b/src/i18n.js index 26c3dc2..7b92e7d 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -457,6 +457,10 @@ const STR = { arkFeeChain: 'On-chain fee', arkFeeService: 'Service fee', arkCoinsExpired: 'expired', + arkExpiredDust: '{amount} has expired and is too small to renew. You can forget it from the Manage page.', + arkCoinForget: 'Forget', + arkCoinForgetTitle: 'Give this coin up: it has expired and is too small to renew or exit.', + arkCoinForgotten: 'Coin forgotten', arkCoinsRenewing: 'renewing', arkCoinsIntroRenewing: 'Your Spending balance of {total} is being renewed: the coins below are in a round and come back as one fresh coin. Each can be pulled on-chain without the server’s help, at a cost that grows with the coin’s history.', arkRenewingTitle: 'Renewal in progress', |
