deviant/scripts/chipnet-create-cupd.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

125 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-cupd.mjs — the CAVEATED divisible Copper (CUPd).
*
* CUP's whole unit (1000 sats) sits at the CashTokens dust floor, so a divisible
* CUP necessarily produces sub-dust atomic outputs. We mint it anyway with a
* documented minimum-holding threshold (the user's explicit choice):
*
* atomic rate 100 sats/FT · decimals 1 → 1 CUPd = 1000 sats (== CUP value)
* smallest atomic FT = 0.1 CUPd = 100 sats (BELOW the 675-sat dust floor)
*
* Consequences (by design, not bugs):
* - SEND/hold minimum: a CUPd UTXO must clear dust → ≥ 7 atomic = 0.7 CUPd.
* So 1.4, 2.7, 0.8… all work; 0.1–0.6 alone do not (wallet refuses sub-dust).
* - CLAIM minimum: the 5% founder fee must clear 546 sats → backing ≥ 10,920
* → claim ≥ ~11 CUPd at a time. Acquire in bulk, then send fractions.
*
* Same DeviantDescFeeSupply schedule (5.0%→0.25%), founder = Wallet C, 2^63-1
* reservoir. Genesis + register in one shot.
*
* Run (plan): node --import tsx scripts/chipnet-create-cupd.mjs
* Run (broadcast): node --import tsx scripts/chipnet-create-cupd.mjs --yes
*/
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { TestNetWallet } from 'mainnet-js';
import { Contract, ElectrumNetworkProvider } from 'cashscript';
import { DESC_FEE_SUPPLY_BYTECODE_HASH } from '../src/verify/canonical.js';
const ROOT = join(import.meta.dirname, '..');
const STATE_FILE = join(ROOT, '.chipnet-divisible-metals.json'); // shared with GLDd/SLVd
const TOKENS_DIR = join(ROOT, '.deviant', 'tokens');
const ARTIFACT_PATH = join(ROOT, 'contracts/dist/DeviantDescFeeSupply.json');
const KEYS = join(ROOT, '.chipnet-keys-c.json');
const BASE_FEE_BPS = 500n, FEE_STEP_BPS = 50n, MIN_FEE_BPS = 25n;
const MAX_FT_SUPPLY = 9_223_372_036_854_775_807n;
const ASSET = {
ticker: 'CUPd', name: 'Copper (divisible)', decimals: 1,
atomicRate: 100n, // sub-floor: 1 CUPd = 100 * 10^1 = 1000 sats
step: 100_000_000n, // checkpoint (atomic), ~10M whole CUPd
};
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));
const NOW = Date.now();
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 produced no 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: {} };
mkdirSync(TOKENS_DIR, { recursive: true });
console.log(`\nCREATE CAVEATED DIVISIBLE COPPER — CUPd (founder ${founder.cashaddr})`);
console.log(` atomic ${ASSET.atomicRate} · decimals ${ASSET.decimals} → 1 CUPd = ${ASSET.atomicRate * 10n ** BigInt(ASSET.decimals)} sats`);
console.log(` SEND/hold min: 0.7 CUPd (700 sats ≥ 675 dust) · CLAIM min: ~11 CUPd (5% fee ≥ 546)`);
console.log(` enables 1.4, 2.7, 0.8 … ; refuses 0.1–0.6 standalone (sub-dust)\n`);
if (!yes) { console.log('(preview only — re-run with --yes to broadcast the immutable genesis + register)\n'); return; }
let entry = state.assets[ASSET.ticker];
if (!entry) {
const utxo = await ensureEligibleUtxo(founder);
const categoryId = utxo.txid;
const contract = new Contract(artifact, [
txidToConsensusBytes(categoryId), ASSET.atomicRate, MAX_FT_SUPPLY,
BASE_FEE_BPS, FEE_STEP_BPS, ASSET.step, MIN_FEE_BPS, founderLB,
], { provider });
const r = await founder.tokenGenesis(
{ cashaddr: contract.tokenAddress, amount: MAX_FT_SUPPLY, value: 1000n, nft: { capability: 'minting', commitment: '' } },
[], { utxoIds: [utxo] });
const genesisTxId = r?.txId ?? r?.txid;
console.log(` genesis TXID: ${genesisTxId} category ${categoryId}`);
entry = {
ticker: ASSET.ticker, name: ASSET.name, decimals: ASSET.decimals, categoryId,
contractAddress: contract.address, contractTokenAddress: contract.tokenAddress, genesisTxId,
backingRateSats: ASSET.atomicRate.toString(), initialReservoirFtSupply: MAX_FT_SUPPLY.toString(),
baseFeeBps: Number(BASE_FEE_BPS), feeStepBps: Number(FEE_STEP_BPS),
supplyStepSize: ASSET.step.toString(), minFeeBps: Number(MIN_FEE_BPS),
founderLockingBytecodeHex: founderLBHex, founderAddress: founder.cashaddr,
};
state.assets[ASSET.ticker] = entry;
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
await sleep(10_000);
} else {
console.log(` (already deployed: ${entry.contractTokenAddress})`);
}
const portable = {
version: 1, id: entry.categoryId, tier: 'A', name: entry.name, ticker: entry.ticker,
rateSats: entry.backingRateSats, decimals: entry.decimals,
createdAt: NOW, lastSeen: NOW, userVerified: false, pinned: false, hidden: false,
categoryId: entry.categoryId, founderFeeBps: entry.baseFeeBps,
founderLockingBytecodeHex: entry.founderLockingBytecodeHex, founderAddress: entry.founderAddress,
contractAddress: entry.contractAddress, contractTokenAddress: entry.contractTokenAddress,
contractBytecodeHash: DESC_FEE_SUPPLY_BYTECODE_HASH,
initialReservoirSupply: entry.initialReservoirFtSupply, genesisTxId: entry.genesisTxId,
feeSchedule: { direction: 'decreasing', baseFeeBps: entry.baseFeeBps, feeStepBps: entry.feeStepBps, supplyStepSize: entry.supplyStepSize, boundFeeBps: entry.minFeeBps },
};
writeFileSync(join(TOKENS_DIR, `${entry.categoryId}.json`), JSON.stringify(portable, null, 2));
console.log(` registered (decimals ${entry.decimals}) → .deviant/tokens/${entry.categoryId.slice(0, 12)}….json`);
console.log(`\n✓ CUPd live. Claim ≥11 in bulk, then send fractions ≥0.7.`);
}
main().catch((e) => { console.error('FAILED:', e?.message ?? e); if (e?.stack) console.error(e.stack); process.exit(1); });