Snapshot of accumulated work-in-progress on top of the baseline commit. This session — retire Tier B from the wallet shell (single-tier model, glossary "Tiers & trust"): - SendFlow.tsx: BCH-only; removed the non-functional Tier-B send path, the D-4/D-5 raw-BCH co-display, and the ADR-0013 friction ladder. - ArtemisionScreen.tsx: Create wizard collapsed to a single 5-step on-chain genesis flow; tier-selection step + Tier-B branch (prepareTierBImport) deleted. - QuickActions.tsx / SettingsScreen.tsx: receive note reworded; "Send friction thresholds" + "Tier explainer" rows removed; registry-trust note reworded. - Verified: tsc clean, vitest 74/74, full preview walkthrough, no console errors. Pre-existing uncommitted work also folded in: - USDm -> FIATm family rename/reorg (Parameters/FIATm/, design records) - ~130 chipnet + sim-fiatm scripts (catalytic-converter, hyperinflation backtests) - snapshots 0153-0168; wiki updates (glossary, decisions, index); ADR-0044/0045 - mockups + additional shell screens (Agora, Apps, MainScreen, AssetDetail, ...) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
158 lines
6.2 KiB
JavaScript
158 lines
6.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* deviant — Deviant wallet CLI entry point.
|
|
*
|
|
* Wires real keys + network + token registry to the testable CLI core
|
|
* (`src/cli/wallet-cli.ts`), then dispatches the parsed command.
|
|
*
|
|
* Run: node --import tsx scripts/deviant.mjs <command> [...]
|
|
* npm: npm run deviant -- <command> [...]
|
|
*
|
|
* Config (env or flags):
|
|
* --keys <path> WIF keys file (default .chipnet-keys.json). JSON: { wif }
|
|
* --network <net> chipnet | testnet4 | mainnet (default chipnet)
|
|
* --tokens <a,b,..> comma-separated token JSON files to load into the
|
|
* registry (default: the chipnet asm + asm2 files if present)
|
|
*
|
|
* Persistence: every token file under `.deviant/tokens/*.json` is loaded into
|
|
* the registry on every run, and `register <file>` writes new entries there —
|
|
* so a token you register stays named across sessions. (Lightweight
|
|
* file-backed persistence; a SQLite backend can replace it behind the same
|
|
* TokenRegistry interface.)
|
|
*
|
|
* Examples:
|
|
* node --import tsx scripts/deviant.mjs balance
|
|
* node --import tsx scripts/deviant.mjs register ./some-token.json
|
|
* node --import tsx scripts/deviant.mjs claim ASM 1000
|
|
* node --import tsx scripts/deviant.mjs swap ASM ASM2 100 --yes
|
|
* node --import tsx scripts/deviant.mjs send ASM 100 --to bchtest:z... --yes
|
|
*/
|
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { TestNetWallet, Wallet } from 'mainnet-js';
|
|
import { InMemoryTokenRegistry } from '../src/token/registry.js';
|
|
import { tokenToPortable } from '../src/token/portable.js';
|
|
import { connectWallet, tokenFileToTierA } from '../src/wallet/runtime.js';
|
|
import { registerToken } from '../src/wallet/register.js';
|
|
import { parseArgs, runCommand, CliError } from '../src/cli/wallet-cli.js';
|
|
|
|
const ROOT = join(import.meta.dirname, '..');
|
|
const TOKENS_DIR = join(ROOT, '.deviant', 'tokens');
|
|
|
|
/** Absolute-path resolver that respects already-absolute paths (POSIX + Windows). */
|
|
function abs(p) {
|
|
return p.startsWith('/') || /^[A-Za-z]:/.test(p) ? p : join(ROOT, p);
|
|
}
|
|
|
|
/** Build the registry: chipnet defaults (or --tokens) + the persistent dir. */
|
|
function buildRegistry(parsed) {
|
|
const tokenPaths = typeof parsed.flags.tokens === 'string'
|
|
? parsed.flags.tokens.split(',').map((p) => p.trim())
|
|
: ['ChipnetTests/.chipnet-asm-token.json', 'ChipnetTests/.chipnet-asm2-token.json'];
|
|
|
|
const registry = new InMemoryTokenRegistry();
|
|
|
|
// 1) Seed files (legacy chipnet genesis artifacts) — tier-A only.
|
|
for (const p of tokenPaths) {
|
|
const a = abs(p);
|
|
if (existsSync(a)) registry.add(tokenFileToTierA(JSON.parse(readFileSync(a, 'utf8'))), 'replace');
|
|
}
|
|
|
|
// 2) Persistent registered tokens — portable or chipnet shape, either tier.
|
|
if (existsSync(TOKENS_DIR)) {
|
|
for (const f of readdirSync(TOKENS_DIR)) {
|
|
if (!f.endsWith('.json')) continue;
|
|
try {
|
|
registerToken(JSON.parse(readFileSync(join(TOKENS_DIR, f), 'utf8')), registry, { policy: 'replace' });
|
|
} catch (err) {
|
|
console.error(`[warn] skipping ${f}: ${err?.message ?? err}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return registry;
|
|
}
|
|
|
|
/** `register <file>` — local-only: parse, add, persist. No chain needed. */
|
|
function handleRegister(parsed, registry) {
|
|
const file = parsed.positionals[0];
|
|
if (!file) throw new CliError('Usage: deviant register <file.json>');
|
|
const a = abs(file);
|
|
if (!existsSync(a)) throw new CliError(`File not found: ${a}`);
|
|
|
|
const raw = JSON.parse(readFileSync(a, 'utf8'));
|
|
const result = registerToken(raw, registry, { policy: 'replace' });
|
|
|
|
// Persist as portable JSON keyed by token id, so it reloads next run.
|
|
mkdirSync(TOKENS_DIR, { recursive: true });
|
|
const dest = join(TOKENS_DIR, `${result.token.id}.json`);
|
|
writeFileSync(dest, JSON.stringify(tokenToPortable(result.token), null, 2));
|
|
|
|
const lines = [result.summary, `Saved → ${dest}`];
|
|
if (result.confirmation) {
|
|
lines.push('', result.confirmation.headline, '', result.confirmation.body);
|
|
for (const w of result.confirmation.warnings) lines.push(` [${w.severity}] ${w.message}`);
|
|
}
|
|
console.log(lines.join('\n'));
|
|
}
|
|
|
|
async function main() {
|
|
const argv = process.argv.slice(2);
|
|
const parsed = parseArgs(argv);
|
|
|
|
const registry = buildRegistry(parsed);
|
|
|
|
// register is a local registry operation — no keys / network / wallet.
|
|
if (parsed.command === 'register') {
|
|
handleRegister(parsed, registry);
|
|
return;
|
|
}
|
|
|
|
// Resolve config from flags (with sensible chipnet defaults).
|
|
const network = typeof parsed.flags.network === 'string' ? parsed.flags.network : 'chipnet';
|
|
const keysPath = typeof parsed.flags.keys === 'string'
|
|
? parsed.flags.keys
|
|
: join(ROOT, '.chipnet-keys.json');
|
|
|
|
const { wif } = JSON.parse(readFileSync(keysPath, 'utf8'));
|
|
const w = network === 'mainnet' ? await Wallet.fromWIF(wif) : await TestNetWallet.fromWIF(wif);
|
|
|
|
const wallet = connectWallet({
|
|
registry,
|
|
network,
|
|
privateKey: w.privateKey,
|
|
cashaddr: w.cashaddr,
|
|
tokenAddress: w.tokenaddr,
|
|
mainnetWallet: w, // enables asset creation (genesis)
|
|
});
|
|
|
|
const result = await runCommand(
|
|
{ wallet, registry, ownTokenAddress: w.tokenaddr },
|
|
parsed,
|
|
);
|
|
|
|
// Auto-register a freshly created asset so it is immediately usable
|
|
// (claim/balance/redeem-all/…) without a separate `register` step.
|
|
if (result.createdToken) {
|
|
mkdirSync(TOKENS_DIR, { recursive: true });
|
|
const dest = join(TOKENS_DIR, `${result.createdToken.id}.json`);
|
|
writeFileSync(dest, JSON.stringify(tokenToPortable(result.createdToken), null, 2));
|
|
console.log(`\nRegistered ${result.createdToken.ticker} → ${dest}`);
|
|
}
|
|
|
|
console.log(result.output);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
// CliError and the typed plan errors (SwapPlanError, RedeemPlanError,
|
|
// RegisterError, …) all carry a human-readable message and, for plan
|
|
// errors, a string `.code`. These are expected validation failures — show
|
|
// the message, not a stack trace. Only truly unexpected errors get a dump.
|
|
if (err instanceof CliError || typeof err?.code === 'string') {
|
|
console.error(`Error: ${err.message}`);
|
|
} else {
|
|
console.error('FAILED:', err?.message ?? err);
|
|
if (err?.stack) console.error(err.stack);
|
|
}
|
|
process.exit(1);
|
|
});
|