From 95c898f91d2f8ea1ef693624a542eb7fa8cadbb9 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 3 Aug 2026 14:05:21 -0700 Subject: A freshly imported wallet shows history, not a celebration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Importing a seed on a second device replayed "Payment received!" for a payment that had arrived on the first one. The two-hour recency guard couldn't catch it: a movement is stamped with the moment THIS device discovered it, so on a fresh device the entire history looks like it just landed — a week-old payment would have celebrated too. A wallet whose ark state didn't exist on this device now treats its first catch-up as history and acknowledges it, so only what turns up afterwards is news. Reopening an existing wallet is unaffected, and a payment that lands while you're watching still celebrates. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi --- src/ark/manager.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ark/manager.js b/src/ark/manager.js index 5617cf1..01de06f 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -88,7 +88,14 @@ export class ArkManager { // ---- lifecycle ---- async init() { - this.state = this.storage.load() || EMPTY_STATE(); + const loaded = this.storage.load(); + this.state = loaded || EMPTY_STATE(); + // A wallet opening on a device for the first time (a fresh import, a new + // browser) is about to discover its whole history at once, and every + // movement it records gets stamped with the moment it was found — so + // without this, a week-old payment looks like it just arrived. Mark the + // first catch-up as history: only what turns up afterwards is news. + if (!loaded) this.state.baselinePending = true; // drop duplicate receive movements (same vtxo) left by the former // poll/stream race; the vtxo set itself was always deduped const seen = new Set(); @@ -211,10 +218,17 @@ export class ArkManager { // Read new mailbox messages, fully validate incoming vtxos, then push any // in-flight actions forward. async sync() { + const baselining = !!this.state.baselinePending; const mailbox = this._mailboxKey(); const { messages } = await readMailbox(this.arkUrl, mailbox, this.state.mailboxCheckpoint); let changed = false; for (const m of messages) changed = (await this._processMailboxMessage(m)) || changed; + // Everything the first catch-up found is history, not news. + if (baselining) { + this.state.receiveAckTs = Date.now(); + delete this.state.baselinePending; + changed = true; + } if (changed) this._save(); await this.resumePending(); } -- cgit v1.2.3