dgb-wallet/packages/rpc/scripts/scan-seed-vps.ts
Digibyte.X d50286a9a5 DigiByte.X Wallet — recovery-first web wallet monorepo
Packages: core (BIP39/32/44/49/84/86 derivation, WIF), keystore (PBKDF2 +
AES-GCM), psbt, p2p (BIP157/158 SPV daemon, Dandelion++ broadcast with
relay-echo verification), blockbook, rpc, electrum, net, bns, web-ui
(React/Vite), mobile (Expo). ops/ holds the nginx vhost, systemd unit and
push.sh deploy script for digibyte.x/web.
2026-09-20 20:16:52 +02:00

138 lines
5.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// End-to-end recovery scan against real DGB mainnet via YOUR OWN VPS
// running ops/setup.sh. Takes a seed phrase, derives account xpubs at
// BIP44/49/84/86, and asks scantxoutset to return every UTXO across
// the first 100 receive + 100 change addresses per account.
//
// One RPC call = full scan. Typical response time on a synced pruned
// node: 5-15 seconds.
//
// Usage — three modes:
//
// 1. LOCAL (Windows desktop, plain http, no TLS — fastest to get running):
// $env:DGB_RPC_URL='http://127.0.0.1:14022'
// $env:DGB_RPC_USER=...
// $env:DGB_RPC_PASSWORD=...
// npm run demo -w @dgb-wallet/rpc
//
// 2. BNS (remote VPS, TLS pinned via BCH BNS record — recommended for VPS):
// $env:DGB_RPC_BNS='rpc.dgb'
// $env:DGB_RPC_USER=...
// $env:DGB_RPC_PASSWORD=...
// npm run demo -w @dgb-wallet/rpc
//
// 3. DIRECT (remote VPS, TLS with explicit fingerprint, dev only):
// $env:DGB_RPC_URL='https://203.0.113.42/'
// $env:DGB_RPC_FINGERPRINT='abc123...def'
// $env:DGB_RPC_USER=...
// $env:DGB_RPC_PASSWORD=...
// npm run demo -w @dgb-wallet/rpc
import {
seedFromPhrase,
validateSeedPhrase,
rootFromSeed,
accountNode,
accountXpub as toXpub,
} from '@dgb-wallet/core';
import {
RpcClient,
descriptorForAccount,
getBlockchainInfo,
scanTxOutSet,
type Purpose,
} from '../src/index.js';
const bnsName = process.env.DGB_RPC_BNS;
const url = process.env.DGB_RPC_URL;
const fingerprint = process.env.DGB_RPC_FINGERPRINT;
const username = process.env.DGB_RPC_USER;
const password = process.env.DGB_RPC_PASSWORD;
if (!username || !password) {
console.error('Missing env: DGB_RPC_USER, DGB_RPC_PASSWORD');
console.error('Set either DGB_RPC_BNS=rpc.dgb (BNS mode) or DGB_RPC_URL=https://... plus DGB_RPC_FINGERPRINT (direct mode).');
console.error('Credentials are stored in /root/dgb-rpc-credentials.txt on the VPS after ops/setup.sh runs.');
process.exit(1);
}
if (!bnsName && !url) {
console.error('Set DGB_RPC_BNS (recommended, e.g. rpc.dgb) or DGB_RPC_URL (direct-URL fallback).');
process.exit(1);
}
const args = process.argv.slice(2);
const phrase =
args.length > 0
? args.join(' ').trim()
: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
if (!validateSeedPhrase(phrase)) {
console.error('Invalid BIP39 seed phrase.');
process.exit(1);
}
console.log('\n=== DigiByte.X Recovery Scan ===\n');
if (bnsName) {
console.log(`Backend: ${bnsName} (BNS mode — ip + tls resolved from BCH chain)`);
} else if (url && url.startsWith('http://')) {
console.log(`Backend: ${url} (LOCAL mode — plain http, localhost only)`);
} else {
console.log(`Backend: ${url} (direct-URL mode${fingerprint ? ', pinned fingerprint' : ', WebPKI — insecure for our design'})`);
}
console.log(`Seed: ${phrase.split(/\s+/).length}-word BIP39\n`);
const client = bnsName
? new RpcClient({ bnsName, username, password, timeoutMs: 30_000 })
: new RpcClient({ url: url!, fingerprintSha256: fingerprint ?? null, username, password, timeoutMs: 30_000 });
const info = await getBlockchainInfo(client);
console.log(`Node: chain=${info.chain}, blocks=${info.blocks.toLocaleString()}, pruned=${info.pruned}`);
console.log(`Progress: ${(info.verificationprogress * 100).toFixed(4)}%`);
if (info.initialblockdownload) {
console.log(' Warning: node still in initial block download; scan results may be incomplete.');
}
console.log();
const seed = await seedFromPhrase(phrase);
const root = rootFromSeed(seed);
const ACCOUNTS = 3;
const RANGE_END = 100; // scan indices 0..100 per (path, account, change)
const PURPOSES: Purpose[] = [44, 49, 84, 86];
// Build one big descriptor list; scantxoutset processes them all in one pass.
const descriptors: string[] = [];
for (const purpose of PURPOSES) {
for (let a = 0; a < ACCOUNTS; a++) {
const acct = accountNode(root, purpose, a);
const xpub = toXpub(acct);
descriptors.push(descriptorForAccount(purpose, xpub, 0, RANGE_END));
descriptors.push(descriptorForAccount(purpose, xpub, 1, RANGE_END));
}
}
console.log(`Scanning ${descriptors.length} descriptors × ${RANGE_END + 1} addresses each = ${descriptors.length * (RANGE_END + 1)} addresses...`);
const t0 = performance.now();
const result = await scanTxOutSet(
client,
descriptors.map((d) => ({ desc: d, range: RANGE_END })),
);
const elapsed = ((performance.now() - t0) / 1000).toFixed(1);
console.log(`Done in ${elapsed}s. UTXO set: ${result.txouts.toLocaleString()} entries at height ${result.height.toLocaleString()}.\n`);
if (result.unspents.length === 0) {
console.log('No UTXOs at any scanned address.');
console.log('(Expected for the public all-abandon test seed.)');
} else {
console.log(`Found ${result.unspents.length} UTXO(s):\n`);
for (const u of result.unspents) {
console.log(` ${u.txid}:${u.vout}`);
console.log(` amount: ${u.amount.toFixed(8)} DGB`);
console.log(` height: ${u.height.toLocaleString()}`);
console.log(` descriptor: ${u.desc}`);
console.log(` scriptPubKey: ${u.scriptPubKey}`);
console.log();
}
console.log(`Total: ${result.total_amount.toFixed(8)} DGB`);
console.log('\nThese UTXOs can be fed to @dgb-wallet/psbt to construct a spend.');
}