Theseus Navigator: - Firefox-style toolbar: SVG back/fwd/reload/home buttons - security padlock badge in the address bar opens a floating site-info overlay VIEW (on top of the page, never pushes content down) - Edge-style favorites bar (shown only on the new-tab/home page) - search-engine picker (DuckDuckGo/Google/Bing/Brave/Startpage) - Theseus logo moved right, opens Settings; native menu removed - address bar doubles as search; bottom resolver status line Argus: - BNS-only indexer: an electrum endpoint over a bare BCHN node (watches just the beacon, caches txs so no txindex needed) + test - registry lifecycle / portfolio / name-index modules Site restructure (nav/apps/store/hermes/choose) + runbook notes.
261 lines
11 KiB
JavaScript
261 lines
11 KiB
JavaScript
// 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;
|
||
}
|
||
}
|
||
});
|