#!/usr/bin/env node /** * compile-contracts.mjs — compiles CashScript sources to artifact JSON. * * Uses the cashscript SDK's compileFile. Output goes to contracts/dist/. * Run after editing any contracts/*.cash source. */ import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'; import { join } from 'node:path'; const ROOT = join(import.meta.dirname, '..'); const SRC = join(ROOT, 'contracts'); const OUT = join(SRC, 'dist'); // The trust-critical D-3 module (src/verify/canonical.ts) imports the canonical // templates as embedded JSON (so it runs in browsers/WebViews with no fs). Those // embedded copies MUST NOT drift from the compiled source, so we mirror them here // on every compile. Keep this list in lockstep with the imports in canonical.ts. const EMBED = join(ROOT, 'src', 'verify', 'artifacts'); const EMBEDDED = new Set([ 'DeviantOpenMintFT.json', 'DeviantOpenMintNBA.json', 'DeviantManagedMintFT.json', 'KatalogosRegistryV1.json', 'DeviantBondingCurveFT.json', 'DeviantEscFeeSupply.json', 'DeviantDescFeeSupply.json', 'OpenTote.json', 'ParimutuelToteOpen.json', 'ParimutuelToteOpenMBA.json', // Phase G.1 — voting-system canonical templates (snapshot 0127+; §6.15 roadmap). 'CitizenToken.json', 'EndorsementCovenant.json', 'RegistryState.json', 'ApplicationCovenant.json', 'ApplicationCovenantB.json', 'ApplicationCovenantC.json', 'ApplicationCovenantD.json', 'ApplicationCovenantE.json', 'BallotCovenant.json', 'ElderFreezeAction.json', 'ComplaintCovenant.json', ]); if (!existsSync(OUT)) mkdirSync(OUT, { recursive: true }); if (!existsSync(EMBED)) mkdirSync(EMBED, { recursive: true }); async function main() { // CashScript 0.12 split the compiler into a separate `cashc` package. // The SDK package (`cashscript`) is runtime-only and no longer exports // compileFile. let compileFile; try { ({ compileFile } = await import('cashc')); } catch { console.error('cashc not installed. Run `npm install` first.'); process.exit(1); } const sources = readdirSync(SRC).filter((f) => f.endsWith('.cash')); if (sources.length === 0) { console.log('No .cash sources found in contracts/'); return; } for (const src of sources) { const srcPath = join(SRC, src); // Skip contracts marked SUPERSEDED or EXPECTED-COMPILE-FAIL in their header // comments. Convention for files we keep on disk as reference (SUPERSEDED) or // as deliberate negative artifacts that document a Script limitation // (EXPECTED-COMPILE-FAIL) but should not break the build. const header = readFileSync(srcPath, 'utf8').slice(0, 600); const skipMatch = /SUPERSEDED|EXPECTED-COMPILE-FAIL/.exec(header); if (skipMatch) { console.log(`skipped ${src} (marked ${skipMatch[0]})`); continue; } try { const artifact = compileFile(srcPath); const jsonName = src.replace(/\.cash$/, '.json'); const json = JSON.stringify(artifact, null, 2); const outPath = join(OUT, jsonName); writeFileSync(outPath, json); console.log(`compiled ${src} -> ${outPath}`); // Mirror canonical templates into the embedded D-3 copy (see EMBED above). if (EMBEDDED.has(jsonName)) { writeFileSync(join(EMBED, jsonName), json); console.log(` embedded ${jsonName} -> ${join(EMBED, jsonName)}`); } } catch (err) { console.error(`FAILED ${src}:`, err.message); process.exitCode = 1; } } } main();