76 lines
3.4 KiB
JavaScript
76 lines
3.4 KiB
JavaScript
|
|
// 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"));
|
||
|
|
}
|