From a89bce97319d5860da8d581ff41cdace9acbb077 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 22 Oct 2026 23:34:31 -0700 Subject: Automatic renewals only when worth it (fee under a tenth of what renews); orphan HTLC coins are settled by the server's answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- tools/ark-htlc-orphan.test.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tools/ark-htlc-orphan.test.js (limited to 'tools') diff --git a/tools/ark-htlc-orphan.test.js b/tools/ark-htlc-orphan.test.js new file mode 100644 index 0000000..cc127be --- /dev/null +++ b/tools/ark-htlc-orphan.test.js @@ -0,0 +1,33 @@ +// bun test tools/ark-htlc-orphan.test.js — an HTLC coin whose payment's +// action has been pruned is an orphan the server settles: spent means spent +// (it stopped counting as pending money), still-open stays pending, and a +// coin a live payment holds is never asked about. +import { test, expect, mock } from 'bun:test'; +import * as proto from '../src/ark/proto.js'; +const asked = []; +mock.module('../src/ark/proto.js', () => ({ ...proto, getVtxoStatus: async (url, raw) => { asked.push(raw[0]); return raw[0] === 1 ? proto.VTXO_STATE_SPENT : proto.VTXO_STATE_SPENDABLE; } })); +const { ArkManager } = await import('../src/ark/manager.js'); +test('orphan HTLC coins are settled by the server\'s answer', async () => { + const m = Object.create(ArkManager.prototype); + m.state = { + vtxos: [ + { id: 'gone', state: 'pending', amountSat: 22, bytes: 'x', policy: 'serverHtlcSend', tag: 1 }, // payment done long ago, action pruned: server says spent + { id: 'open', state: 'pending', amountSat: 22, bytes: 'x', policy: 'serverHtlcSend', tag: 2 }, // orphan the server still holds open + { id: 'held', state: 'pending', amountSat: 22, bytes: 'x', policy: 'serverHtlcSend', tag: 3 }, // a live payment's: not asked about + { id: 'coin', state: 'pending', amountSat: 500, bytes: 'x', policy: 'pubkey', tag: 2 }, // a plain orphan the server counts: spendable again + ], + actions: [{ type: 'ln-pay', step: 'initiated', parts: [{ inputId: 'in1' }], htlcVtxoIds: ['held'] }], + scheduled: [], + }; + m.arkUrl = 'x'; + m._decoded = (v) => ({ point: { raw: Uint8Array.of(v.tag) }, policy: { type: v.policy } }); + m._keyForVtxo = () => ({ privkey: new Uint8Array(32) }); + m._save = () => {}; m._movement = () => {}; m._remoteBusy = () => new Set(); + await m.reconcile(); + const st = Object.fromEntries(m.state.vtxos.map((v) => [v.id, v.state])); + expect(st.gone).toBe('spent'); + expect(st.open).toBe('pending'); + expect(st.held).toBe('pending'); + expect(st.coin).toBe('spendable'); + expect(asked.sort()).toEqual([1, 2, 2]); +}); -- cgit v1.2.3