// Lifecycle, pricing and the payment split. // // These are the rules the covenant will encode, so this file doubles as the // fixture the CashScript contract must reproduce exactly. Several tests assert // economic properties rather than arithmetic — no surplus cliff, the owner always // holds the cheap option, the split never loses a satoshi — because those are the // ones that quietly stop being true when a constant gets edited. import test from "node:test"; import assert from "node:assert/strict"; import { PHASE, phaseAt, expiryFor, idleEndFor, BLOCKS_PER_YEAR, GRACE_BLOCKS, GRACE_MONTHS, TIERS, tierForName, tierConfig, renewalPriceSats, idlePremiumSats, openFloorSats, takeoverPriceSats, reclaimPriceSats, SPLIT, splitTakeover, COMMIT, commitIsMature, FUND, describeLifecycle, IDLE_PREMIUM_MULTIPLE, OPEN_FLOOR_MULTIPLE, } from "../src/lib/registry-lifecycle.js"; const START = 900_000; const EXPIRY = expiryFor(START, 1); const IDLE_END = idleEndFor(EXPIRY); const TIER = 3; // the tier the design's $20 / $1000 / $100 figures describe // ---------- phases ---------- test("phase boundaries are exact, not approximate", () => { assert.equal(phaseAt({ height: EXPIRY - 1, expiryHeight: EXPIRY, idleEndHeight: IDLE_END }), PHASE.active); assert.equal(phaseAt({ height: EXPIRY, expiryHeight: EXPIRY, idleEndHeight: IDLE_END }), PHASE.idle); assert.equal(phaseAt({ height: IDLE_END - 1, expiryHeight: EXPIRY, idleEndHeight: IDLE_END }), PHASE.idle); assert.equal(phaseAt({ height: IDLE_END, expiryHeight: EXPIRY, idleEndHeight: IDLE_END }), PHASE.open); }); test("grace is 6 months, not 12 — the namespace has to actually recycle", () => { assert.equal(GRACE_MONTHS, 6); assert.equal(IDLE_END - EXPIRY, GRACE_BLOCKS); // A 1-year name is unavailable for 18 months after last use, not 24. assert.equal(IDLE_END - START, BLOCKS_PER_YEAR + GRACE_BLOCKS); assert.ok(IDLE_END - START < BLOCKS_PER_YEAR * 2); }); // ---------- tiers ---------- test("tier follows label length and ignores the TLD", () => { assert.equal(tierForName("ab"), 1); assert.equal(tierForName("abcd"), 2); assert.equal(tierForName("abcdefg"), 3); assert.equal(tierForName("abcdefgh"), 4); assert.equal(tierForName("sirius.nav"), 3, "the TLD must not shift the tier"); assert.equal(tierForName("sirius"), tierForName("sirius.nav")); }); test("tier 3 lands on the design's $20 / $1000 / $100 figures", () => { assert.equal(renewalPriceSats(TIER), 200_000n); assert.equal(idlePremiumSats(TIER), 10_000_000n); // 50x assert.equal(openFloorSats(TIER), 1_000_000n); // 5x assert.equal(IDLE_PREMIUM_MULTIPLE, 50n); assert.equal(OPEN_FLOOR_MULTIPLE, 5n); }); test("shorter names cost more, monotonically", () => { const prices = TIERS.map((t) => t.renewalSats); for (let i = 1; i < prices.length; i++) { assert.ok(prices[i] < prices[i - 1], `tier ${i + 1} must be cheaper than tier ${i}`); } }); test("multi-year renewal is a straight multiple", () => { assert.equal(renewalPriceSats(TIER, 3), renewalPriceSats(TIER) * 3n); }); // ---------- the decay curve ---------- const priceAt = (height) => takeoverPriceSats({ height, expiryHeight: EXPIRY, idleEndHeight: IDLE_END, tier: TIER }); test("an ACTIVE name is not for sale at any price", () => { assert.equal(priceAt(EXPIRY - 1), null); assert.equal(priceAt(START), null); }); test("takeover starts at the premium and ends at the floor", () => { assert.equal(priceAt(EXPIRY), idlePremiumSats(TIER)); assert.equal(priceAt(IDLE_END), openFloorSats(TIER)); assert.equal(priceAt(IDLE_END + 100_000), openFloorSats(TIER), "the floor holds indefinitely"); }); test("the price only ever falls — never rises, never plateaus mid-glide", () => { let previous = priceAt(EXPIRY); for (let h = EXPIRY + 100; h < IDLE_END; h += 500) { const p = priceAt(h); assert.ok(p < previous, `price rose or stalled at height ${h}`); previous = p; } }); test("NO SURPLUS CLIFF at the IDLE→OPEN boundary", () => { // The whole reason for a glide instead of a step. If the price fell sharply at // one block, every bidder would submit in that block and the winner would be // whoever paid the biggest miner fee — the outcome the pricing exists to avoid. const justBefore = priceAt(IDLE_END - 1); const atBoundary = priceAt(IDLE_END); const gap = justBefore - atBoundary; const floor = openFloorSats(TIER); assert.ok(gap >= 0n); assert.ok( gap * 1000n < floor, `boundary drop of ${gap} sat against a ${floor} sat floor is a cliff, not a glide`, ); }); test("the glide is roughly linear across the window", () => { const mid = priceAt(EXPIRY + Number(GRACE_BLOCKS) / 2); const expected = (idlePremiumSats(TIER) + openFloorSats(TIER)) / 2n; const drift = mid > expected ? mid - expected : expected - mid; assert.ok(drift * 100n < expected, "midpoint should sit near the average of the endpoints"); }); test("price is never below the floor at any height", () => { const floor = openFloorSats(TIER); for (let h = EXPIRY; h < IDLE_END + 10_000; h += 977) { assert.ok(priceAt(h) >= floor, `price dipped under the floor at ${h}`); } }); // ---------- the owner's advantage ---------- test("the owner's option is tens of times cheaper throughout IDLE", () => { // This asymmetry — not the length of the grace window — is what protects a // present owner. Renewing always beats being bought out. const reclaim = reclaimPriceSats({ tier: TIER }); for (const h of [EXPIRY, EXPIRY + 1000, IDLE_END - 1]) { assert.ok(priceAt(h) > reclaim * 4n, `takeover at ${h} is not meaningfully dearer than reclaiming`); } assert.equal(idlePremiumSats(TIER) / reclaim, 50n, "50x at the start of IDLE"); }); test("reclaim price ignores phase and demand entirely", () => { // An owner must never be priceable out of a name they already hold. assert.equal(reclaimPriceSats({ tier: TIER }), renewalPriceSats(TIER)); assert.equal(reclaimPriceSats({ tier: TIER, years: 2 }), renewalPriceSats(TIER, 2)); }); // ---------- the split ---------- test("IDLE takeover splits 30 / 0 / 70", () => { const s = splitTakeover({ amountSats: 10_000_000n, phase: PHASE.idle }); assert.equal(s.ownerSats, 3_000_000n); assert.equal(s.minerFeeSats, 0n); assert.equal(s.fundSats, 7_000_000n); }); test("OPEN takeover pays the absent owner nothing", () => { // The 30% is a buy-out while they are still entitled to reclaim. Once OPEN, // that entitlement has lapsed and so has the justification. const s = splitTakeover({ amountSats: 1_000_000n, phase: PHASE.open }); assert.equal(s.ownerSats, 0n); assert.equal(s.fundSats, 1_000_000n); }); test("the split never loses or conjures a satoshi, at any amount", () => { // Integer division truncates; the fund takes the remainder so the parts always // reconcile. A covenant that is one satoshi off simply will not unlock. for (const amount of [1n, 3n, 7n, 99n, 101n, 999_999n, 10_000_001n, 123_456_789n]) { for (const phase of [PHASE.idle, PHASE.open]) { const s = splitTakeover({ amountSats: amount, phase }); assert.equal( s.ownerSats + s.minerFeeSats + s.fundSats, amount, `${amount} sat in ${phase} did not reconcile`, ); assert.ok(s.ownerSats >= 0n && s.fundSats >= 0n && s.minerFeeSats >= 0n); } } }); test("no miner share by default", () => { assert.equal(SPLIT.minerPercent, 0n, "15% is a discount for the one party that also orders blocks"); }); test("restoring the miner share still reconciles exactly", () => { // Proving the 30/15/55 variant is a one-value change, not a rewrite. const original = SPLIT.minerPercent; try { SPLIT.minerPercent = 15n; const s = splitTakeover({ amountSats: 10_000_000n, phase: PHASE.idle }); assert.equal(s.ownerSats, 3_000_000n); assert.equal(s.minerFeeSats, 1_500_000n); assert.equal(s.fundSats, 5_500_000n); assert.equal(s.ownerSats + s.minerFeeSats + s.fundSats, 10_000_000n); } finally { SPLIT.minerPercent = original; } }); test("selling an ACTIVE name is refused outright", () => { assert.throws(() => splitTakeover({ amountSats: 1000n, phase: PHASE.active }), /not for sale/); }); test("a zero or negative takeover is refused", () => { assert.throws(() => splitTakeover({ amountSats: 0n, phase: PHASE.idle }), /must be positive/); }); // ---------- commit–reveal ---------- test("commit maturity window is one confirmation to one week", () => { assert.equal(COMMIT.minConfirmations, 1); assert.equal(COMMIT.maxConfirmations, 1008); assert.equal(COMMIT.saltBytes, 16); assert.equal(commitIsMature(1000, 1000), false, "same block is not a confirmation"); assert.equal(commitIsMature(1000, 1001), true, "one confirmation is enough to fix ordering"); assert.equal(commitIsMature(1000, 2008), true); assert.equal(commitIsMature(1000, 2009), false, "stale commits must not be hoardable"); }); // ---------- fund ---------- test("the fund is unset until deliberately configured", () => { // A covenant bakes this in permanently. Shipping a placeholder would send // every takeover to whatever address happened to be in the file. assert.equal(FUND.address, null); assert.equal(FUND.multisig, "2-of-3"); assert.equal(FUND.voluntary, true, "the answer to the IFP comparison rests on this"); }); // ---------- the UI summary ---------- test("describeLifecycle gives a UI everything it needs to warn in time", () => { const active = describeLifecycle({ height: EXPIRY - 5000, expiryHeight: EXPIRY, idleEndHeight: IDLE_END, tier: TIER }); assert.equal(active.phase, PHASE.active); assert.equal(active.blocksUntilExpiry, 5000); assert.equal(active.takeoverPriceSats, null); assert.equal(active.ownerCanReclaim, true); const idle = describeLifecycle({ height: EXPIRY + 10, expiryHeight: EXPIRY, idleEndHeight: IDLE_END, tier: TIER }); assert.equal(idle.phase, PHASE.idle); assert.equal(idle.blocksUntilExpiry, 0); assert.ok(idle.blocksUntilOpen > 0); assert.ok(idle.ownerAdvantage >= 40, `owner advantage of ${idle.ownerAdvantage}x is too thin`); const open = describeLifecycle({ height: IDLE_END + 1, expiryHeight: EXPIRY, idleEndHeight: IDLE_END, tier: TIER }); assert.equal(open.phase, PHASE.open); assert.equal(open.ownerCanReclaim, false); assert.equal(open.takeoverPriceSats, openFloorSats(TIER)); }); test("every tier survives a full lifecycle without a cliff or a rise", () => { for (const { tier } of TIERS) { const floor = openFloorSats(tier); let previous = takeoverPriceSats({ height: EXPIRY, expiryHeight: EXPIRY, idleEndHeight: IDLE_END, tier }); assert.equal(previous, idlePremiumSats(tier)); for (let h = EXPIRY + 1000; h <= IDLE_END; h += 2000) { const p = takeoverPriceSats({ height: h, expiryHeight: EXPIRY, idleEndHeight: IDLE_END, tier }); assert.ok(p <= previous, `tier ${tier} price rose at ${h}`); assert.ok(p >= floor, `tier ${tier} dipped below floor at ${h}`); previous = p; } } });