aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-27 23:45:07 -0700
committerAdam Soltys <[email protected]>2026-09-27 23:45:07 -0700
commit360c39400e219b9b4863e3ae6716f4e73b788045 (patch)
treed796b01d6f3eaf39be286b9978aa9e4a36f9a497 /src
parent8a3af8c8afa9c86bc84f2821faaf6530eeb81a03 (diff)
downloadcoinosv3-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.js15
-rw-r--r--src/features/ark.js15
-rw-r--r--src/i18n.js4
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',