#!/usr/bin/env node /** * chipnet-mut-run.mjs — orchestrator for the post-deployment-mutability test. * * Runs the full scenario the user described: * 1) Founder issues MBA at fee 1%, rate 1000 (mut-deploy) * 2) User B claims (mut-claim) * 3) User B redeems (mut-redeem) * 4) Local analysis — show no admin path exists (mut-analyze) * 5) "Fee change to 2%" → fresh deployment (mut-deploy) * 6) User B claims at 2% (mut-claim) * 7) "Fee changes" to 3%, 4%, 50%, back to 1% (mut-analyze, local) * 8) "Rate changes" to 100, 10000 (mut-analyze, local) * 9) Final accounting (mut-analyze) * * Steps 7–9 are local-only because the empirical truth is identical to * step 5: each "change" is structurally a fresh contract. Broadcasting * 7 more identical-shape geneses would burn chipnet fees without adding * new information. The analyze script reports the would-be addresses * and validateMetadata verdicts for each. * * Each broadcast step is spawned as a sub-process — same code path as * a manual `npm run chipnet:mut-deploy` etc. State is shared via * `ChipnetTests/.chipnet-mut-state.json`. Failures in any step abort the run. */ import { spawn } from 'node:child_process'; import { join } from 'node:path'; const ROOT = join(import.meta.dirname, '..'); function runNode(script, env = {}) { return new Promise((resolve, reject) => { const args = ['--import', 'tsx', join(ROOT, 'scripts', script)]; const child = spawn(process.execPath, args, { cwd: ROOT, env: { ...process.env, ...env }, stdio: 'inherit', }); child.on('exit', (code) => { if (code === 0) resolve(); else reject(new Error(`${script} exited ${code}`)); }); child.on('error', reject); }); } function divider(ch = '#') { console.log(''); console.log(ch.repeat(80)); } async function main() { divider(); console.log('## MUTABILITY TEST — orchestrator'); console.log('## Founder = Wallet C (default), User = Wallet B (default)'); console.log('## Demonstrates that post-deployment fee/rate changes are structurally'); console.log('## impossible on the canonical contract.'); divider(); // Step 1: deploy MBA at 1% console.log('\n### Step 1/9 — Genesis MBA@1% (rate=1000, feeBps=100)\n'); await runNode('chipnet-mut-deploy.mjs', { LABEL: 'mba-1pc', FEE_BPS: '100', RATE: '1000', FOUNDER: 'c' }); // Step 2: B claims 55 units (gross=55,000, fee=550 — clears 546 dust) console.log('\n### Step 2/9 — User B claims 55 units from MBA@1%\n'); await runNode('chipnet-mut-claim.mjs', { LABEL: 'mba-1pc', USER: 'b', AMOUNT: '55' }); // Step 3: B redeems (P2PKH spend of FT UTXO, FT burned, BCH released) console.log('\n### Step 3/9 — User B redeems FT UTXO\n'); await runNode('chipnet-mut-redeem.mjs', { LABEL: 'mba-1pc', USER: 'b' }); // Step 4: analyze — show no admin path console.log('\n### Step 4/9 — Local analysis after baseline\n'); await runNode('chipnet-mut-analyze.mjs'); // Step 5: deploy MBA@2% (the "fee change" workaround — fresh contract) console.log('\n### Step 5/9 — "Fee change to 2%" = fresh genesis (rate=1000, feeBps=200)\n'); await runNode('chipnet-mut-deploy.mjs', { LABEL: 'mba-2pc', FEE_BPS: '200', RATE: '1000', FOUNDER: 'c' }); // Step 6: B claims 28 units from MBA@2% (gross=28,000, fee=560) console.log('\n### Step 6/9 — User B "continues to transact" — actually claims from MBA@2% (a different token)\n'); await runNode('chipnet-mut-claim.mjs', { LABEL: 'mba-2pc', USER: 'b', AMOUNT: '28' }); // Step 7+: rest is local — derive what each would-be change looks like console.log('\n### Step 7-9/9 — Local proof for 3%, 4%, 50%, back-to-1%, rate=100, rate=10000 + final accounting\n'); await runNode('chipnet-mut-analyze.mjs'); divider(); console.log('## ORCHESTRATOR COMPLETE'); console.log('## See .chipnet-mut-state.json for the full event log.'); divider(); } main().catch((err) => { console.error('ORCHESTRATOR FAILED:', err?.message ?? err); process.exit(1); });