diff options
| author | Adam Soltys <[email protected]> | 2026-10-09 16:45:22 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-09 16:45:22 -0700 |
| commit | 095caadcba5a63f5969e50777d7e482c1d32174e (patch) | |
| tree | 6106cc7dd21d3328c788026c237d8310b720eda4 /src | |
| parent | 20369cae83fe02968ac4318f3b8f4e9dcbbaa1cf (diff) | |
| download | coinosv3-095caadcba5a63f5969e50777d7e482c1d32174e.tar.gz coinosv3-095caadcba5a63f5969e50777d7e482c1d32174e.zip | |
Spending: one coin, one "Received" row — across devices. The Daniel still saw four "Received 1,000" rows and "Payment received! 4,000" for what the ASP shows is a single 1,000 sat coin (and two rows for his 1,005). A receive movement is recorded by every device or tab that sees the coin arrive, each under its own id, and the cross-device merge unions movements by id — so each kept its own row, and the registrar-receipt tagging marked some ⚡. lnFoldedIds now also folds receives of the same coin: the ⚡-tagged one stays (its icon is right), else the earliest. Same read-time derivation as the claim twins (history, celebration, arkMovements, LN tagging), so nothing is deleted and no synced copy brings a twin back.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
Diffstat (limited to 'src')
| -rw-r--r-- | src/ark/manager.js | 31 |
1 files changed, 21 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); |
