aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/ark/manager.js31
-rw-r--r--tools/ln-receive-twin-test.js8
2 files changed, 29 insertions, 10 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js
index a2907c5..703d341 100644
--- a/src/ark/manager.js
+++ b/src/ark/manager.js
@@ -79,19 +79,30 @@ const actionTs = (a) => Number(String(a.id || '').split('-').pop()) || 0;
// Renewals another device has in flight, as this device should show them:
// not finished there, and not already finished as far as this device's
// history knows (the "Renewed" row synced ahead of the final telling).
-// A Lightning receive the ASP settles is claimed as an arkoor — and that same
-// coin can reach the mailbox too, where it used to be recorded again as a
-// plain receive: one payment, two "Received" rows, and a celebration that
-// added them up (The Daniel saw 2,010 for 1,005). The claim now stamps its
-// coins' ids on its ln-receive movement; a receive movement for one of those
-// coins is the same money and is left out. An ln-receive recorded before the
-// ids were stamped is matched by amount within a few minutes. Derived, never
-// deleted: a synced copy from another device can't bring the twin back.
+// One coin, one "Received" row. A receive movement is recorded by whichever
+// device (or tab) sees the coin arrive, each under its own movement id, and
+// the cross-device merge unions movements by id — so a wallet open on two
+// devices kept one row per device for the same coin, and the celebration
+// added them up (The Daniel: four "Received 1,000" rows for one 1,000 sat
+// coin, "Payment received! 4,000"). Same for a Lightning receive the ASP
+// settles: the claim's ln-receive and the mailbox's plain receive of the same
+// coin. This names the extra rows: per coin, the receive tagged as Lightning
+// if one is (its ⚡ is the right icon), else the earliest; and any receive of
+// a coin a claim already tells of (by the ids the claim stamps, or — for a
+// claim recorded before it stamped them — the same amount within minutes).
+// Derived at read time, never deleted: a synced copy from another device
+// can't bring a twin back.
export function lnFoldedIds(movements) {
const out = new Set();
- const lns = (movements || []).filter((m) => m.type === 'ln-receive' && m.status === 'complete');
- if (!lns.length) return out;
const recs = (movements || []).filter((m) => m.type === 'receive' && m.status === 'complete' && m.vtxoId);
+ const byCoin = new Map();
+ for (const r of recs) (byCoin.get(r.vtxoId) || byCoin.set(r.vtxoId, []).get(r.vtxoId)).push(r);
+ for (const rows of byCoin.values()) {
+ if (rows.length < 2) continue;
+ const keep = rows.find((r) => r.viaLn) || rows.reduce((a, b) => ((a.ts || 0) <= (b.ts || 0) ? a : b));
+ for (const r of rows) if (r !== keep) out.add(r.id);
+ }
+ const lns = (movements || []).filter((m) => m.type === 'ln-receive' && m.status === 'complete');
for (const ln of lns) {
if (ln.vtxoIds && ln.vtxoIds.length) {
for (const r of recs) if (ln.vtxoIds.includes(r.vtxoId)) out.add(r.id);
diff --git a/tools/ln-receive-twin-test.js b/tools/ln-receive-twin-test.js
index bbec971..20d4f48 100644
--- a/tools/ln-receive-twin-test.js
+++ b/tools/ln-receive-twin-test.js
@@ -27,5 +27,13 @@ f = lnFoldedIds([ln('L1', 500, T), ln('L2', 500, T + 5000), rcv('R1', 500, T + 1
check('two claims of one amount fold one receive each', f.has('R1') && f.has('R2') && f.size === 2);
f = lnFoldedIds([rcv('R', 1005, T), rcv('S', 2000, T)]);
check('plain receives with no claim stay', f.size === 0);
+// the same coin recorded by two devices (each its own movement id), one of
+// them tagged ⚡ by the registrar's receipts: one row, the tagged one
+f = lnFoldedIds([rcv('A', 1000, T), { ...rcv('B', 1000, T + 2000), vtxoId: 'v-A', viaLn: true }, { ...rcv('C', 1000, T + 3000), vtxoId: 'v-A' }, { ...rcv('D', 1000, T + 4000), vtxoId: 'v-A', viaLn: true }]);
+check('one coin recorded four times shows once, the ⚡ copy', f.size === 3 && !f.has('B'), [...f].join(','));
+f = lnFoldedIds([rcv('A', 1000, T), { ...rcv('B', 1000, T + 2000), vtxoId: 'v-A' }]);
+check('...or the earliest when none is tagged', f.size === 1 && f.has('B'));
+f = lnFoldedIds([rcv('A', 1000, T), rcv('B', 1000, T + 2000)]);
+check('two coins of the same amount stay two rows', f.size === 0);
console.log(ok ? '\n✅ a claimed LN receive is told once' : '\n❌ failed');
process.exit(ok ? 0 : 1);