deviant/scripts/chipnet-create-metal-mbas.mjs
walerikus 518a3ce174 chore: initial project baseline — full Deviant codebase under version control
Brings the whole project into git (previously only the on-ramp feature and
a few parallel files were tracked). Includes: wallet core (src/), CashScript
contracts/, the Capacitor shell (shells/), scripts/, tests, and the docs
corpus (wiki/, snapshots/, Parameters/, Archive/, KnowledgeBase text).

Excluded via .gitignore: node_modules/, dist/, .env (secrets), testnet WIF
key files (.chipnet-*.json, devWallets.ts), KnowledgeBase/RAW media (~9.6 GB),
transcripts/ (session logs), and machine-local config (.claude/*.local,
*.lock). Secret-scanned before commit — no credentials in the tree.

Note: src/swapMath.ts, src/swapBuilder.ts, contracts/OpenMintToken.cash are
legacy (OP_RETURN era) but still imported; committed as-is, flagged for a
separate removal once de-referenced.

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

151 lines
7.1 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-create-metal-mbas.mjs — genesis 3 decreasing-fee MBAs (GLD/SLV/CUP)
* on the DeviantDescFeeSupply covenant (ADR-0029), founder = least-BCH wallet.
*
* Fee schedule (immutable, identical across all three):
* feeBps = max(500 - (issuedSoFar / supplyStepSize) * 50, 25)
* → 5.0% at genesis, -0.5% per checkpoint, floored at 0.25%.
* Sequence: 500,450,400,350,300,250,200,150,100,50,25(floor).
*
* The only per-asset difference is the checkpoint size (zero-ladder) and the
* flat backing rate:
* GLD rate 100000 sats/u checkpoint every 100,000 units
* SLV rate 10000 sats/u checkpoint every 1,000,000 units
* CUP rate 1000 sats/u checkpoint every 10,000,000 units
*
* Two-phase: prints the immutable plan and STOPS unless --yes is passed.
* Idempotent: a genesis already recorded in state is skipped on re-run.
*
* Run (plan): node --import tsx scripts/chipnet-create-metal-mbas.mjs
* Run (broadcast): node --import tsx scripts/chipnet-create-metal-mbas.mjs --yes
*/
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { TestNetWallet } from 'mainnet-js';
import { Contract, ElectrumNetworkProvider } from 'cashscript';
const ROOT = join(import.meta.dirname, '..');
const STATE_FILE = join(ROOT, '.chipnet-metal-mbas.json');
const ARTIFACT_PATH = join(ROOT, 'contracts/dist/DeviantDescFeeSupply.json');
const KEYS = join(ROOT, '.chipnet-keys-c.json'); // Wallet C — least BCH (founder)
// Immutable fee schedule shared by all three.
const BASE_FEE_BPS = 500n;
const FEE_STEP_BPS = 50n;
const MIN_FEE_BPS = 25n;
// CashTokens consensus ceiling for a category's fungible supply (signed int64
// max). Metals are non-consumable and circulate indefinitely, so the reservoir
// is seeded at the ceiling — "depletion" becomes a non-event. The fee ladder is
// unaffected: it steps off issuedSoFar (cumulative claims), not reservoir size.
const MAX_FT_SUPPLY = 9_223_372_036_854_775_807n; // 2^63 - 1
const ASSETS = [
{ ticker: 'GLD', name: 'Gold', rate: 100_000n, step: 100_000n, supply: MAX_FT_SUPPLY },
{ ticker: 'SLV', name: 'Silver', rate: 10_000n, step: 1_000_000n, supply: MAX_FT_SUPPLY },
{ ticker: 'CUP', name: 'Copper', rate: 1_000n, step: 10_000_000n, supply: MAX_FT_SUPPLY },
];
const yes = process.argv.includes('--yes');
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));
/** The full immutable fee ladder for an asset, as a display table. */
function feeLadder(step) {
const rows = [];
for (let n = 0; n <= 10; n++) {
let bps = BASE_FEE_BPS - BigInt(n) * FEE_STEP_BPS;
if (bps < MIN_FEE_BPS) bps = MIN_FEE_BPS;
rows.push(` ${String(n * Number(step)).padStart(12)} units issued → ${(Number(bps) / 100).toFixed(2)}%`);
}
return rows.join('\n');
}
/** Ensure a genesis-eligible (vout 0, plain BCH) UTXO exists, minting one if not. */
async function ensureEligibleUtxo(wallet) {
let utxos = await wallet.getUtxos();
let eligible = utxos.filter((u) => u.vout === 0 && !u.token);
if (eligible.length > 0) return eligible[0];
const ineligible = utxos.filter((u) => !(u.vout === 0 && !u.token));
const r = await wallet.send([{ cashaddr: wallet.cashaddr, value: 3000n, unit: 'sat' }], ineligible.length ? { utxoIds: ineligible } : undefined);
console.log(` splitter TXID: ${r?.txId ?? r?.txid}`);
await sleep(12_000);
utxos = await wallet.getUtxos();
eligible = utxos.filter((u) => u.vout === 0 && !u.token);
if (!eligible.length) throw new Error('splitter did not produce an eligible vout-0 UTXO');
return eligible[0];
}
async function main() {
const artifact = JSON.parse(readFileSync(ARTIFACT_PATH, 'utf8'));
const { wif } = JSON.parse(readFileSync(KEYS, 'utf8'));
const founder = await TestNetWallet.fromWIF(wif);
const provider = new ElectrumNetworkProvider('chipnet');
const founderLB = p2pkh(founder.publicKeyHash);
const founderLBHex = Buffer.from(founderLB).toString('hex');
const state = existsSync(STATE_FILE) ? JSON.parse(readFileSync(STATE_FILE, 'utf8')) : { founder: founder.cashaddr, assets: {} };
const ctor = (a, catBytes) => [catBytes, a.rate, a.supply, BASE_FEE_BPS, FEE_STEP_BPS, a.step, MIN_FEE_BPS, founderLB];
console.log(`\nCREATE DECREASING-FEE MBAs — GLD / SLV / CUP (DeviantDescFeeSupply, ADR-0029)`);
console.log(` Founder (fee recipient): ${founder.cashaddr} [least-BCH wallet C]`);
console.log(` Fee schedule (all 3): 5.0% → −0.5%/checkpoint → 0.25% floor`);
console.log(` baseFeeBps=${BASE_FEE_BPS} feeStepBps=${FEE_STEP_BPS} minFeeBps=${MIN_FEE_BPS}\n`);
for (const a of ASSETS) {
console.log(` [${a.ticker}] ${a.name}: rate ${a.rate} sats/u · checkpoint every ${a.step} units · reservoir ${a.supply}`);
console.log(feeLadder(a.step));
console.log('');
}
if (!yes) {
console.log(`(preview only — re-run with --yes to broadcast 3 immutable genesis transactions)\n`);
return;
}
for (const a of ASSETS) {
console.log(`\n=== Genesis ${a.ticker} (${a.name}) ===`);
if (state.assets[a.ticker]) {
console.log(` (already deployed: ${state.assets[a.ticker].contractTokenAddress})`);
continue;
}
const utxo = await ensureEligibleUtxo(founder);
const categoryId = utxo.txid;
const contract = new Contract(artifact, ctor(a, txidToConsensusBytes(categoryId)), { provider });
console.log(` contract: ${contract.tokenAddress}`);
console.log(` category: ${categoryId}`);
const r = await founder.tokenGenesis(
{ cashaddr: contract.tokenAddress, amount: a.supply, value: 1000n, nft: { capability: 'minting', commitment: '' } },
[], { utxoIds: [utxo] });
const genesisTxId = r?.txId ?? r?.txid;
console.log(` genesis TXID: ${genesisTxId}`);
state.assets[a.ticker] = {
ticker: a.ticker,
name: a.name,
categoryId,
contractTokenAddress: contract.tokenAddress,
genesisTxId,
backingRateSats: a.rate.toString(),
initialReservoirFtSupply: a.supply.toString(),
baseFeeBps: Number(BASE_FEE_BPS),
feeStepBps: Number(FEE_STEP_BPS),
supplyStepSize: a.step.toString(),
minFeeBps: Number(MIN_FEE_BPS),
founderLockingBytecodeHex: founderLBHex,
founderAddress: founder.cashaddr,
};
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
await sleep(10_000);
}
console.log(`\n✓ Done. State → ${STATE_FILE}`);
for (const t of Object.keys(state.assets)) {
const e = state.assets[t];
console.log(` ${t}: category ${e.categoryId} @ ${e.contractTokenAddress}`);
}
}
main().catch((e) => { console.error('FAILED:', e?.message ?? e); if (e?.stack) console.error(e.stack); process.exit(1); });