diff options
| author | Adam Soltys <[email protected]> | 2026-06-24 23:36:17 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-24 23:36:17 -0700 |
| commit | 6d8df4d5b004ea9a84057ff78b9276262cb8f264 (patch) | |
| tree | 1b9f82551726bc6088fe21b81d745a0becc67b0f /src/wallet.js | |
| parent | 3b1adc89a361fd4c1a0552673c621a06f041dc75 (diff) | |
| download | coinosv3-6d8df4d5b004ea9a84057ff78b9276262cb8f264.tar.gz coinosv3-6d8df4d5b004ea9a84057ff78b9276262cb8f264.zip | |
Drop background/full rescans for on-demand per-address rescan
Remove the periodic full re-scan entirely — the WS and the frontier poll keep
the balance current, and refreshLive's _reconcileHeld catches spends of held
coins. The only gap (a deposit to a reused old address) is now handled
explicitly: Settings → Rescan opens a paginated list of every known address,
each with its own Rescan button (wallet.rescanAddress re-queries just that one).
Replaces the full 'Rescan wallet' button.
This sharply cuts recurring REST volume (the main rate-limit driver). Trade-off:
when the socket is down, a pending tx's confirmed badge in History may lag until
the WS reconnects or you rescan — the balance itself stays correct via reconcile.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Diffstat (limited to 'src/wallet.js')
| -rw-r--r-- | src/wallet.js | 67 |
1 files changed, 58 insertions, 9 deletions
diff --git a/src/wallet.js b/src/wallet.js index f52efdf..0f2a763 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -428,6 +428,59 @@ export class Wallet { return changed; } + // Every address the wallet currently knows about (scanned receive + change), + // sorted by chain then index, for the per-address rescan list in Settings. + knownAddresses() { + const mk = (e, chain) => ({ chain, index: e.index, address: e.address, used: !!e.used, balance: (e.confirmed || 0) + (e.pending || 0) }); + return [ + ...this.receive.map((e) => mk(e, 0)), + ...this.change.map((e) => mk(e, 1)), + ].sort((a, b) => a.chain - b.chain || a.index - b.index); + } + + // Re-query a single address and reconcile just its coins/history into state — + // the targeted alternative to a full rescan, for recovering a deposit to a + // reused old address. Returns whether the address is used. + async rescanAddress(chain, index) { + const { address } = this.derive(chain, index); + const info = await this.api.addressInfo(address); + const cs = info.chain_stats || {}; + const ms = info.mempool_stats || {}; + const used = (cs.tx_count || 0) > 0 || (ms.tx_count || 0) > 0; + const confirmed = (cs.funded_txo_sum || 0) - (cs.spent_txo_sum || 0); + const pending = (ms.funded_txo_sum || 0) - (ms.spent_txo_sum || 0); + + const arr = chain === 0 ? this.receive : this.change; + let entry = arr.find((e) => e.index === index); + if (!entry) { entry = { chain, index, address }; arr.push(entry); arr.sort((a, b) => a.index - b.index); } + this.addrMap.set(address, { chain, index }); + entry.used = used; + entry.confirmed = confirmed; + entry.pending = pending; + + const us = await this.api.addressUtxos(address); + this.utxos = this.utxos.filter((u) => u.address !== address); + for (const u of us) this.utxos.push({ txid: u.txid, vout: u.vout, value: u.value, address, chain, index, confirmed: !!u.status.confirmed }); + + const list = await this.api.addressTxs(address); + for (const tx of list) { + const summary = this._txSummary(tx); + const at = this.txs.findIndex((t) => t.txid === tx.txid); + if (at >= 0) this.txs[at] = summary; + else this.txs.push(summary); + } + + this.nextReceiveIndex = firstUnused(this.receive); + this.nextChangeIndex = firstUnused(this.change); + this.utxos.sort((x, y) => y.value - x.value); + this._sortTxs(); + this._recomputeBalanceFromChains(); + this.loaded = true; + this.saveCache(); + this.emit(); + return used; + } + // silent=false: foreground load (shows loading state, always re-renders). // silent=true: background refresh (poll / WS) — no spinner, and only emits // if the visible state changed, so it never disrupts typing. @@ -1079,16 +1132,12 @@ export class Wallet { this._lastPoll = Date.now(); this.refreshLive(); }, 3000); - // Periodic full scan: reconciles pending→confirmed history, spends, and - // stale cache/relay state — but only when the socket is down. While live the - // WS already pushes confirmations and block events, so a full re-scan would - // just burn the rate limit (and risk starving the very socket we rely on). - this._deepTimer = setInterval(() => { - if (this.live) return; - this.scan({ silent: true }).catch(() => {}); - }, 120000); + // No periodic full re-scan: the WS (and the frontier poll above) keep the + // balance current, and refreshLive's _reconcileHeld catches spends of held + // coins. The only thing neither covers is a deposit to a reused old address, + // which is handled on demand via per-address rescan in Settings. // Only mempool.space has a live socket; other explorers rely on the poll - // and deep scan above (wsUrl returns null → no socket). + // above (wsUrl returns null → no socket). if (typeof WebSocket === 'undefined' || !this.wsUrl()) return; this._wsWant = true; |
