aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-27 12:28:33 -0700
committerAdam Soltys <[email protected]>2026-10-27 12:28:33 -0700
commita788054f0dcbdd17e2df7614d3156ad053afaa82 (patch)
treead83b834ed6cd738603cbe84c3a864fcfda44a7a
parentc2f8c3281f02f29c19025553addfbd7638256b74 (diff)
downloadcoinosv3-server-a788054f0dcbdd17e2df7614d3156ad053afaa82.tar.gz
coinosv3-server-a788054f0dcbdd17e2df7614d3156ad053afaa82.zip
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 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
-rw-r--r--names/server.js22
1 files changed, 20 insertions, 2 deletions
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));