#!/usr/bin/env node /** * chipnet-setup.mjs — step 3 of the Q4 validation plan (snapshot 0005). * * Derives or loads a deterministic chipnet keypair, prints the receive * address and balance, and tells the user how to fund it via faucet. * Idempotent: first run generates a fresh WIF and saves it to * `.chipnet-keys.json` (gitignored); subsequent runs reload that file and * just re-check the balance. * * Network: defaults to chipnet via mainnet-js's TestNetWallet (which uses * wss://chipnet.imaginary.cash:50004 internally — see snapshot 0007 for * the verification). testnet4 fallback would require overriding the * electrum endpoint via ElectrumNetworkProvider — not done here because * step 4+ have not surfaced a reason to switch yet. * * Human-in-the-loop: * 1. Run this script. It prints an address. * 2. Open the faucet URL and paste the address. * 3. Re-run this script to confirm balance > 0. * 4. Proceed to step 4 (chipnet-genesis.mjs). */ import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { TestNetWallet } from 'mainnet-js'; const ROOT = join(import.meta.dirname, '..'); const KEYS_FILE = join(ROOT, '.chipnet-keys.json'); const FAUCETS = [ // Primary, active as of 2026-05. 'https://tbch.googol.cash/', // Backup. Lower throughput; useful if the primary is rate-limited. 'https://bitcoincash-faucet.com/', ]; async function loadOrCreate() { if (existsSync(KEYS_FILE)) { const { wif } = JSON.parse(readFileSync(KEYS_FILE, 'utf8')); const w = await TestNetWallet.fromWIF(wif); return { wallet: w, fresh: false }; } const w = await TestNetWallet.newRandom(); const payload = { wif: w.privateKeyWif, cashaddr: w.cashaddr, tokenaddr: w.tokenaddr, generated: new Date().toISOString(), network: 'chipnet', note: 'Chipnet/testnet dev wallet. Never fund with real BCH.', }; writeFileSync(KEYS_FILE, JSON.stringify(payload, null, 2)); return { wallet: w, fresh: true }; } function divider() { console.log('='.repeat(72)); } async function main() { const { wallet, fresh } = await loadOrCreate(); const balance = await wallet.getBalance(); const sats = typeof balance === 'object' ? balance.sat : balance; divider(); console.log(fresh ? 'Generated new chipnet keypair.' : 'Loaded existing chipnet keypair.'); console.log(`Stored at: ${KEYS_FILE}`); divider(); console.log('Network: chipnet (via mainnet-js TestNetWallet default)'); console.log(`Receive (BCH): ${wallet.cashaddr}`); console.log(`Receive (tokens): ${wallet.tokenaddr}`); console.log(`Balance: ${sats} sats`); divider(); if (sats === 0 || sats === 0n) { console.log('Wallet is empty. Fund it via one of:'); for (const url of FAUCETS) console.log(` - ${url}`); console.log(''); console.log('Paste the BCH receive address (bchtest:...) into the faucet.'); console.log('Then re-run this script to confirm the balance.'); console.log('Minimum useful funding: ~50,000 sats (covers genesis + several mints + swap).'); process.exitCode = 2; return; } console.log('Wallet is funded — ready for step 4 (chipnet-genesis.mjs).'); } main().catch((err) => { console.error('FAILED:', err?.message ?? err); if (err?.stack) console.error(err.stack); process.exit(1); });