aboutsummaryrefslogtreecommitdiffstats
path: root/src/wallet.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-24 07:34:36 -0700
committerAdam Soltys <[email protected]>2026-06-24 07:34:36 -0700
commitdc1e7a400d1158d0bb8a6a7914f7ac1b3073e034 (patch)
treea15b8e4932c85a3b95ab612c4dfd6bffe139e4c1 /src/wallet.js
parent6190f25ca44ed12a0e136004096aef042c472b77 (diff)
downloadcoinosv3-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.js33
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();