argus/src/generate-covenant-table.mjs

76 lines
3.4 KiB
JavaScript
Raw Normal View History

// 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 <cashaddr> --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 <cashaddr> --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"));
}