#!/usr/bin/env node
// aere-pqc.js, the unified Aere Quantum Migration Toolkit CLI. One entry point
// wrapping the whole product: scan an account, simulate fleet migration cost,
// build an unsigned migration transaction sequence, and print a per-account
// quantum-readiness report.
//
// aere-pqc scan
--rpc
// aere-pqc simulate --count 100 --scheme hybrid --auths 5
// aere-pqc build-migration --eoa 0x.. --falcon 0x09.. [--path direct|migrator]
// aere-pqc readiness --rpc [--tokens 0x..,0x..] [--scheme ..]
//
// Nothing in this CLI signs, deploys, or broadcasts. The only network calls are
// read-only JSON-RPC (eth_getCode/getBalance/call/estimateGas/gasPrice).
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { scanLive, scanBytecode, isValidAddress } from './lib/scanner.js';
import { simulateMigration, formatSimulation, schemeList } from './lib/simulate.js';
import {
buildMigrationTransactions, enumerateAssets, estimateMigrationGas, predictTargetAddress,
} from './lib/migrator.js';
import { buildReadinessReport, formatReadinessReport, readinessSchemeList } from './lib/readiness.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DEFAULT_RPC = 'https://rpc.aere.network';
const isTTY = process.stdout.isTTY;
const C = isTTY
? { b: (s) => `\x1b[1m${s}\x1b[0m`, dim: (s) => `\x1b[2m${s}\x1b[0m`, g: (s) => `\x1b[32m${s}\x1b[0m`, y: (s) => `\x1b[33m${s}\x1b[0m` }
: { b: (s) => s, dim: (s) => s, g: (s) => s, y: (s) => s };
function loadSampleKey() {
const p = join(__dirname, '..', 'contracts', 'deployments', 'pqc-account.json');
return JSON.parse(readFileSync(p, 'utf8')).sampleAccount.falconPubKey;
}
function parseArgs(argv) {
const a = { _: [] };
for (let i = 0; i < argv.length; i++) {
const t = argv[i];
if (t.startsWith('--')) {
const key = t.slice(2);
const next = argv[i + 1];
if (next === undefined || next.startsWith('--')) { a[key] = true; }
else { a[key] = next; i++; }
} else a._.push(t);
}
return a;
}
function topUsage(code = 0) {
console.log(`${C.b('Aere Quantum Migration Toolkit')} (chain 2800)
Commands
${C.b('scan')} --rpc classify quantum exposure (RED/YELLOW/GREEN)
${C.b('simulate')} --count [--scheme ] fleet migration-cost projection
${C.b('build-migration')} --eoa 0x.. --falcon 0x09.. build UNSIGNED migration txs
${C.b('readiness')} --rpc one-account readiness + cost report
Global
--rpc JSON-RPC endpoint (default ${DEFAULT_RPC})
--json machine-readable output
--help this help; or " --help"
Scope: this classifies and CONSTRUCTS/SIMULATES migration of ACCOUNT/APPLICATION
quantum exposure. It never signs, deploys, or moves funds, and it does NOT make
Aere consensus post-quantum (2800 seals with classical secp256k1 QBFT).`);
process.exit(code);
}
// ── scan ─────────────────────────────────────────────────────────────────────
async function cmdScan(args) {
const addr = args._[0];
const rpc = args.rpc === true ? DEFAULT_RPC : (args.rpc || DEFAULT_RPC);
let report;
if (args.fixture) {
const path = join(__dirname, 'fixtures', args.fixture + '.hex');
report = scanBytecode(`fixture:${args.fixture}`, readFileSync(path, 'utf8').trim());
} else if (args.bytecode) {
report = scanBytecode(addr || '(provided bytecode)', args.bytecode);
} else if (addr && isValidAddress(addr)) {
report = await scanLive(addr, rpc);
} else {
console.error('scan needs --rpc , or --fixture , or --bytecode 0x..');
process.exit(1);
}
if (args.json) { console.log(JSON.stringify(report, (_k, v) => (typeof v === 'bigint' ? '0x' + v.toString(16) : v), 2)); return; }
const badge = report.readiness === 'GREEN' ? C.g(' GREEN ') : report.readiness === 'YELLOW' ? C.y(' YELLOW ') : ' RED ';
console.log(`\n[${badge}] ${C.b(report.address)}`);
console.log(` kind: ${report.kind}${report.live ? ` chainId: ${report.live.chainId} code: ${report.live.codeBytes} bytes` : ''}`);
if (report.reason) console.log(` ${C.dim(report.reason)}`);
if (report.migration) console.log(` ${C.b('migration')}: ${report.migration}`);
console.log('');
}
// ── simulate ─────────────────────────────────────────────────────────────────
function cmdSimulate(args) {
if (args.schemes) { console.log(schemeList().join('\n')); return; }
let accounts;
if (args.accounts) {
accounts = JSON.parse(readFileSync(args.accounts, 'utf8'));
} else {
const count = parseInt(args.count, 10) || 1;
accounts = Array.from({ length: count }, () => ({
scheme: args.scheme || 'falcon512',
authsPerAccount: args.auths ? parseInt(args.auths, 10) : 1,
deployAccount: !args['no-deploy'],
}));
}
const sim = simulateMigration(accounts, { gweiPrice: args.gwei ? parseFloat(args.gwei) : undefined });
if (args.json) { console.log(JSON.stringify(sim, null, 2)); return; }
console.log('\n' + formatSimulation(sim) + '\n');
}
// ── build-migration ──────────────────────────────────────────────────────────
async function cmdBuildMigration(args) {
const falcon = args.falcon && args.falcon !== true ? args.falcon : loadSampleKey();
const usingSample = !(args.falcon && args.falcon !== true);
const eoa = args.eoa && args.eoa !== true ? args.eoa : '0x000000000000000000000000000000000000dEaD';
const usingDemoEoa = !(args.eoa && args.eoa !== true);
const salt = args.salt ? parseInt(args.salt, 10) : 0;
const path = args.path && args.path !== true ? args.path : 'direct';
const rpc = args.rpc === true ? DEFAULT_RPC : args.rpc;
// asset selection: explicit --tokens list (with optional --amounts) or, if
// --rpc is given, enumerate real balances read-only.
let tokens = [];
let nativeWei = 0n;
const tokenAddrs = args.tokens && args.tokens !== true ? String(args.tokens).split(',').map((s) => s.trim()).filter(Boolean) : [];
let enumerated = null;
// When moving native AERE from the SAME EOA that pays gas, you cannot move the
// ENTIRE balance: the tx itself needs gas. Reserve a buffer (default 0.01 AERE,
// override with --native-reserve ) so the native move can actually settle.
const nativeReserve = args['native-reserve'] && args['native-reserve'] !== true
? BigInt(args['native-reserve']) : 10000000000000000n; // 1e16 wei = 0.01 AERE
let reservedNote = null;
const explicitNative = args.native && args.native !== true ? BigInt(args.native) : null;
// native amount: explicit --native wins everywhere; else --no-native = 0; else
// (only with live enumeration) auto-drain balance minus a gas reserve.
if (explicitNative != null) nativeWei = explicitNative;
else if (args['no-native']) nativeWei = 0n;
if (rpc && !usingDemoEoa) {
enumerated = await enumerateAssets({ rpcUrl: rpc, owner: eoa, tokens: tokenAddrs });
const full = BigInt(enumerated.native.balanceWei);
if (explicitNative != null) {
reservedNote = `native move = explicit --native ${nativeWei} wei (current balance ${full} wei).`;
} else if (!args['no-native']) {
if (path === 'direct') {
nativeWei = full > nativeReserve ? full - nativeReserve : 0n;
if (full > 0n) reservedNote = `native move = balance ${full} minus a ${nativeReserve} wei gas reserve = ${nativeWei} wei. NOTE: draining an ACTIVE account races its own spending; prefer --native for a fixed amount.`;
} else {
nativeWei = full; // migrator forwards msg.value; the EOA still pays gas, so leave headroom beyond this
if (full > 0n) reservedNote = `native move = full balance ${full} wei; ensure the migrate tx has gas headroom beyond this (migrator forwards msg.value).`;
}
}
tokens = enumerated.tokens.filter((t) => t.needsMove).map((t) => ({ token: t.token, amountWei: t.balance }));
} else if (tokenAddrs.length && args.amounts && args.amounts !== true) {
const amts = String(args.amounts).split(',').map((s) => s.trim());
tokens = tokenAddrs.map((t, i) => ({ token: t, amountWei: BigInt(amts[i] || '0') }));
} else {
tokens = tokenAddrs.map((t) => ({ token: t, amountWei: 1n })); // placeholder amount; user sets real value
}
const plan = buildMigrationTransactions({
path, eoa, falconPubKey: falcon, salt,
tokens, nativeWei,
deployAccount: !args['no-deploy'],
migrator: args.migrator && args.migrator !== true ? args.migrator : undefined,
bindFalcon: !args['no-bind'],
});
if (rpc && args.estimate) await estimateMigrationGas(plan, rpc);
if (args.json) { console.log(JSON.stringify(plan, (_k, v) => (typeof v === 'bigint' ? v.toString() : v), 2)); return; }
console.log(`\n${C.b('Aere post-quantum migration')} (path: ${plan.path})`);
if (usingSample) console.log(C.dim(" (demo: target derived from the live sample account's real Falcon-512 key)"));
if (usingDemoEoa) console.log(C.dim(' (demo EOA 0x..dEaD; pass --eoa 0xYourAccount for a real build)'));
console.log(` from EOA: ${plan.eoa}`);
console.log(` factory (live): ${plan.factory}`);
console.log(` target account: ${C.b(plan.target)}`);
if (enumerated) console.log(` enumerated: ${enumerated.movable} movable asset(s) via ${rpc}`);
if (reservedNote) console.log(` ${C.dim(reservedNote)}`);
console.log('');
console.log(` ${C.b('Unsigned transactions')} (${plan.transactions.length}). Sign each in your own wallet, in order:`);
for (const item of plan.transactions) {
console.log(`\n Step ${item.step}. [${item.kind}] ${C.dim('(signed: ' + item.signed + ')')}`);
console.log(` ${item.description}`);
console.log(` to: ${item.tx.to}`);
console.log(` value: ${item.tx.value}`);
console.log(` data: ${item.tx.data.length > 50 ? item.tx.data.slice(0, 50) + '..' : item.tx.data} (${(item.tx.data.length - 2) / 2} bytes)`);
if (item.gasEstimate != null) console.log(` gas (live estimate): ${item.gasEstimate.toLocaleString()}`);
if (item.gasEstimateError) console.log(` gas: ${C.dim('estimate n/a (' + item.gasEstimateError + ')')}`);
for (const w of item.warnings || []) console.log(` ${C.y('warn')}: ${w}`);
}
if (plan.warnings.length) {
console.log(`\n ${C.b('Warnings')}:`);
for (const w of plan.warnings) console.log(` - ${w}`);
}
console.log(`\n ${C.dim(plan.scope)}\n`);
}
// ── readiness ────────────────────────────────────────────────────────────────
async function cmdReadiness(args) {
const addr = args._[0];
if (!addr || !isValidAddress(addr)) { console.error('readiness needs '); process.exit(1); }
const rpc = args.rpc === true ? DEFAULT_RPC : (args.rpc || DEFAULT_RPC);
const scan = await scanLive(addr, rpc);
const tokenAddrs = args.tokens && args.tokens !== true ? String(args.tokens).split(',').map((s) => s.trim()).filter(Boolean) : [];
const assets = await enumerateAssets({ rpcUrl: rpc, owner: addr, tokens: tokenAddrs });
const report = buildReadinessReport({
scan, assets,
scheme: args.scheme && args.scheme !== true ? args.scheme : 'falcon512_solidity',
path: args.path && args.path !== true ? args.path : 'direct',
projectedAuths: args.auths ? parseInt(args.auths, 10) : 1,
gwei: args.gwei ? parseFloat(args.gwei) : undefined,
});
if (args.json) { console.log(JSON.stringify(report, null, 2)); return; }
console.log(formatReadinessReport(report));
}
async function main() {
const argv = process.argv.slice(2);
const cmd = argv[0];
if (!cmd || cmd === '--help' || cmd === '-h') topUsage(0);
const args = parseArgs(argv.slice(1));
if (args.help) {
console.log(`See: aere-pqc ${cmd} ... (schemes: sim=${schemeList().join(',')} ; readiness=${readinessSchemeList().join(',')})`);
return;
}
try {
if (cmd === 'scan') await cmdScan(args);
else if (cmd === 'simulate') cmdSimulate(args);
else if (cmd === 'build-migration') await cmdBuildMigration(args);
else if (cmd === 'readiness') await cmdReadiness(args);
else { console.error(`unknown command: ${cmd}`); topUsage(1); }
} catch (e) {
console.error(`error: ${e.message}`);
process.exit(1);
}
}
main();