diff options
| author | Adam Soltys <[email protected]> | 2026-07-06 06:19:06 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-06 06:19:06 -0700 |
| commit | 1043ca529e859b41f27d80c51b80ac897773a5df (patch) | |
| tree | 0b50b41b6490a52d83f65f572dd8e8a321bb045d /src/wallet.js | |
| parent | 1139c6beafb1199c149bd1f51a46d817e1d46173 (diff) | |
| download | coinosv3-1043ca529e859b41f27d80c51b80ac897773a5df.tar.gz coinosv3-1043ca529e859b41f27d80c51b80ac897773a5df.zip | |
Sync ark funds across devices
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 <[email protected]>
Diffstat (limited to 'src/wallet.js')
| -rw-r--r-- | src/wallet.js | 8 |
1 files changed, 8 insertions, 0 deletions
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() { |
