diff options
| author | Adam Soltys <[email protected]> | 2026-06-24 07:34:36 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-24 07:34:36 -0700 |
| commit | dc1e7a400d1158d0bb8a6a7914f7ac1b3073e034 (patch) | |
| tree | a15b8e4932c85a3b95ab612c4dfd6bffe139e4c1 /src/wallet.js | |
| parent | 6190f25ca44ed12a0e136004096aef042c472b77 (diff) | |
| download | coinosv3-dc1e7a400d1158d0bb8a6a7914f7ac1b3073e034.tar.gz coinosv3-dc1e7a400d1158d0bb8a6a7914f7ac1b3073e034.zip | |
Configurable Nostr sync; fully reconcile wallet state on load
Device sync:
- Add a "Device sync" settings card: toggle (on by default) + editable
relay list (defaults to wss://relay.coinos.io). When off, the wallet
never contacts a relay. nostr.js is now multi-relay and reads an
enabled/relays preference from localStorage.
Reconciliation (fixes a stray "payment received" + stale history on
import, especially via the Nostr/cache load path):
- Always full-scan on load instead of a frontier-only refreshLive, so
balance, history confirmations and spends are reconciled and the
celebration baselines against everything that already exists (cache/
Nostr still shows instantly first).
- Actually start the periodic full-scan safety net (_deepTimer, 2 min);
it was declared but never wired up.
- WS pushes now refresh an existing tx's pending→confirmed status, not
only add new transactions.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/wallet.js')
| -rw-r--r-- | src/wallet.js | 33 |
1 files changed, 24 insertions, 9 deletions
diff --git a/src/wallet.js b/src/wallet.js index 79c6353..111fb92 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -17,7 +17,7 @@ import * as btc from '@scure/btc-signer'; import { p2wpkh } from '@scure/btc-signer/payment'; import { Api, pool, wsUrl } from './api.js'; -import { NostrSync } from './nostr.js'; +import { NostrSync, getSyncConfig } from './nostr.js'; // No look-ahead: stop scanning a chain at the first unused address. This wallet // only ever exposes ONE unused address at a time (freshReceive = first unused; @@ -678,8 +678,11 @@ export class Wallet { this.stopRealtime(); // Safety-net poll: only the fresh frontier (next receive/change address), // in case the WebSocket misses a deposit. Already-scanned addresses are - // never re-polled. No periodic full scan — use the manual Rescan for that. + // never re-polled — the deep scan below covers spends/confirmations. this._pollTimer = setInterval(() => this.refreshLive(), 20000); + // Periodic full scan: reconciles everything refreshLive can't see on its own + // — pending→confirmed history, spends, and stale cache/relay state. + this._deepTimer = setInterval(() => this.scan({ silent: true }).catch(() => {}), 120000); if (typeof WebSocket === 'undefined') return; this._wsWant = true; @@ -832,10 +835,17 @@ export class Wallet { const a = vin.prevout && vin.prevout.scriptpubkey_address; if (a && mine.has(a)) relevant = true; } - // Record it in history (so a pending deposit shows up there too). - if (relevant && !this.txs.some((t) => t.txid === tx.txid)) { - this.txs.push(this._txSummary(tx)); - changed = true; + // Record it in history — or refresh it, so a pending tx flips to confirmed. + if (relevant) { + const idx = this.txs.findIndex((t) => t.txid === tx.txid); + const summary = this._txSummary(tx); + if (idx < 0) { + this.txs.push(summary); + changed = true; + } else if (this.txs[idx].confirmed !== summary.confirmed) { + this.txs[idx] = summary; + changed = true; + } } } if (changed) { @@ -952,8 +962,11 @@ export class Wallet { try { localStorage.setItem(this._cacheKey(), JSON.stringify(snap)); } catch {} - // Push to Nostr too (debounced), so other devices get the update. - if (!this.offline) { + // Push to the configured relays too (debounced), so other devices get the + // update — unless cross-device sync is turned off. + const sync = getSyncConfig(); + if (!this.offline && sync.enabled) { + this.nostr.setRelays(sync.relays); clearTimeout(this._nostrPubTimer); this._nostrPubTimer = setTimeout(() => this.nostr.publish(snap), 2500); } @@ -974,7 +987,9 @@ export class Wallet { // Pull the latest state from Nostr; apply it if it's newer than what we have. // Returns true if state was applied (so the caller can skip a full scan). async syncFromNostr() { - if (this.offline) return false; + const sync = getSyncConfig(); + if (this.offline || !sync.enabled) return false; + this.nostr.setRelays(sync.relays); let remote; try { remote = await this.nostr.fetch(); |
