// One-off (idempotent): seed the chipnet TLD-certificate beacon with the // operator-confirmed initial TLD set. Reads what's already there via the wallet's // provider (so re-runs skip anything present), then mints a TREG for each // missing TLD. Verifies at the end by rebuilding the snapshot from beacon // history — the same code path the /tld-list.json endpoint uses. // // node src/seed-tld-beacon.mjs // // Design context: Decentralized.DNS/DESIGN-tld-registry.md §9 item 1. Chipnet // only — mainnet is a separate switch and needs the TLD covenant first. import { loadWallet } from "./lib/wallet.js"; import { registerTld, parseOpReturn } from "./lib/bns.js"; import { buildTldSnapshot } from "./lib/tld-index.js"; import { TLD_BEACON } from "./config.js"; // Operator-confirmed seed set (2026-08-20): shipped defaults minus // deviant/bcnr/silentmode, plus x for the coinspectrum.x work already in flight. // Extended 2026-08-29: asm, neo, gt, sc, sia, com; then dex, cex, nt; then dea. // The operator holds the countersignature key that would otherwise gate // ICANN-adjacent TLDs (.com, .sc, .gt, .neo) on mainnet; on chipnet no covenant // enforces the gate, so these are seeded directly under operator authority. // `.com` is HELD by the operator (excluded from the public registrar UI) so // nobody else can claim names under it — the TLD cert stays on-chain, but no // second-level registrations are offered. Future TUPD may set policy:"frozen" // once the TLD covenant lands and enforces it. const SEED_TLDS = [ "bch", "p2p", "bit", "nav", "test", "x", "asm", "neo", "gt", "sc", "sia", "com", "dex", "cex", "nt", "dea", "hub", "os", "vpn", "cash", ]; // Empty records for the seed — the TLD covenant work will populate // policy/fee_bps/reserved later. Empty means "defaults apply everywhere", // which per the design doc §3 is {policy:"open", fee_bps:500, min_len:1}. const SEED_RECORDS = {}; const wallet = await loadWallet("main"); console.log(`from wallet : ${wallet.cashaddr}`); console.log(`TLD beacon : ${TLD_BEACON}`); console.log(`target TLD set : ${SEED_TLDS.join(" ")}`); // Fetch what's already on the beacon so re-runs skip it. console.log("\nfetching current TLD-beacon state …"); const history = await wallet.provider.getHistory(TLD_BEACON); const txs = new Map(); for (const { tx_hash } of history) { try { txs.set(tx_hash, await wallet.provider.getRawTransactionObject(tx_hash)); } catch { /* unreadable — skip */ } } const before = buildTldSnapshot({ history, txs }); console.log(`present : ${before.tlds.length ? before.tlds.map((t) => t.tld).join(" ") : "(none)"}`); const existing = new Set(before.tlds.map((t) => t.tld)); const toMint = SEED_TLDS.filter((t) => !existing.has(t)); if (!toMint.length) { console.log("\nnothing to do — all target TLDs already registered."); process.exit(0); } console.log(`will register : ${toMint.join(" ")} (${toMint.length} tx)`); const bal = await wallet.getBalance("sat"); console.log(`wallet balance : ${bal} sat`); const results = []; for (const tld of toMint) { process.stdout.write(` TREG ${tld} … `); try { const res = await registerTld(wallet, tld, SEED_RECORDS); results.push({ tld, ok: true, ...res }); console.log(`ok txid=${res.txid} category=${res.category?.slice(0, 16)}…`); } catch (e) { results.push({ tld, ok: false, error: String(e && e.message || e) }); console.log(`FAIL ${e && e.message || e}`); } // A short pause so a chain query between two REGs sees the previous tx. await new Promise((r) => setTimeout(r, 500)); } const failed = results.filter((r) => !r.ok); console.log(`\n${results.length - failed.length}/${results.length} TREG(s) broadcast${failed.length ? ` (${failed.length} failed)` : ""}`); // Verify by rebuilding the snapshot from the beacon. console.log("\nverifying via TLD-beacon snapshot rebuild …"); await new Promise((r) => setTimeout(r, 2000)); // mempool propagation const historyAfter = await wallet.provider.getHistory(TLD_BEACON); const txsAfter = new Map(); for (const { tx_hash } of historyAfter) { try { txsAfter.set(tx_hash, await wallet.provider.getRawTransactionObject(tx_hash)); } catch { /* skip */ } } const after = buildTldSnapshot({ history: historyAfter, txs: txsAfter }); console.log(`snapshot root : ${after.root}`); console.log(`snapshot tlds :`); for (const t of after.tlds) { console.log(` - ${t.tld.padEnd(6)} cat=${t.category.slice(0, 16)}… regHeight=${t.regHeight ?? "(mempool)"} txid=${t.regTxid.slice(0, 16)}…`); } const missing = SEED_TLDS.filter((t) => !after.tlds.some((s) => s.tld === t)); if (missing.length) { console.log(`\nWARNING: still missing after broadcast: ${missing.join(" ")}`); process.exit(1); } console.log(`\ndone — all ${SEED_TLDS.length} target TLDs present on the TLD beacon.`); process.exit(0);