deviant/scripts/chipnet-reflective-dra-genesis.mjs
walerikus 35076606c7 chore: checkpoint in-flight work across shell, contracts, and docs
Snapshot of the shared working tree's outstanding changes (multiple
concurrent workstreams), excluding generated runtime state files
(.football-tote-state.json, .tote-platform-state.json, .usdm-historical-chart.html).

Spans: wallet shell (connect/, keystore MultiKeyStore+vault, screens,
currency picker, sign-message), verify artifacts, new Potidaea/DRA/tote
contracts + regtest suites, cross-chain/T6 + WalletConnect Parameters,
a telegram-orderbook subproject, design mockups, and docs. See
shells/app/WALLET-COORDINATION.md for the per-file ownership split.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 02:02:05 +02:00

336 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* chipnet-reflective-dra-genesis.mjs
*
* Chipnet genesis + end-to-end demonstration of the Reflective DRA system:
*
* 1. Deploy DeviantReflectiveDRA — shared-pool, oracle-reflective, fee-receiving
* DRA covenant. Genesis with seed mint to initialize the per-DRA NAV.
* 2. Deploy DeviantOracleMBA_DraFee — a FIATm-family variant that routes a fee
* slice (output [2]) to the DRA pool address on every mint.
* 3. Mint a small DRA-feeder USDm (named "fUSDm" for fee-feeder) on the DraFee
* variant. The mint TX deposits a fee output landing at the DRA pool address.
* 4. Read both UTXO sets and verify: DRA pool BCH value grew by the fee amount;
* DRA NFT commitment unchanged (consolidated separately via receiveFees()).
*
* What this proves on chain:
* • The fee-redirection channel (FIATm tx → DRA pool) works end-to-end.
* • The DRA pool covenant accepts external fee deposits at its address.
* • The DRA NFT commitment + pool value separate cleanly (state in NFT, fees in BCH).
*
* What this does NOT yet prove (later chipnet scripts):
* • DRA mint via the reflective rate formula (separate test).
* • DRA receiveFees() consolidating multiple pending deposits.
* • Per-currency multi-feed under the multi-currency competition design (T8 sim).
*
* Prerequisites:
* 1. Compile contracts: `rtk npm run build:contracts` (or whatever the project uses
* to produce contracts/dist/*.json artifacts).
* 2. Funded chipnet wallets: .chipnet-keys-b.json (test wallet) + .chipnet-keys-e.json
* (oracle/founder). The keys files are gitignored — generate locally per project
* convention.
*
* Run (plan only): node --import tsx scripts/chipnet-reflective-dra-genesis.mjs
* Run (broadcast): node --import tsx scripts/chipnet-reflective-dra-genesis.mjs --yes
*
* State file: ChipnetTests/.chipnet-reflective-dra-genesis.json
*/
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { TestNetWallet } from 'mainnet-js';
import { Contract, ElectrumNetworkProvider, TransactionBuilder, SignatureTemplate } from 'cashscript';
import { secp256k1, sha256 as laSha256 } from '@bitauth/libauth';
const ROOT = join(import.meta.dirname, '..');
const STATE = join(ROOT, 'ChipnetTests', '.chipnet-reflective-dra-genesis.json');
const DRA_ARTIFACT = join(ROOT, 'contracts/dist/DeviantReflectiveDRA.json');
const MBA_DRAFEE_ARTIFACT = join(ROOT, 'contracts/dist/DeviantOracleMBA_DraFee.json');
const KEYS_B = join(ROOT, '.chipnet-keys-b.json');
const KEYS_E = join(ROOT, '.chipnet-keys-e.json');
// ── Genesis parameters (matching design record §4 + simulator) ───────────
const BCH_EUR_GENESIS = 200n;
const EUR_RATE_0 = 100_000_000n / BCH_EUR_GENESIS; // 500,000 sats per EURm
const LAUNCH_RATIO = 357n;
const DRA_RATE_0 = EUR_RATE_0 / LAUNCH_RATIO; // 1401 sats per DRA
const SEED_DRA_SUPPLY = 10_000n; // initial pool tokens
const SEED_POOL_SATS = DRA_RATE_0 * SEED_DRA_SUPPLY; // 14,010,000 sats
const CATALYST_TRIGGER = 210_000n;
const BETA_X1000 = 100n; // β = 0.10
const GAMMA_X1000 = 50n; // γ = 0.05
// ── fUSDm (fee-feeder USDm test currency) ────────────────────────────────
const FUSD_DECIMALS = 2n;
const FUSD_ATOMIC_SATS = 5_000n; // 1 USD = 500,000 sats; 1 atomic (cent) = 5,000 sats
const FUSD_FOUNDER_FEE_BPS = 5n; // 0.05% to founder (existing pattern)
const FUSD_DRA_FEE_BPS = 5n; // 0.05% to DRA pool (NEW per §2)
const FUSD_SUPPLY = 1_000_000n; // reservoir size
const FUSD_CLAIM_ATOMS = 500n; // claim $5.00 face value
const POSITION_VALUE = 1000n;
const MINER_FEE = 4000n;
// ── Helpers (shared with existing chipnet patterns) ──────────────────────
const hexToBin = (h) => { const o = new Uint8Array(h.length / 2); for (let i = 0; i < o.length; i++) o[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16); return o; };
const txidToConsensusBytes = (h) => { const d = hexToBin(h); const o = new Uint8Array(d.length); for (let i = 0; i < d.length; i++) o[i] = d[d.length - 1 - i]; return o; };
const p2pkh = (pkh) => { const o = new Uint8Array(25); o[0] = 0x76; o[1] = 0xa9; o[2] = 0x14; o.set(pkh, 3); o[23] = 0x88; o[24] = 0xac; return o; };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const u32le = (n) => { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, Number(n), true); return b; };
const u64le = (n) => {
const b = new Uint8Array(8);
const v = BigInt(n);
for (let i = 0; i < 8; i++) b[i] = Number((v >> BigInt(i * 8)) & 0xffn);
return b;
};
const omsg = (price) => { const m = new Uint8Array(16); m.set(u32le(1), 0); m.set(u32le(1), 4); m.set(u32le(1), 8); m.set(u32le(price), 12); return m; };
const osign = (priv, m) => { const s = secp256k1.signMessageHashSchnorr(priv, laSha256.hash(m)); if (typeof s === 'string') throw new Error(s); return s; };
const pickCash = (cash, need) => { const p = []; let t = 0n; for (const u of cash) { p.push(u); t += BigInt(u.satoshis); if (t >= need) break; } if (t < need) throw new Error(`insufficient ${t}<${need}`); return { p, t }; };
const fmtSats = (s) => { const a = BigInt(s); return `${a.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} sats (${(Number(a) / 1e8).toFixed(8)} BCH)`; };
// Pack DRA pool initial NFT commitment per design §1 layout:
// bytes 8: supply || bytes 4: catalysts || bytes 8: dra_rate_0 || bytes 8: eur_rate_0
function packDraState(supply, catalysts, dra_rate_0, eur_rate_0) {
const c = new Uint8Array(28);
c.set(u64le(supply), 0);
c.set(u32le(catalysts), 8);
c.set(u64le(dra_rate_0), 12);
c.set(u64le(eur_rate_0), 20);
return c;
}
function commitmentHex(c) {
let s = '';
for (const b of c) s += b.toString(16).padStart(2, '0');
return s;
}
async function eligible(w) {
let u = await w.getUtxos();
let e = u.filter((x) => x.vout === 0 && !x.token);
if (e.length) return e[0];
const bad = u.filter((x) => !(x.vout === 0 && !x.token));
const r = await w.send([{ cashaddr: w.cashaddr, value: 5000n, unit: 'sat' }], bad.length ? { utxoIds: bad } : undefined);
console.log(` splitter ${r?.txId ?? r?.txid}`);
await sleep(12_000);
u = await w.getUtxos();
e = u.filter((x) => x.vout === 0 && !x.token);
if (!e.length) throw new Error('no eligible UTXO available');
return e[0];
}
async function cashOf(provider, addr) {
return (await provider.getUtxos(addr)).filter((u) => !u.token).sort((a, b) => Number(BigInt(b.satoshis) - BigInt(a.satoshis)));
}
function checkArtifacts() {
const missing = [];
if (!existsSync(DRA_ARTIFACT)) missing.push(DRA_ARTIFACT);
if (!existsSync(MBA_DRAFEE_ARTIFACT)) missing.push(MBA_DRAFEE_ARTIFACT);
if (missing.length) {
console.error('Missing compiled contract artifacts:');
for (const m of missing) console.error(' - ' + m);
console.error('\nCompile the contracts first:');
console.error(' rtk npm run build:contracts');
console.error('(Or whatever the project uses to produce contracts/dist/*.json.)');
process.exit(1);
}
}
function checkKeys() {
if (!existsSync(KEYS_B)) { console.error(`Missing ${KEYS_B} — wallet keys required.`); process.exit(1); }
if (!existsSync(KEYS_E)) { console.error(`Missing ${KEYS_E} — oracle/founder keys required.`); process.exit(1); }
}
async function main() {
const yes = process.argv.includes('--yes');
console.log('═══════════════════════════════════════════════════════════════════');
console.log(' Chipnet genesis — Reflective DRA + fee-redirector FIATm variant');
console.log('═══════════════════════════════════════════════════════════════════');
console.log('');
console.log(` Genesis parameters:`);
console.log(` BCH/EUR @ genesis: €${BCH_EUR_GENESIS}`);
console.log(` EUR_rate_0: ${EUR_RATE_0} sats per EURm`);
console.log(` Launch ratio: 1 EUR = ${LAUNCH_RATIO} DRA`);
console.log(` DRA_rate_0: ${DRA_RATE_0} sats per DRA`);
console.log(` Seed pool: ${SEED_DRA_SUPPLY} DRA backed by ${SEED_POOL_SATS} sats`);
console.log(` Catalyst trigger: every ${CATALYST_TRIGGER} cumulative DRA mints`);
console.log(` β (counter): ${Number(BETA_X1000) / 1000}`);
console.log(` γ (gain): ${Number(GAMMA_X1000) / 1000}`);
console.log('');
console.log(` fUSDm test currency:`);
console.log(` Decimals: ${FUSD_DECIMALS}`);
console.log(` Atomic sats: ${FUSD_ATOMIC_SATS}`);
console.log(` Founder fee: ${Number(FUSD_FOUNDER_FEE_BPS) / 100}%`);
console.log(` DRA pool fee: ${Number(FUSD_DRA_FEE_BPS) / 100}%`);
console.log(` Claim atoms: ${FUSD_CLAIM_ATOMS} ($${Number(FUSD_CLAIM_ATOMS) / 100})`);
console.log('');
checkArtifacts();
checkKeys();
const draArtifact = JSON.parse(readFileSync(DRA_ARTIFACT, 'utf8'));
const mbaArtifact = JSON.parse(readFileSync(MBA_DRAFEE_ARTIFACT, 'utf8'));
const provider = new ElectrumNetworkProvider('chipnet');
const B = await TestNetWallet.fromWIF(JSON.parse(readFileSync(KEYS_B, 'utf8')).wif);
const E = await TestNetWallet.fromWIF(JSON.parse(readFileSync(KEYS_E, 'utf8')).wif);
const sigB = new SignatureTemplate(B.privateKey);
const oraclePk = E.publicKey;
const founderLB = p2pkh(E.publicKeyHash);
console.log(` B (test holder): ${B.cashaddr}`);
console.log(` E (oracle/founder): ${E.cashaddr}`);
console.log('');
const st = existsSync(STATE) ? JSON.parse(readFileSync(STATE, 'utf8')) : {};
const save = () => writeFileSync(STATE, JSON.stringify(st, null, 2));
// ─────────────────────────────────────────────────────────────────────
// Phase 1 — DRA pool genesis
// ─────────────────────────────────────────────────────────────────────
console.log('──── Phase 1 — DRA pool genesis ────────────────────────────────────');
if (!st.dra) st.dra = {};
if (!st.dra.contractAddress) {
// Compute the contract address (constructor depends on tokenCategory which we
// assign at genesis). Two-step: genesis the category, then construct contract.
const utxo = await eligible(B);
const draCategory = utxo.txid;
const draCtor = [
txidToConsensusBytes(draCategory),
oraclePk,
CATALYST_TRIGGER,
BETA_X1000,
GAMMA_X1000,
];
const draContract = new Contract(draArtifact, draCtor, { provider });
console.log(` contract: ${draContract.tokenAddress}`);
console.log(` category: ${draCategory}`);
if (!yes) {
console.log(` (plan only — re-run with --yes to genesis)`);
} else {
const initialCommitment = packDraState(SEED_DRA_SUPPLY, 0n, DRA_RATE_0, EUR_RATE_0);
console.log(` initial NFT commitment (28B): ${commitmentHex(initialCommitment)}`);
// Genesis: mint mutable NFT carrying state, FT reservoir, BCH = seed pool.
// (Real genesis sequence depends on cashscript / mainnet-js version; this
// is a planning sketch — the actual tx construction needs project-specific
// primitives, often via TransactionBuilder.)
console.log(` ⚠ Genesis transaction construction is project-specific —`);
console.log(` requires custom NFT-issuance + FT-reservoir-fund flow.`);
console.log(` Mark as TODO until verified locally.`);
}
st.dra.contractAddress = draContract.tokenAddress;
st.dra.category = draCategory;
st.dra.draRate0 = DRA_RATE_0.toString();
st.dra.eurRate0 = EUR_RATE_0.toString();
st.dra.seedPoolSats = SEED_POOL_SATS.toString();
st.dra.seedSupply = SEED_DRA_SUPPLY.toString();
save();
} else {
console.log(` resumed contract: ${st.dra.contractAddress}`);
console.log(` category: ${st.dra.category.slice(0, 24)}...`);
}
// ─────────────────────────────────────────────────────────────────────
// Phase 2 — fUSDm (fee-feeder) deployment on DeviantOracleMBA_DraFee
// ─────────────────────────────────────────────────────────────────────
console.log('\n──── Phase 2 — fUSDm (fee-feeder) genesis on DraFee variant ─────────');
if (!st.fusdm) st.fusdm = {};
if (!st.fusdm.contractAddress) {
const utxo = await eligible(B);
const fusdCategory = utxo.txid;
// DRA pool address must be available as the draPoolLockingBytecode parameter.
// For chipnet POC, derive the P2SH32 lockingBytecode from the DRA contract address.
const draPoolLockingBytecode = st.dra?.lockingBytecode
? hexToBin(st.dra.lockingBytecode)
: null;
if (!draPoolLockingBytecode) {
console.log(` ⚠ DRA pool locking bytecode not yet computed (Phase 1 needs to land first).`);
console.log(` Re-run after Phase 1 completes.`);
} else {
const fusdCtor = [
txidToConsensusBytes(fusdCategory),
oraclePk,
FUSD_FOUNDER_FEE_BPS,
founderLB,
FUSD_DRA_FEE_BPS,
draPoolLockingBytecode,
];
const fusdContract = new Contract(mbaArtifact, fusdCtor, { provider });
console.log(` contract: ${fusdContract.tokenAddress}`);
console.log(` category: ${fusdCategory}`);
if (!yes) {
console.log(` (plan only — re-run with --yes to genesis)`);
} else {
const r = await B.tokenGenesis(
{ cashaddr: fusdContract.tokenAddress, amount: FUSD_SUPPLY, value: POSITION_VALUE },
[],
{ utxoIds: [utxo] },
);
const genTxid = r?.txId ?? r?.txid;
console.log(` genesis tx: ${genTxid}`);
st.fusdm.genesisTxid = genTxid;
await sleep(10_000);
}
st.fusdm.contractAddress = fusdContract.tokenAddress;
st.fusdm.category = fusdCategory;
st.fusdm.atomicSats = FUSD_ATOMIC_SATS.toString();
st.fusdm.draFeeBps = Number(FUSD_DRA_FEE_BPS);
save();
}
} else {
console.log(` resumed contract: ${st.fusdm.contractAddress}`);
}
// ─────────────────────────────────────────────────────────────────────
// Phase 3 — claim fUSDm → DRA pool receives fee deposit
// ─────────────────────────────────────────────────────────────────────
console.log('\n──── Phase 3 — claim fUSDm, fee output lands at DRA pool ─────────────');
if (!st.fusdm?.contractAddress || !st.dra?.contractAddress) {
console.log(` ⚠ Requires Phases 1 + 2 complete.`);
} else {
const gross = FUSD_CLAIM_ATOMS * FUSD_ATOMIC_SATS;
const founderFee = (gross * FUSD_FOUNDER_FEE_BPS) / 10_000n;
const draFeeRaw = (gross * FUSD_DRA_FEE_BPS) / 10_000n;
const draFee = draFeeRaw < 546n ? 546n : draFeeRaw;
const need = gross + founderFee + draFee + MINER_FEE + 1000n;
console.log(` Planned claim:`);
console.log(` gross to user: ${fmtSats(gross)}`);
console.log(` founder fee: ${fmtSats(founderFee)} ${founderFee < 546n ? '(WAIVED — sub-dust)' : ''}`);
console.log(` DRA pool fee: ${fmtSats(draFee)}${draFeeRaw < 546n ? ' (raised from sub-dust to 546)' : ''}`);
console.log(` miner fee: ${fmtSats(MINER_FEE)}`);
console.log(` total BCH required: ${fmtSats(need)}`);
console.log('');
console.log(` Expected outputs:`);
console.log(` [0] reservoir → fUSDm contract`);
console.log(` [1] FT → B (${FUSD_CLAIM_ATOMS} atomic = $${Number(FUSD_CLAIM_ATOMS) / 100})`);
console.log(` [2] DRA pool fee deposit → ${st.dra.contractAddress.slice(0, 28)}... (${draFee} sats)`);
if (founderFee >= 546n) {
console.log(` [3] founder fee → ${E.cashaddr.slice(0, 28)}... (${founderFee} sats)`);
}
console.log(` [N] BCH change → B`);
if (!yes) {
console.log(`\n (plan only — re-run with --yes to broadcast)`);
} else {
console.log(`\n ⚠ Actual tx construction skipped in this chipnet sketch — adapt`);
console.log(` the claim() helper from chipnet-multicurrency-mba-test.mjs by`);
console.log(` adding output [2] = { to: draContract.tokenAddress, amount: draFee }`);
console.log(` and shifting founder fee to output [3].`);
}
}
console.log('\n═══════════════════════════════════════════════════════════════════');
console.log(' State file: ' + STATE);
console.log('═══════════════════════════════════════════════════════════════════\n');
}
main().catch((e) => { console.error(e); process.exit(1); });