aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ark/manager.js11
-rw-r--r--src/features/ark.js9
-rw-r--r--src/i18n.js2
3 files changed, 17 insertions, 5 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index 79fdcd0..df199af 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -825,8 +825,13 @@ export class ArkManager {
v.state = owner.step === 'done' || owner.step === 'failed' ? 'spent' : 'pending';
changed = true;
}
+ // An HTLC coin whose payment no action names any more (done ln-pays are
+ // pruned after a while) is an orphan too: five 22-sat ones sat 'pending'
+ // in a wallet for days, counted as money on its way. The server's answer
+ // settles it — spent is spent; one still open stays pending, since an
+ // HTLC coin is never this wallet's to spend (see below).
const candidates = this.state.vtxos.filter((v) => v.state === 'spendable'
- || (v.state === 'pending' && !held.has(v.id) && v.bytes && this._policyType(v) === 'pubkey'));
+ || (v.state === 'pending' && !held.has(v.id) && v.bytes));
const was = candidates.map((v) => v.state); // what each was when this pass began
// Each status query signs a challenge (schnorr) and may decode the vtxo —
// real secp256k1 work. Building them all in one synchronous map was a
@@ -851,8 +856,8 @@ export class ArkManager {
// an orphan the server still counts is simply spendable again; one it
// holds as spent is gone, and the pay that took it was recorded (or
// will merge in) elsewhere — no row of its own
- if (states[i] === VTXO_STATE_SPENDABLE) { v.state = 'spendable'; changed = true; }
- else if (states[i] === VTXO_STATE_SPENT) { v.state = 'spent'; changed = true; }
+ if (states[i] === VTXO_STATE_SPENT) { v.state = 'spent'; changed = true; }
+ else if (states[i] === VTXO_STATE_SPENDABLE && this._policyType(v) === 'pubkey') { v.state = 'spendable'; changed = true; }
return;
}
if (states[i] !== VTXO_STATE_SPENT) return;
diff --git a/src/features/ark.js b/src/features/ark.js
index 771fa45..6952fc0 100644
--- a/src/features/ark.js
+++ b/src/features/ark.js
@@ -3437,13 +3437,19 @@ export function arkFeature(ctx) {
const deepCoins = !policy.maxDepth ? [] : spendables.filter((v) =>
!expiringCoins.includes(v) && depthOf(v) >= policy.maxDepth && mgr.refreshFee([v], tip) * 10 <= v.amountSat);
if (!expiringCoins.length && !deepCoins.length) { arkRenewWarn = null; return; }
+ // Worth it, or not at all: an automatic renewal never spends more than
+ // a tenth of what it renews. 301 sats to renew a 1,000-sat coin and
+ // four bits of change restored an exit nobody would ever use (the
+ // exit itself would cost more than the coins). Coins left alone stay
+ // spendable — the server keeps honoring them.
+ const worthIt = (set) => mgr.refreshFee(set, tip) * 10 <= set.reduce((n, v) => n + v.amountSat, 0);
if (!expiringCoins.length) {
// nothing is expiring: this round is for the deep coins alone, and
// they pay for it — the floor logic below still applies
arkRenewWarn = null;
let renewSet = [...deepCoins, ...spendables.filter((v) => !deepCoins.includes(v) && v.amountSat < 1000)];
const netSat = (set) => set.reduce((n, v) => n + v.amountSat, 0) - mgr.refreshFee(set, tip);
- if (netSat(renewSet) < 330) return;
+ if (netSat(renewSet) < 330 || !worthIt(renewSet)) return;
await mgr.refresh(renewSet.map((v) => v.id));
render();
return;
@@ -3478,6 +3484,7 @@ export function arkFeature(ctx) {
const netSat = (set) => set.reduce((n, v) => n + v.amountSat, 0) - mgr.refreshFee(set, tip);
while (netSat(renewSet) < 330 && extras.length) renewSet = [...renewSet, extras.shift()];
if (netSat(renewSet) < 330) return; // whole balance can't clear the floor — the warn above covers it
+ if (!worthIt(renewSet)) return; // not worth a tenth of itself: left alone, still spendable
await mgr.refresh(renewSet.map((v) => v.id));
render();
} catch {} // transient — the next throttled attempt retries
diff --git a/src/i18n.js b/src/i18n.js
index b8d9f45..edf9102 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -787,7 +787,7 @@ const STR = {
arkCustodyCustodial: 'Allow coins to expire',
arkCustodyCustodialDesc: 'Nothing renews and no renewal fees are paid. A coin keeps its own exit until it expires. After that, paying still works, but bringing it on-chain needs Coinos’s cooperation.',
arkCustodySelf: 'Minimize renewal cost',
- arkCustodySelfDesc: 'Coins renew in the last hours before they expire, when renewal is cheapest, so you can always exit on your own without Coinos. Roughly once every {days} days per coin.',
+ arkCustodySelfDesc: 'Coins renew in the last hours before they expire, when renewal is cheapest, so you can always exit on your own without Coinos. Roughly once every {days} days per coin. Coins too small to be worth the fee are left alone.',
arkCustodyExit: 'Minimize exit cost',
arkCustodyExitDesc: 'Every transfer adds a hop to a coin’s history, and a unilateral exit pays one on-chain transaction per hop. This renews a coin before it expires, and also as soon as its history is {hops} hops deep, so bringing any coin on-chain stays close to a fresh coin’s price. Only for coins worth it: never more than a tenth of a coin on its renewal.',
arkCustodyCostNone: 'no fees',