// End-to-end recovery scan against real DigiByte mainnet via public // Electrum-DGB servers. Takes a seed phrase, derives addresses at // BIP44/49-modern/49-legacy/84/86, and reports live balance for each. // // This is the recovery-check-balance flow, real chain data, no local // node required. Feed the resulting UTXOs into @dgb-wallet/psbt and // you have a working end-to-end wallet. import { seedFromPhrase, validateSeedPhrase, rootFromSeed, accountNode, addressNode, p2pkhAddress, p2shP2wpkhAddress, p2shP2wpkhAddressesBoth, p2wpkhAddress, p2trAddress, } from '@dgb-wallet/core'; import { DEFAULT_SERVERS, connectAny, getBalances, getChainTip, listUnspent } from '../src/index.js'; 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.'); console.error('Usage: npm run demo -w @dgb-wallet/electrum -- "word1 word2 ... word12"'); console.error('(with no args, uses the public all-abandon BIP39 test vector)'); process.exit(1); } const ACCOUNTS = 1; const ADDRESSES_PER_ACCOUNT = 5; console.log('\n=== DigiByte Recovery Scan (real mainnet) ===\n'); console.log(`Seed: ${phrase.split(/\s+/).length}-word BIP39 (all-abandon test seed shown by default)`); console.log(`Accounts: 0..${ACCOUNTS - 1}`); console.log(`Depth: ${ADDRESSES_PER_ACCOUNT} receive addresses per account per path\n`); console.log('Connecting to a public Electrum-DGB server...'); let client; try { client = await connectAny(DEFAULT_SERVERS, { timeoutMs: 10_000 }); } catch (e) { console.error(`\nCould not reach any Electrum-DGB server:\n${(e as Error).message}\n`); console.error('Servers are community-run and may be temporarily offline.'); console.error('Update DEFAULT_SERVERS in src/servers.ts if you know of live peers.'); process.exit(1); } console.log(` connected: ${client.server.host}:${client.server.port} (${client.server.protocol})\n`); const tip = await getChainTip(client); console.log(`Chain tip: height ${tip.height.toLocaleString()}\n`); const seed = await seedFromPhrase(phrase); const root = rootFromSeed(seed); interface AddressJob { path: string; label: string; address: string; } const jobs: AddressJob[] = []; for (let a = 0; a < ACCOUNTS; a++) { const bip44 = accountNode(root, 44, a); const bip49 = accountNode(root, 49, a); const bip84 = accountNode(root, 84, a); const bip86 = accountNode(root, 86, a); for (let i = 0; i < ADDRESSES_PER_ACCOUNT; i++) { jobs.push({ path: `m/44'/20'/${a}'/0/${i}`, label: 'BIP44', address: p2pkhAddress(addressNode(bip44, 0, i)), }); jobs.push({ path: `m/49'/20'/${a}'/0/${i}`, label: 'BIP49-S', address: p2shP2wpkhAddress(addressNode(bip49, 0, i)), }); jobs.push({ path: `m/49'/20'/${a}'/0/${i}`, label: 'BIP49-3', address: p2shP2wpkhAddressesBoth(addressNode(bip49, 0, i)).legacy, }); jobs.push({ path: `m/84'/20'/${a}'/0/${i}`, label: 'BIP84', address: p2wpkhAddress(addressNode(bip84, 0, i)), }); jobs.push({ path: `m/86'/20'/${a}'/0/${i}`, label: 'BIP86', address: p2trAddress(addressNode(bip86, 0, i)), }); } } console.log(`Querying ${jobs.length} addresses in parallel over one connection...\n`); const balances = await getBalances(client, jobs.map((j) => j.address)); let totalConfirmed = 0; let totalUnconfirmed = 0; let anyWithFunds = false; console.log('label path address confirmed unconfirmed'); console.log('─'.repeat(148)); for (const j of jobs) { const b = balances.get(j.address)!; totalConfirmed += b.confirmed; totalUnconfirmed += b.unconfirmed; const marker = b.confirmed > 0 || b.unconfirmed > 0 ? ' ★' : ' '; if (b.confirmed > 0 || b.unconfirmed > 0) anyWithFunds = true; console.log( `${j.label.padEnd(9)} ${j.path.padEnd(24)} ${j.address.padEnd(66)} ${(b.confirmed / 1e8).toFixed(8).padStart(16)} DGB ${(b.unconfirmed / 1e8).toFixed(8).padStart(16)} DGB${marker}`, ); } console.log('─'.repeat(148)); console.log(` ${(totalConfirmed / 1e8).toFixed(8).padStart(16)} DGB ${(totalUnconfirmed / 1e8).toFixed(8).padStart(16)} DGB`); console.log(); if (anyWithFunds) { console.log('★ Addresses with funds — fetching UTXOs:\n'); for (const j of jobs) { const b = balances.get(j.address)!; if (b.confirmed === 0 && b.unconfirmed === 0) continue; const utxos = await listUnspent(client, j.address); for (const u of utxos) { console.log(` ${j.address}`); console.log(` ${u.txid}:${u.vout} value=${(u.value / 1e8).toFixed(8)} DGB height=${u.height}`); } } console.log(); console.log('These UTXOs can be fed to @dgb-wallet/psbt to construct a spend.'); } else { console.log('No funds at any scanned address (expected for the all-abandon test seed).'); console.log('Run with your own seed: npm run demo -w @dgb-wallet/electrum -- "your words here"'); console.log('(Reminder: never paste a real seed protecting funds anywhere but the wallet itself.)'); } console.log(); client.close();