A name whose certificate sits in the registry covenant has an owner and an expiry that are hashed into its address, not written on the chain. Until now a resolver saw only "held by some script address", forever. The transaction that moves the certificate now says the state (`c` in the payload) and the resolver checks it against the address, using the covenant's code from the network table. Nobody is trusted about who owns such a name or until when. Entering is the one step script cannot police - a certificate can be sent to a covenant address with any expiry - so resolvers set the price: the entering transaction pays the fund for whole years and may claim no more than it paid for. Once a name is inside, only a verified state speaks for it; otherwise an owner could announce their way back to a name that never expires. A name past its grace period stays taken but resolves to nothing, judged at the chain tip (the indexer now answers what that is) or, failing that, at the newest block in the beacon history, so a stale index lapses a name late and never early. Same rules in the gateway, the Ariadne resolver and the Android resolver; the builders announce every move, and there is one to put an existing name into the covenant. Chipnet's fund and steward are the operator key.
75 lines
3.4 KiB
JavaScript
75 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"));
|
|
}
|