aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-06-15 22:57:39 -0700
committerAdam Soltys <[email protected]>2026-06-15 22:57:39 -0700
commit5f478de7d90f9170b81b21af6492de1be2076696 (patch)
treedb1747b15ce78110a02ca921de32db61238d5f0b
parentb7e73b295311fcfcff3e247bb4451fbf757cefb9 (diff)
downloadcoinosv3-5f478de7d90f9170b81b21af6492de1be2076696.tar.gz
coinosv3-5f478de7d90f9170b81b21af6492de1be2076696.zip
Stop re-scanning coin addresses; frontier-only poll + manual rescan
Per request: an address is scanned once, then never auto-scanned again. The routine poll now checks only the fresh frontier (next receive + change address) for new deposits; coin-holding addresses are no longer polled and the periodic full re-scan is removed. Spends on existing coins arrive via the WebSocket while connected; a new Settings → Rescan button does a full scan to catch anything missed (e.g. a payment to a reused old address). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
-rw-r--r--src/app.js11
-rw-r--r--src/wallet.js28
2 files changed, 25 insertions, 14 deletions
diff --git a/src/app.js b/src/app.js
index 789f12c..c64c650 100644
--- a/src/app.js
+++ b/src/app.js
@@ -521,6 +521,17 @@ function settingsTab() {
{ class: 'card col' },
h('h3', {}, 'Offline transfer'),
snapshotActions()
+ ),
+ h(
+ 'div',
+ { class: 'card col' },
+ h('h3', {}, 'Rescan'),
+ h('p', { class: 'small muted', style: 'margin:0' },
+ 'Your current address updates automatically. Rescan only if a payment was sent to an old (reused) address and isn’t showing.'),
+ wallet.offline
+ ? null
+ : h('button', { disabled: wallet.scanning, onClick: () => wallet.scan() },
+ wallet.scanning ? 'Scanning…' : 'Rescan wallet')
)
);
}
diff --git a/src/wallet.js b/src/wallet.js
index b3c6a25..440f8dc 100644
--- a/src/wallet.js
+++ b/src/wallet.js
@@ -229,21 +229,22 @@ export class Wallet {
return arr.find((a) => a.index === index);
}
- // Light poll: only re-query the addresses that can actually change — ones
- // holding coins (could be spent) and the next fresh receive/change address
- // (could receive). Stable historical addresses stay cached and aren't
- // re-fetched. If anything moved, fall back to a full scan to reconcile.
+ // Light poll: only check the fresh frontier — the next receive and change
+ // address — for new activity. Already-scanned addresses are never re-polled;
+ // changes to them (spends) arrive over the WebSocket while connected, and a
+ // manual rescan covers anything missed (e.g. address reuse). If the frontier
+ // moved, escalate to a full scan once to reconcile.
async refreshLive() {
if (this.offline || this._refreshing || this._polling) return;
this._polling = true;
try {
- const targets = new Map();
- for (const u of this.utxos) targets.set(`${u.chain}/${u.index}`, { chain: u.chain, index: u.index });
- targets.set(`0/${this.nextReceiveIndex}`, { chain: 0, index: this.nextReceiveIndex });
- targets.set(`1/${this.nextChangeIndex}`, { chain: 1, index: this.nextChangeIndex });
+ const targets = [
+ { chain: 0, index: this.nextReceiveIndex },
+ { chain: 1, index: this.nextChangeIndex },
+ ];
let changed = false;
- for (const { chain, index } of targets.values()) {
+ for (const { chain, index } of targets) {
const { address } = this.derive(chain, index);
const info = await this.api.addressInfo(address);
const cs = info.chain_stats || {};
@@ -497,11 +498,10 @@ export class Wallet {
startRealtime() {
if (this.offline) return;
this.stopRealtime();
- // Safety-net polling (the WebSocket gives instant updates, but its pushes
- // can be missed): a light poll of just the live addresses every 15s, plus
- // an occasional full scan to catch anything the frontier check can't.
- this._pollTimer = setInterval(() => this.refreshLive(), 15000);
- this._deepTimer = setInterval(() => this.scan({ silent: true }), 300000);
+ // 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.
+ this._pollTimer = setInterval(() => this.refreshLive(), 20000);
if (typeof WebSocket === 'undefined') return;
this._wsWant = true;