From a788054f0dcbdd17e2df7614d3156ad053afaa82 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 27 Oct 2026 12:28:33 -0700 Subject: names: a float send that threw is looked up before the payment is tried again The float wallet keeps every send as an action. forward() records the send it is about to make (destination, amount, start time) before making it; a later try finds that action: done = delivered, failed or never started = go again, anything else = still unresolved, wait. Before, a send that threw after its cosign landed (the coins moved, the answer didn't) was queued and paid a second time. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4 --- names/server.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'names') diff --git a/names/server.js b/names/server.js index f8fa378..f2a9f41 100644 --- a/names/server.js +++ b/names/server.js @@ -440,7 +440,24 @@ async function forwardOnce(name, sat, hash, pubkey) { // 2026-10-27 ("crit1 … double payout", ~5M). So: done is done, still out // is wait, and only a payment that really failed is tried again. const sent = hash && state.inflight?.[hash]; - if (sent) { + // ...and the same for a float send: one that threw can still have moved + // the coins (the cosign landed, the answer didn't). The float wallet keeps + // every send as an action; the one made for this payment says what it did. + if (sent?.float) { + const acts = (fwd?.state?.actions || []).filter((a) => a.type === 'send' && a.destAddress === sent.dest + && a.amountSat === sent.sat && Number(String(a.id).slice(5).replace(/-+$/, '')) >= sent.after); + if (acts.some((a) => a.step === 'done')) { + delete state.inflight[hash]; + recordLnReceipt(name, sat, hash); + persist(); + log(`delivered ${sat} sat to ${name}: the earlier float send completed`); + return; + } + if (acts.some((a) => a.step !== 'failed')) throw new Error('float send for this payment still unresolved — not paying again'); + log(`forward: the earlier float send for ${name} ${acts.length ? 'failed' : 'never started'} — trying again`); + delete state.inflight[hash]; + persist(); + } else if (sent) { const pays = (await ln.call('listpays', { payment_hash: sent.paymentHash })).pays || []; if (pays.some((x) => x.status === 'complete')) { delete state.inflight[hash]; @@ -465,7 +482,8 @@ async function forwardOnce(name, sat, hash, pubkey) { const viaFloat = async () => { if (!ownServer || fwd.balance().spendableSat < sat) return false; - try { await fwd.send(dest, sat); } + if (hash) { (state.inflight ||= {})[hash] = { float: true, dest, sat, name, after: Date.now() }; persist(); } + try { await fwd.send(dest, sat); settled(); } catch (e) { // enough sats, too many coins: merge them, and the queue's next try goes if (/too many coins/.test(e.message)) consolidateFloat().catch((x) => log('float consolidate failed: ' + x.message)); -- cgit v1.2.3