// Prints the `covenant` block of a network record in src/lib/resolver-web.js. // // The resolver is a dependency-free single file: it cannot compile a contract // or import the artifact. What it needs to recognise a covenant-held name is // the covenant's CODE — every byte of the redeem script after the 26 state // bytes — so that code is written into the network table, and this script is // where it comes from. test/covenant-names.test.mjs fails if the table and the // contract ever disagree. // // node src/generate-covenant-table.mjs --fund --steward <33-byte pubkey hex> // // CODE = HEAD(tier) ‖ TAIL. Only the three prices differ between tiers, and // they are pushed first, so the table stores one short head per tier and the // long tail once. import { binToHex, cashAddressToLockingBytecode, flattenBinArray } from "@bitauth/libauth"; import { registryTerms, registryCode, REGISTRY_VERSION } from "./lib/registry-covenant.js"; import { pushInt } from "./lib/covenant.js"; import { TIERS } from "./lib/registry-lifecycle.js"; /** { version, fundLock, steward, blocksPerYear, graceBlocks, tiers: [{ maxLabel, renewalSats, head }], tail } */ export function covenantTable({ fundLock, stewardPk }) { let tail = null; let shared = null; const tiers = TIERS.map((t) => { const terms = registryTerms({ tier: t.tier, fundLock, stewardPk }); const code = binToHex(registryCode(terms)); const head = binToHex(flattenBinArray([ pushInt(terms.ownerPercentIdle), pushInt(terms.blocksPerYear), pushInt(terms.graceBlocks), pushInt(terms.floorSats), pushInt(terms.premiumSats), pushInt(terms.renewalSats), ])); if (!code.startsWith(head)) throw new Error("the code does not start with the tier's price pushes"); const rest = code.slice(head.length); if (tail !== null && rest !== tail) throw new Error("tiers differ in more than their prices"); tail = rest; shared = terms; return { maxLabel: Number.isFinite(t.maxLength) ? t.maxLength : 255, renewalSats: Number(terms.renewalSats), head }; }); return { version: REGISTRY_VERSION, fundLock: binToHex(shared.fundLock), steward: binToHex(shared.stewardPk), blocksPerYear: shared.blocksPerYear, graceBlocks: shared.graceBlocks, tiers, tail, }; } const isMain = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, "/").split("/").pop()); if (isMain) { const arg = (name) => { const i = process.argv.indexOf(`--${name}`); return i > 0 ? process.argv[i + 1] : null; }; const fund = arg("fund"), steward = arg("steward"); if (!fund || !steward) { console.error("usage: node src/generate-covenant-table.mjs --fund --steward <33-byte pubkey hex>"); process.exit(1); } const decoded = cashAddressToLockingBytecode(fund); if (typeof decoded === "string") { console.error(decoded); process.exit(1); } const t = covenantTable({ fundLock: decoded.bytecode, stewardPk: steward }); const lines = [ " covenant: {", ` version: ${t.version},`, ` fundLock: "${t.fundLock}",`, ` steward: "${t.steward}",`, ` blocksPerYear: ${t.blocksPerYear},`, ` graceBlocks: ${t.graceBlocks},`, " tiers: [", ...t.tiers.map((x) => ` { maxLabel: ${x.maxLabel}, renewalSats: ${x.renewalSats}, head: "${x.head}" },`), " ],", ` tail: "${t.tail}",`, " },", ]; console.log(lines.join("\n")); }