From 1043ca529e859b41f27d80c51b80ac897773a5df Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 6 Jul 2026 06:19:06 -0700 Subject: Sync ark funds across devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ark vtxos lived only in per-device storage, so a second device with the same seed saw the right on-chain balance (that rides the snapshot) but none of its ark funds — board/change vtxos exist only where created. Ark state now rides the encrypted nostr snapshot as a merge-safe cache extension: additive union (bring in vtxos a device is missing) with each device's own server reconcile() as the authority for spent-ness, so a stale or wrong 'spent' on one device can never propagate and poison the others. A connect-time reconcile catches coins spent elsewhere while a device was away, and a live subscription to our own replaceable event merges another device's new coins within seconds. New seam: registerCacheExtension({mergeAlways:true}) extensions get fed snapshots that aren't newer than local state too (last-writer-wins is right for rescannable chain data, wrong for merge-style vtxo state), and nostr.subscribeStates for the live path. Verified two same-seed devices over a mini relay: B sees A's boarded balance with zero mailbox activity, spends a synced vtxo, A converges to a stable balance with the spent vtxo correctly marked. Co-Authored-By: Claude Fable 5 --- src/wallet.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/wallet.js') diff --git a/src/wallet.js b/src/wallet.js index 93f5c39..ccb7b81 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -1195,7 +1195,15 @@ export class Wallet { // Called from load() with its options (wallet switch / (re)open). registerLoadHook(fn) { this._loadHooks.push(fn); } // save() -> object merged into the cache snapshot; load(d) restores. + // An extension with mergeAlways:true has a commutative load() and is also + // fed snapshots OLDER than local state (see _mergeSnapshotExtensions). registerCacheExtension(e) { this._cacheExtensions.push(e); } + // Feed merge-safe extensions a snapshot that ISN'T newer than local state — + // last-writer-wins is right for rescannable chain data, but merge-style + // state (ark vtxos) must flow both directions. + _mergeSnapshotExtensions(d) { + for (const e of this._cacheExtensions) { try { if (e.mergeAlways) e.load(d); } catch {} } + } // Called with every saved state snapshot (e.g. to push it to sync relays). registerCacheSavedHook(fn) { this._cacheSavedHooks.push(fn); } lockedCoinIds() { -- cgit v1.2.3