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>
125 lines
7.1 KiB
JavaScript
125 lines
7.1 KiB
JavaScript
#!/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); });
|