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.
138 lines
5.2 KiB
TypeScript
138 lines
5.2 KiB
TypeScript
// 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.');
|
||
}
|