recover an imported wallet's coins with an nbxplorer utxo scan
registering an xpub only indexes it from that moment on, so an imported seed with history read as a confident zero: every call succeeded, the coins were simply absent. scantxoutset walks the node's current utxo set directly and finds them regardless of when the account was registered. runs all four script variants sequentially — the funds could be on any one — and surfaces progress through the existing SyncState channel so the balance says "scanning" rather than nothing. auto-fires on an imported mnemonic only; a generated seed has no history to look for. recovers spendable coins, not spent history. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,7 @@ import {
|
||||
type NewAddressRequest,
|
||||
type NodeInfo,
|
||||
type OnchainTx,
|
||||
type RescanState,
|
||||
type SendCoinsRequest,
|
||||
type SendCoinsResult,
|
||||
type SignMessageResult,
|
||||
@@ -312,9 +313,31 @@ export class OnchainBackend extends BaseBackend {
|
||||
syncedAt: this.current?.at ?? null,
|
||||
stale: this.current !== null && Date.now() - this.current.at >= SCAN_TTL_MS,
|
||||
lastError: this.lastError,
|
||||
rescan: this.chain.rescanState?.() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the chain source to go looking for this wallet's coins from scratch.
|
||||
*
|
||||
* Returns as soon as the search is queued — it takes minutes, and the caller is an HTTP route. The
|
||||
* refresh chained onto its completion is the part that matters: a rescan that found coins has changed
|
||||
* nothing visible until the snapshot in front of it is rebuilt, and without this the owner would be
|
||||
* staring at the same zero until the TTL happened to expire.
|
||||
*/
|
||||
async startRescan(): Promise<RescanState> {
|
||||
const start = this.chain.startRescan?.bind(this.chain);
|
||||
if (!start) this.notSupported('rescanning the chain');
|
||||
const { state, done } = start(this.scanContext().accounts);
|
||||
void done
|
||||
.then(() => this.refresh())
|
||||
// The rescan's own failure is already on its state and reported through getSyncState; a failed
|
||||
// refresh behind it lands on lastError the same way. Neither should surface as an unhandled
|
||||
// rejection in the sidecar's log.
|
||||
.catch(() => {});
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wallet's view of the chain, stale-while-revalidate.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user