diff options
| author | Adam Soltys <[email protected]> | 2026-06-28 03:15:00 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-06-28 03:15:00 -0700 |
| commit | e8edfd88b14ad07f5f97f5cb1d5d603d825a413d (patch) | |
| tree | c98c7fb8dced7442637b9c0c442bbf3a68edcced | |
| parent | df58295848c7675e6501148da2a988b918811a2c (diff) | |
| download | coinosv3-e8edfd88b14ad07f5f97f5cb1d5d603d825a413d.tar.gz coinosv3-e8edfd88b14ad07f5f97f5cb1d5d603d825a413d.zip | |
Scan silent payments from each wallet's birth, not the indexer's start
A freshly-generated wallet can't have received SP before it existed, so set its
scan watermark to the current tip on first connect (skipping the whole historical
catch-up) instead of scanning from 0. Threaded a 'generated' flag through the
create flow (openWallet → enterWallet → activateAccount → load spFresh); imported
wallets still scan from the indexer start since they may have prior receipts.
Verified on regtest: a fresh wallet skips the block-5780 receipt (watermark=tip,
balance 0); the same seed imported finds it.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
| -rw-r--r-- | src/app.js | 12 | ||||
| -rw-r--r-- | src/wallet.js | 13 |
2 files changed, 18 insertions, 7 deletions
@@ -542,7 +542,7 @@ function createPane() { ), h( 'button', - { class: 'btn-block', onClick: () => openWallet(ui.draftMnemonic) }, + { class: 'btn-block', onClick: () => openWallet(ui.draftMnemonic, { generated: true }) }, t('skipVerification') ) ); @@ -595,11 +595,11 @@ function createPane() { if (hasPass && ui.confirmPass !== ui.passphrase) { ui.unlockError = t('passphraseMismatch'); render(); return; } - openWallet(ui.draftMnemonic); + openWallet(ui.draftMnemonic, { generated: true }); }, }, t('openWallet')) ), - h('button', { class: 'btn-block', onClick: () => openWallet(ui.draftMnemonic) }, t('skipVerification')) + h('button', { class: 'btn-block', onClick: () => openWallet(ui.draftMnemonic, { generated: true }) }, t('skipVerification')) ); } @@ -657,11 +657,11 @@ function optionsPanel() { // Import accepts a recovery phrase, an xpub/zpub (watch-only), or an xprv/zprv // (full spending). Classify the pasted text and open the right kind of wallet. -async function openWallet(input) { +async function openWallet(input, opts = {}) { ui.unlockError = ''; const raw = (input || '').trim(); const m = raw.replace(/\s+/g, ' '); - if (isValidMnemonic(m)) { await enterWallet(m, ui.passphrase); return; } + if (isValidMnemonic(m)) { await enterWallet(m, ui.passphrase, { generated: opts.generated }); return; } let pk; try { pk = parseExtendedKey(raw); } catch { ui.unlockError = t('invalidImport'); render(); return; } const acc = pk.kind === 'xpub' @@ -693,7 +693,7 @@ async function activateAccount(acc, opts = {}) { const netName = getNetwork(); if (acc.type === 'watch') wallet.load({ xpub: acc.xpub, netName, offline: false }); else if (acc.xprv) wallet.load({ xprv: acc.xprv, netName, offline: false }); - else wallet.load({ mnemonic: acc.mnemonic, passphrase: acc.passphrase || '', netName, offline: false }); + else wallet.load({ mnemonic: acc.mnemonic, passphrase: acc.passphrase || '', netName, offline: false, spFresh: !!opts.generated }); // Record the account-level xpub so this wallet survives a session wipe as a // watch-only entry (see the durable account directory) — you keep seeing your // balance/history and re-enter the seed to spend again. diff --git a/src/wallet.js b/src/wallet.js index 2f9cca5..497b60b 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -158,8 +158,11 @@ export class Wallet { } // --- setup -------------------------------------------------------------- - load({ mnemonic = '', passphrase = '', xpub = '', xprv = '', netName = 'mainnet', offline = false }) { + load({ mnemonic = '', passphrase = '', xpub = '', xprv = '', netName = 'mainnet', offline = false, spFresh = false }) { this.stopRealtime(); + // A freshly-generated wallet can't have received silent payments before it + // existed, so we start its SP watermark at the current tip (no history scan). + this._spFresh = spFresh; this.mnemonic = (mnemonic || '').trim().replace(/\s+/g, ' '); this.passphrase = passphrase; this.xpub = xpub || ''; @@ -377,6 +380,14 @@ export class Wallet { const indexer = spIndexerUrl(this.netName); if (!keys || !indexer || this.offline) return { unavailable: true }; if (this._spScanBusy) return { busy: true }; + // Fresh wallet, never scanned: set the watermark to the current tip instead of + // scanning all history (nothing predates the wallet). New blocks + mempool + // arrive via the live push from here on. + if (this._spFresh && !this.lastSpScan && !rescan) { + this._spFresh = false; + try { this.lastSpScan = (await (await fetch(`${indexer}/height`)).json()).height || 0; this.saveCache(); } catch {} + return { fresh: true }; + } this._spScanBusy = true; // Only a manual scan shows the visible "Scanning…" state; auto-scans are // invisible (setting spScanning for them left the card stuck on "Scanning…"). |
