// 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.'); }