diff options
| author | Adam Soltys <[email protected]> | 2026-10-22 23:34:31 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-22 23:34:31 -0700 |
| commit | a89bce97319d5860da8d581ff41cdace9acbb077 (patch) | |
| tree | 263363716c53cfa1ec2cf7a984a08568690cb48e /src/features/ark.js | |
| parent | 2d1f83c88000503b0ce5dbe391ce1929bcb9a71b (diff) | |
| download | coinosv3-a89bce97319d5860da8d581ff41cdace9acbb077.tar.gz coinosv3-a89bce97319d5860da8d581ff41cdace9acbb077.zip | |
Automatic renewals only when worth it (fee under a tenth of what renews); orphan HTLC coins are settled by the server's answer
Adam's wallet renewed a 1,000-sat coin past its expiry plus four bits of change for 301 sats — the expiry trigger had no floor, only the depth trigger did. Now every automatic renewal, under either renewing strategy, is skipped when the fee would exceed a tenth of the coins it renews; they stay spendable, the server honors them, and the strategy card says so.
Reconcile: five 22-sat HTLC-send coins sat 'pending' for days — their Lightning payments' actions had been pruned, and an unheld pending coin was only asked about when it was a plain pubkey coin. HTLC orphans are asked about now too: spent is spent; one the server still holds open stays pending (an HTLC coin is never this wallet's to spend). tools/ark-htlc-orphan.test.js.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'src/features/ark.js')
| -rw-r--r-- | src/features/ark.js | 9 |
1 files changed, 8 insertions, 1 deletions
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 |
