aboutsummaryrefslogtreecommitdiffstats
path: root/src/nwc-bg.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-08-22 09:10:10 -0700
committerAdam Soltys <[email protected]>2026-08-22 09:10:10 -0700
commit9963c4c45ccd33cc8588fd3c1cf816cf09a82c2b (patch)
treecdc5d6ca684ae7e6e521c575a386b8b7c04bce71 /src/nwc-bg.js
parent16a9e78080554774d8fec8a4f29d19f97fa3722a (diff)
downloadcoinosv3-9963c4c45ccd33cc8588fd3c1cf816cf09a82c2b.tar.gz
coinosv3-9963c4c45ccd33cc8588fd3c1cf816cf09a82c2b.zip
One record answers, and failure learns patience: a rewritten mirror disarms stale sibling copies of its connections, the responder pays from the record whose network the invoice names (standing down when none fits), a ghost coin reconciles and retries instead of dying, and error replies wait out a grace period so a sibling's success reaches the client first
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_017Fc8SHJRvBBXgMCntjNCri
Diffstat (limited to 'src/nwc-bg.js')
-rw-r--r--src/nwc-bg.js25
1 files changed, 25 insertions, 0 deletions
diff --git a/src/nwc-bg.js b/src/nwc-bg.js
index b22c36e..92786c0 100644
--- a/src/nwc-bg.js
+++ b/src/nwc-bg.js
@@ -118,6 +118,31 @@ export const bgSpendableSat = (rec) =>
((rec && rec.mgr && rec.mgr.vtxos) || []).filter((v) => v.state === 'spendable')
.reduce((n, v) => n + (v.amountSat || 0), 0);
+// After (re)writing a wallet's record: strip its connections/offer from any
+// OTHER record that still carries them — an old network's mirror, say — so
+// exactly one record ever answers for a connection. The sibling's coin state
+// stays put for a later merge; only its answering role is removed. A stale
+// copy that races the live one answers from the wrong wallet (and a wrong-
+// network mirror fails instantly, beating the real payer's reply).
+export async function disarmSiblingRecords(walletKey, rec, { loadAll = allBgs, save = saveBg } = {}) {
+ const pks = new Set((rec.connections || []).map((c) => c.servicePk));
+ if (rec.offer?.pk) pks.add(rec.offer.pk);
+ if (!pks.size) return 0;
+ let disarmed = 0;
+ for (const r of await loadAll()) {
+ if (r.walletKey === walletKey || !r.rec || r.rec.v < 3) continue;
+ const conns = r.rec.connections || [];
+ const keep = conns.filter((c) => !pks.has(c.servicePk));
+ const offerHit = !!(r.rec.offer?.pk && pks.has(r.rec.offer.pk));
+ if (keep.length === conns.length && !offerHit) continue;
+ r.rec.connections = keep;
+ if (offerHit) r.rec.offer = null;
+ await save(r.walletKey, r.rec);
+ disarmed++;
+ }
+ return disarmed;
+}
+
// The worker records what it spent so the app can absorb it into budgets and
// history on next open.
export async function noteBgSpend(walletKey, spend) {