2026-09-06 18:46:45 +02:00
|
|
|
|
<!doctype html>
|
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
|
|
|
|
<title>Admin — Sirius.X</title>
|
2026-09-06 18:46:45 +02:00
|
|
|
|
<meta name="description" content="Sirius.x operator admin — mint TLDs, manage the registry, verify chain health. Operator-key gated.">
|
|
|
|
|
|
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
|
|
|
|
|
|
<link rel="mask-icon" href="../assets/favicon.svg" color="#d6ff3d">
|
|
|
|
|
|
<meta name="robots" content="noindex,nofollow">
|
|
|
|
|
|
<style>
|
|
|
|
|
|
:root{
|
|
|
|
|
|
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
|
|
|
|
|
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
|
|
|
|
|
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
|
|
|
|
|
|
}
|
|
|
|
|
|
*{box-sizing:border-box}
|
|
|
|
|
|
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
|
|
|
|
|
|
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
|
|
|
|
|
|
a{color:var(--acid)}
|
|
|
|
|
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem}
|
|
|
|
|
|
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
|
|
|
|
|
|
padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
|
|
|
|
|
|
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px}
|
|
|
|
|
|
.topnav .brand .g{color:var(--acid)}
|
|
|
|
|
|
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
|
|
|
|
|
|
.topnav a:hover{color:var(--ink);background:var(--panel)}
|
|
|
|
|
|
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
|
|
|
|
|
|
.topnav a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
|
|
|
|
|
|
.topnav a.portal:hover{background:rgba(214,255,61,.18)}
|
|
|
|
|
|
header.hero{text-align:center;padding:2.8rem 1.2rem 1rem}
|
|
|
|
|
|
.mark{font-size:40px;line-height:1}
|
|
|
|
|
|
h1{font-size:clamp(1.5rem,4vw,2rem);margin:.3rem 0 .3rem}
|
|
|
|
|
|
h1 .g{color:var(--acid)}
|
|
|
|
|
|
.tag{color:var(--mut);font-size:.98rem;max-width:600px;margin:0 auto}
|
|
|
|
|
|
h2{font-size:1.2rem;margin:0 0 .35rem}
|
|
|
|
|
|
h3{font-size:.98rem;margin:0 0 .3rem;font-family:ui-monospace,monospace}
|
|
|
|
|
|
section{padding:1.8rem 0;border-top:1px solid var(--line);margin-top:1.6rem}
|
|
|
|
|
|
section:first-of-type{border-top:none;margin-top:.8rem}
|
|
|
|
|
|
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px 18px;margin-top:.8rem}
|
|
|
|
|
|
label{display:block;font-size:11.5px;color:var(--mut);margin:12px 0 4px;text-transform:uppercase;letter-spacing:.4px}
|
|
|
|
|
|
.field-hint{color:var(--dim);font-size:11px;margin-left:8px;text-transform:none;letter-spacing:0}
|
|
|
|
|
|
input[type=text],textarea{
|
|
|
|
|
|
width:100%;padding:9px 12px;border-radius:8px;border:1px solid #ffffff22;background:#0e131b;
|
|
|
|
|
|
color:var(--ink);font-size:13px;font-family:ui-monospace,monospace;outline:none
|
|
|
|
|
|
}
|
|
|
|
|
|
textarea{min-height:68px;resize:vertical}
|
|
|
|
|
|
input:focus,textarea:focus{border-color:#4b7bec}
|
|
|
|
|
|
.btn{display:inline-block;text-decoration:none;padding:9px 16px;border-radius:9px;background:#4b7bec;color:#fff;font-size:13px;border:none;cursor:pointer;font-family:inherit}
|
|
|
|
|
|
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
|
|
|
|
|
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
|
|
|
|
|
|
.btn.small{padding:5px 10px;font-size:12px}
|
|
|
|
|
|
.btn:hover{filter:brightness(1.12)}
|
|
|
|
|
|
.btn:disabled{opacity:.45;cursor:not-allowed}
|
|
|
|
|
|
.row{display:flex;gap:9px;flex-wrap:wrap;margin-top:12px;align-items:center}
|
|
|
|
|
|
code{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all}
|
|
|
|
|
|
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:10px}
|
|
|
|
|
|
.status.ok{border-left:3px solid var(--ok)}
|
|
|
|
|
|
.status.err{border-left:3px solid var(--taken);color:var(--taken)}
|
|
|
|
|
|
.status.warn{border-left:3px solid var(--warn);color:var(--warn)}
|
|
|
|
|
|
.hidden{display:none}
|
|
|
|
|
|
.badge{display:inline-block;font-size:10.5px;padding:2px 8px;border-radius:999px;font-weight:600;letter-spacing:.3px}
|
|
|
|
|
|
.b-op{background:rgba(214,255,61,.14);color:var(--acid)}
|
|
|
|
|
|
.b-other{background:rgba(139,152,169,.14);color:var(--mut)}
|
|
|
|
|
|
.b-mem{background:rgba(255,199,95,.14);color:var(--warn)}
|
|
|
|
|
|
table.tlds{width:100%;border-collapse:collapse;margin-top:1rem;font-size:13px}
|
|
|
|
|
|
table.tlds th,table.tlds td{padding:8px 10px;text-align:left;border-bottom:1px solid var(--line);vertical-align:top}
|
|
|
|
|
|
table.tlds th{color:var(--mut);font-weight:500;font-size:11.5px;text-transform:uppercase;letter-spacing:.4px}
|
|
|
|
|
|
table.tlds .tld{font-family:ui-monospace,monospace;color:var(--acid);font-size:13.5px;font-weight:600}
|
|
|
|
|
|
table.tlds .cat{font-family:ui-monospace,monospace;color:var(--mut);font-size:11.5px}
|
|
|
|
|
|
table.tlds .records{font-family:ui-monospace,monospace;color:var(--dim);font-size:11.5px;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
|
|
|
|
.steps-log{margin-top:10px;font-family:ui-monospace,monospace;font-size:12px;color:var(--mut);background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 12px;max-height:140px;overflow-y:auto;display:none}
|
|
|
|
|
|
.steps-log.on{display:block}
|
|
|
|
|
|
.steps-log div::before{content:"› ";color:var(--acid)}
|
|
|
|
|
|
.msg{font-size:13px;margin-top:10px}
|
|
|
|
|
|
.msg.ok{color:var(--ok)} .msg.err{color:var(--taken)}
|
|
|
|
|
|
footer{border-top:1px solid var(--line);padding:1.8rem 0 3rem;color:var(--dim);font-size:12.5px;text-align:center;margin-top:2rem}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
|
|
<nav class="topnav">
|
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
|
|
|
|
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
2026-09-06 18:46:45 +02:00
|
|
|
|
<a href="../#try">Try it</a>
|
2026-09-08 00:10:11 +02:00
|
|
|
|
<a href="../#search-input">✏️ Register</a>
|
feat(sirius-x): public tld.html — search + mint a TLD end-to-end
Missing user-facing surface: minting a TLD required either the portal
(sign in first, register second, tucked under 'Signed in') or the
operator-only admin panel. New tld.html is a public, single-purpose
page that lets anyone go straight from label to certificate.
Flow: type a label → live check against /api/tlds → 'Register .foo →'
appears → sign-in cards (New / Import / WizardConnect) revealed inline
→ mint section shows label, address, price → submit walks through
'connected → checking-availability → loading-coins → building →
signing → broadcasting → registered'.
Wire is proven end-to-end (search returns a registerable card for
free labels, wallet flow generates a valid seed, address, submit
reaches 'wallet is empty' with a friendly faucet hint for the
signed-in address). Downstream of loading-coins is the same build/
sign/broadcast path exercised by every name registration.
Dispatch on wallet.source uses registerTldWithExternalWallet when
signed in via WC, else registerTldWithBuiltInWallet. Live TLD table
under the search shows every currently registered TLD with its
category; refreshes on successful mint.
Nav: '🌐 TLD' added to all 7 pages between Register and Brand.
Landing's 'Register a TLD' card now points here (previously linked
to the operator admin, which locked out anyone who isn't operator).
2026-09-07 20:39:11 +02:00
|
|
|
|
<a href="../tld.html">🌐 TLD</a>
|
feat(sirius-x/nav): use theseus.x compass SVG in Theseus button, not emoji
The nav's 'Theseus' link showed a generic 🧭 emoji, which renders
differently in every browser/OS and does not match theseus.x's
actual brand mark. Replaced with a small inline SVG of theseus.x's
compass (rounded dark square, N-half red, S-half acid) at 14x14 so
the button now carries theseus.x's real logo — recognisable and
identical everywhere.
Inlined the SVG rather than pointing at a shared asset so there's no
extra request per page and no relative-path arithmetic between root
and subdir pages. Applied to all 8 pages: landing, portal, register,
tld, theseus, docs, brand, admin.
2026-09-07 22:17:33 +02:00
|
|
|
|
<a href="../theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
2026-09-06 18:46:45 +02:00
|
|
|
|
<a href="./" class="here">🛠 Admin</a>
|
|
|
|
|
|
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
<header class="hero">
|
|
|
|
|
|
<div class="mark">🛠</div>
|
|
|
|
|
|
<h1><span class="g">Admin</span> panel</h1>
|
|
|
|
|
|
<p class="tag">Operator-side tools: mint new TLDs, browse the registry, verify chain health.
|
|
|
|
|
|
Sign in with the wallet the ecosystem knows as the operator; unfamiliar wallets get
|
|
|
|
|
|
read-only access to the registry.</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
|
|
|
|
|
|
<section id="signin">
|
|
|
|
|
|
<h2>Sign in</h2>
|
|
|
|
|
|
<p class="status">Chipnet-only. Keys stay in this browser tab; a mint transaction is signed
|
|
|
|
|
|
locally and broadcast direct to the electrum. Operator gate is client-side — the covenant
|
|
|
|
|
|
that enforces it on-chain is a mainnet requirement (Decentralized.DNS/DESIGN-tld-registry.md §5.2).</p>
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<label for="seed">Recovery phrase</label>
|
|
|
|
|
|
<textarea id="seed" placeholder="word1 word2 … word12" spellcheck="false" autocomplete="off"></textarea>
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<button class="btn acid" id="signin-btn">Sign in →</button>
|
|
|
|
|
|
<span class="field-hint" id="signin-hint">seed stays in memory only</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="signin-err" class="msg err hidden"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="me" class="hidden">
|
|
|
|
|
|
<h2>Signed in <span class="badge b-op" id="me-role" style="margin-left:8px">operator</span></h2>
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<label>Address</label>
|
|
|
|
|
|
<div><code id="me-addr">—</code></div>
|
|
|
|
|
|
<label style="margin-top:12px">Token address (holds the TLD certificates)</label>
|
|
|
|
|
|
<div><code id="me-taddr">—</code></div>
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<button class="btn ghost small" id="signout-btn">Sign out</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="mint" class="hidden">
|
|
|
|
|
|
<h2>Mint a new TLD</h2>
|
|
|
|
|
|
<p class="tag" style="max-width:none">Adds a per-TLD certificate to the TLD beacon. The cert
|
|
|
|
|
|
lands in your token address. Cost is chain fees + beacon dust (~1,300 chipnet sats).</p>
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<label>TLD label <span class="field-hint">1–16 chars, [a-z0-9-]</span></label>
|
|
|
|
|
|
<input type="text" id="mint-tld" placeholder="e.g. shop, dao, wiki" maxlength="16">
|
|
|
|
|
|
|
|
|
|
|
|
<label>Records JSON <span class="field-hint">optional; leave empty for defaults</span></label>
|
|
|
|
|
|
<textarea id="mint-records" placeholder='e.g. {"policy":"open","fee_bps":500}'>{}</textarea>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<button class="btn acid" id="mint-btn">Mint TLD →</button>
|
|
|
|
|
|
<span class="field-hint" id="mint-hint"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="steps-log" id="mint-log"></div>
|
|
|
|
|
|
<div class="msg" id="mint-msg"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="registry">
|
|
|
|
|
|
<h2>TLD registry</h2>
|
2026-09-08 02:19:41 +02:00
|
|
|
|
<p class="tag" style="max-width:none;font-size:.92rem">Every TLD registered on the beacon. Operators
|
|
|
|
|
|
(signed-in) can toggle a TLD's visibility on the public /api/tlds listing — hidden TLDs stop
|
|
|
|
|
|
appearing in the name-search UIs, but on-chain registrations under them keep resolving.</p>
|
|
|
|
|
|
<div class="card" id="op-token-card" style="margin-top:.8rem">
|
|
|
|
|
|
<label>Operator token <span class="field-hint">stored per browser tab, not sent anywhere except the /api/tld-visibility POST</span></label>
|
|
|
|
|
|
<input type="password" id="op-token" placeholder="paste BNS_OPERATOR_TOKEN">
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<button class="btn ghost small" id="op-token-save">Save token</button>
|
|
|
|
|
|
<span id="op-token-msg" class="dim"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-09-06 18:46:45 +02:00
|
|
|
|
<div class="status" id="registry-status">Loading current registry…</div>
|
|
|
|
|
|
<div id="registry-table"></div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
feat(sirius-x): footer everywhere, redesigned wallet button, dropdown auto-acts
Three UX fixes:
1. Beautiful footer on every page. Extracted the 4-column landing footer
into js/site-footer.js — reads the nav brand link to derive per-page
base ('./' at root, '../' in subdirs) so links resolve from any depth
without duplicating the HTML across 8 pages. Every page now ships an
empty <footer id="site-footer"></footer> and includes the injector;
inline footer + its CSS block removed from landing too so the source
stays in one place.
2. Wallet button redesign. Pill now shows a state dot (dim when not
signed in, green with subtle glow when signed in), a monospace short
address label when signed in ('qra6rs…7yl2') OR 'Wallet' when not,
a WC/seed badge on signed-in state, and a subtle caret. Full border
and hover states, proper aria-expanded/aria-label wiring. Dropdown
menu itself upgraded: grid layout per item (icon column + title +
hint), heavier backdrop blur, larger min-width, clearer typography.
3. Dropdown options actually do something. Removed the redundant 'Open
sign-in page →' item — the other three all land on portal too, so
listing 'open the page' as a fourth option was noise. On portal
arrival, the ?mode= handler now AUTO-TRIGGERS the primary action for
each mode:
new -> click Generate a phrase (12 words appear immediately)
import -> focus the seed textarea (cursor ready to paste)
wc -> click Connect wallet (WC session starts, URI shown)
No extra clicks between dropdown choice and the flow it names.
Cache-buster on the profile-menu.js include bumped to
?v=20260907wallet so cached copies pick up the new design; footer
injector at ?v=20260907rel.
2026-09-07 22:00:21 +02:00
|
|
|
|
<footer id="site-footer"></footer>
|
2026-09-06 18:46:45 +02:00
|
|
|
|
|
|
|
|
|
|
<script type="importmap">
|
|
|
|
|
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<script type="module">
|
|
|
|
|
|
// Cache-buster bump when the bundle changes (Chrome's ES-module map caches
|
|
|
|
|
|
// aggressively per plain URL). Portal.html has the same pattern.
|
feat(registrar): 90/10 revenue split — TLD owner earns from name mints
Every second-level name registration under a TLD now routes 90% of the
service fee to whoever holds that TLD's certificate on chain, with 10%
going to the platform address. That's the economic incentive for
minting a TLD: you earn from every name registered under it.
The mechanism, end to end:
1. resolver-web.js fetchTldMap now also records mintScriptHex — the
scriptPubKey of the TREG output that carries each TLD's NFT.
Exported so registrar can decode it into a cashaddr with libauth.
MVP: this is the ORIGINAL owner; NFT transfers after mint are not
traced yet (a follow-up will walk the chain of transfers).
2. registrar.js gains findTldOwnerAddress(client, tld) and
splitServiceFee(sats). The split constants live at the top of the
file (TLD_OWNER_SHARE_NUM/DEN = 90/100) so the ratio moves in one
place. Rounding: BigInt division favours the platform on odd sat
counts so the two shares always sum EXACTLY to the input.
3. quoteRegistration wraps the existing flow: it derives the TLD from
the name, looks up the TLD owner, and if the owner ≠ buyer it asks
buildRegistrationTx to add a second fee output. If the owner
couldn't be resolved (TLD not registered, decode failure) the full
fee stays on the platform address — the buyer still pays the same
amount either way.
4. register-tx.js buildRegistrationTx accepts tldFeeAddress/tldFeeSats
and, when set, emits an extra P2PKH output for the TLD owner. Sits
between the beacon dust and the platform-fee output; outputMap
records .tldOwnerFee so callers can find it. costs also carries
tldOwnerFeeSats and netCostSats includes it.
5. priceSummary in registrar-config splits the 'Service fee' row into
'Service fee — TLD owner (90%)' + 'Service fee — platform (10%)'
whenever tldOwnerFeeSats > 0, with a per-line note explaining
where the money goes.
Bundle: re-exported findTldOwnerAddress + splitServiceFee from
register-entry.js. Rebuilt bns-register.js (~34 kB) and deployed;
cache-buster bumped to ?v=20260908split on portal / admin /
register-flow.js.
Verified live: findTldOwnerAddress('.bch') returns the operator
cashaddr; quoteRegistration('tester42.bch') builds cleanly with a
9,000/1,000 split output pair on a 10,000-sat fee; priceSummary
renders both lines. No regressions on the TLD-mint flow (buyer IS
the TLD owner there — split short-circuits and it stays a single
fee output as before).
2026-09-08 02:42:16 +02:00
|
|
|
|
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260908split";
|
2026-09-06 18:46:45 +02:00
|
|
|
|
|
|
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
|
|
|
|
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
|
|
|
|
|
|
|
|
|
|
|
// Operator gate: address that owns the chipnet TLD beacon + seeded TLDs.
|
|
|
|
|
|
// Client-side only — the covenant that enforces this on-chain lands with the
|
|
|
|
|
|
// mainnet TLD-registry work. For chipnet this is honour-system; a curious
|
|
|
|
|
|
// visitor can bypass by pasting any seed, but non-operator UPDs/TREGs won't
|
|
|
|
|
|
// carry the countersignature future clients will check.
|
|
|
|
|
|
const OPERATOR_ADDRESS = "bchtest:qzew3dqcsj0guwe5y3uxf9q2xs5ajgy9lca4z093cu";
|
|
|
|
|
|
|
|
|
|
|
|
let wallet = null;
|
|
|
|
|
|
let isOperator = false;
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- sign in ----------
|
|
|
|
|
|
$("signin-btn").addEventListener("click", async () => {
|
|
|
|
|
|
const btn = $("signin-btn");
|
|
|
|
|
|
const errBox = $("signin-err");
|
|
|
|
|
|
errBox.classList.add("hidden");
|
|
|
|
|
|
const raw = $("seed").value.trim();
|
|
|
|
|
|
if (!raw) { errBox.textContent = "Enter your seed phrase."; errBox.classList.remove("hidden"); return; }
|
|
|
|
|
|
btn.disabled = true; btn.textContent = "Unlocking…";
|
|
|
|
|
|
try {
|
|
|
|
|
|
wallet = await BNS.BuiltInWallet.fromMnemonic(raw);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
errBox.textContent = "Could not unlock: " + (e.message || e);
|
|
|
|
|
|
errBox.classList.remove("hidden");
|
|
|
|
|
|
btn.disabled = false; btn.textContent = "Sign in →";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
$("seed").value = "";
|
|
|
|
|
|
btn.disabled = false; btn.textContent = "Sign in →";
|
|
|
|
|
|
isOperator = wallet.address === OPERATOR_ADDRESS;
|
|
|
|
|
|
await enterAdmin();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
$("signout-btn").addEventListener("click", () => {
|
|
|
|
|
|
wallet = null; isOperator = false;
|
|
|
|
|
|
$("me").classList.add("hidden");
|
|
|
|
|
|
$("mint").classList.add("hidden");
|
|
|
|
|
|
$("signin").classList.remove("hidden");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
async function enterAdmin() {
|
|
|
|
|
|
$("signin").classList.add("hidden");
|
|
|
|
|
|
$("me").classList.remove("hidden");
|
|
|
|
|
|
$("me-addr").textContent = wallet.address;
|
|
|
|
|
|
$("me-taddr").textContent = wallet.tokenAddress;
|
|
|
|
|
|
const role = $("me-role");
|
|
|
|
|
|
role.textContent = isOperator ? "operator" : "read-only";
|
|
|
|
|
|
role.className = "badge " + (isOperator ? "b-op" : "b-other");
|
|
|
|
|
|
if (isOperator) {
|
|
|
|
|
|
$("mint").classList.remove("hidden");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-08 02:19:41 +02:00
|
|
|
|
// ---------- operator token ----------
|
|
|
|
|
|
// Stored in sessionStorage so refreshing the tab keeps the token for the
|
|
|
|
|
|
// session but a full browser close forgets it. Not localStorage — the
|
|
|
|
|
|
// weaker persistence is the right default for a POST-write credential.
|
|
|
|
|
|
const opToken = () => { try { return sessionStorage.getItem("bnsOperatorToken") || ""; } catch { return ""; } };
|
|
|
|
|
|
function setOpTokenMsg(text, ok) {
|
|
|
|
|
|
const el = $("op-token-msg"); el.textContent = text;
|
|
|
|
|
|
el.style.color = ok === true ? "var(--ok)" : ok === false ? "var(--taken)" : "var(--dim)";
|
|
|
|
|
|
}
|
|
|
|
|
|
$("op-token").value = opToken();
|
|
|
|
|
|
$("op-token-save").addEventListener("click", () => {
|
|
|
|
|
|
try { sessionStorage.setItem("bnsOperatorToken", $("op-token").value.trim()); } catch {}
|
|
|
|
|
|
setOpTokenMsg(opToken() ? "saved for this session" : "cleared", opToken() ? true : null);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- TLD registry with hide/unhide ----------
|
|
|
|
|
|
// Fetches BOTH /api/tlds?include_hidden=1 (so we see the hidden ones the
|
|
|
|
|
|
// public listing filters out) and the current /api/tld-visibility. Renders
|
|
|
|
|
|
// one row per TLD with a checkbox for the hidden flag — flipping it POSTs
|
|
|
|
|
|
// to /api/tld-visibility with the operator token.
|
2026-09-06 18:46:45 +02:00
|
|
|
|
async function loadRegistry() {
|
|
|
|
|
|
const status = $("registry-status");
|
|
|
|
|
|
const table = $("registry-table");
|
|
|
|
|
|
status.className = "status";
|
|
|
|
|
|
status.textContent = "Loading current registry from the chain…";
|
|
|
|
|
|
table.innerHTML = "";
|
|
|
|
|
|
try {
|
2026-09-08 02:19:41 +02:00
|
|
|
|
const [rTlds, rVis] = await Promise.all([
|
|
|
|
|
|
fetch("https://silentmode.st/api/tlds?include_hidden=1"),
|
|
|
|
|
|
fetch("https://silentmode.st/api/tld-visibility"),
|
|
|
|
|
|
]);
|
|
|
|
|
|
if (!rTlds.ok) throw new Error("api/tlds " + rTlds.status);
|
|
|
|
|
|
if (!rVis.ok) throw new Error("api/tld-visibility " + rVis.status);
|
|
|
|
|
|
const body = await rTlds.json();
|
|
|
|
|
|
const vis = await rVis.json();
|
|
|
|
|
|
const hidden = new Set(vis.hidden || []);
|
|
|
|
|
|
const tlds = (body.tlds || []).sort((a, b) => a.tld.localeCompare(b.tld));
|
2026-09-06 18:46:45 +02:00
|
|
|
|
status.className = "status ok";
|
2026-09-08 02:19:41 +02:00
|
|
|
|
status.textContent = `${tlds.length} TLDs on the beacon · ${hidden.size} hidden from public listing`;
|
|
|
|
|
|
let html = '<table class="tlds"><thead><tr><th>TLD</th><th>Names</th><th>Advertised</th><th>Hidden from public</th></tr></thead><tbody>';
|
|
|
|
|
|
for (const t of tlds) {
|
|
|
|
|
|
const isHidden = hidden.has(t.tld);
|
|
|
|
|
|
html += `<tr>
|
|
|
|
|
|
<td class="tld">.${esc(t.tld)}</td>
|
|
|
|
|
|
<td class="cat">${t.registered_count ?? 0}</td>
|
|
|
|
|
|
<td class="cat">${t.in_advertised ? "yes" : ""}</td>
|
|
|
|
|
|
<td><label class="row" style="gap:6px;margin:0;font-size:12.5px;color:var(--mut)">
|
|
|
|
|
|
<input type="checkbox" data-hide-tld="${esc(t.tld)}" ${isHidden ? "checked" : ""}>
|
|
|
|
|
|
${isHidden ? '<span class="badge b-mem">hidden</span>' : '<span class="dim">public</span>'}
|
|
|
|
|
|
</label></td>
|
|
|
|
|
|
</tr>`;
|
2026-09-06 18:46:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
html += "</tbody></table>";
|
|
|
|
|
|
table.innerHTML = html;
|
2026-09-08 02:19:41 +02:00
|
|
|
|
// Wire visibility checkboxes.
|
|
|
|
|
|
table.querySelectorAll("input[data-hide-tld]").forEach((cb) => {
|
|
|
|
|
|
cb.addEventListener("change", async (e) => {
|
|
|
|
|
|
const tld = e.target.getAttribute("data-hide-tld");
|
|
|
|
|
|
const hide = e.target.checked;
|
|
|
|
|
|
if (!opToken()) {
|
|
|
|
|
|
e.target.checked = !hide;
|
|
|
|
|
|
setOpTokenMsg("paste your operator token first — required for /api/tld-visibility writes", false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
e.target.disabled = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const r = await fetch("https://silentmode.st/api/tld-visibility", {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "content-type": "application/json", authorization: "Bearer " + opToken() },
|
|
|
|
|
|
body: JSON.stringify({ tld, hide }),
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!r.ok) {
|
|
|
|
|
|
const j = await r.json().catch(() => ({}));
|
|
|
|
|
|
throw new Error(j.error || "http " + r.status);
|
|
|
|
|
|
}
|
|
|
|
|
|
setOpTokenMsg(`.${tld} is now ${hide ? "hidden" : "public"}`, true);
|
|
|
|
|
|
setTimeout(loadRegistry, 400); // refresh row states + counts
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
e.target.checked = !hide;
|
|
|
|
|
|
setOpTokenMsg("update failed: " + err.message, false);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
e.target.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-09-06 18:46:45 +02:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
status.className = "status err";
|
|
|
|
|
|
status.textContent = "Could not load registry: " + (e.message || e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
loadRegistry();
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- mint a new TLD ----------
|
|
|
|
|
|
$("mint-btn").addEventListener("click", async () => {
|
|
|
|
|
|
if (!wallet || !isOperator) return;
|
|
|
|
|
|
const tld = $("mint-tld").value.trim().toLowerCase();
|
|
|
|
|
|
if (!/^[a-z0-9-]{1,16}$/.test(tld)) {
|
|
|
|
|
|
$("mint-msg").className = "msg err";
|
|
|
|
|
|
$("mint-msg").textContent = "TLD label must be 1–16 chars [a-z0-9-]";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
let records = {};
|
|
|
|
|
|
const rawRecords = $("mint-records").value.trim();
|
|
|
|
|
|
if (rawRecords) {
|
|
|
|
|
|
try { records = JSON.parse(rawRecords); }
|
|
|
|
|
|
catch (e) { $("mint-msg").className = "msg err"; $("mint-msg").textContent = "Records JSON is invalid: " + e.message; return; }
|
|
|
|
|
|
}
|
|
|
|
|
|
const btn = $("mint-btn");
|
|
|
|
|
|
const msg = $("mint-msg");
|
|
|
|
|
|
const log = $("mint-log");
|
|
|
|
|
|
log.className = "steps-log on"; log.innerHTML = "";
|
|
|
|
|
|
msg.textContent = ""; msg.className = "msg";
|
|
|
|
|
|
const push = (t) => { const d = document.createElement("div"); d.textContent = t; log.appendChild(d); log.scrollTop = log.scrollHeight; };
|
|
|
|
|
|
btn.disabled = true; btn.textContent = "Signing…";
|
|
|
|
|
|
let el = null;
|
|
|
|
|
|
try {
|
|
|
|
|
|
el = await BNS.connect();
|
|
|
|
|
|
push("connected to chipnet");
|
|
|
|
|
|
const res = await BNS.registerTldWithBuiltInWallet(el, {
|
|
|
|
|
|
wallet, tld, records,
|
|
|
|
|
|
onProgress: (step) => push(step),
|
|
|
|
|
|
});
|
|
|
|
|
|
push("broadcast: " + (res.txid || res.txId || "(unknown)"));
|
|
|
|
|
|
msg.className = "msg ok";
|
|
|
|
|
|
msg.textContent = "Minted .${tld}. Category " + (res.category || "unknown") + ". Registry refresh below in ~30s.";
|
|
|
|
|
|
btn.textContent = "Mint TLD →"; btn.disabled = false;
|
|
|
|
|
|
$("mint-tld").value = "";
|
|
|
|
|
|
$("mint-records").value = "{}";
|
|
|
|
|
|
setTimeout(loadRegistry, 3000);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
msg.className = "msg err";
|
|
|
|
|
|
msg.textContent = "Mint failed: " + (e.message || e);
|
|
|
|
|
|
push("error: " + (e.message || e));
|
|
|
|
|
|
btn.disabled = false; btn.textContent = "Mint TLD →";
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
try { el?.close?.(); } catch {}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
feat(sirius-x): footer everywhere, redesigned wallet button, dropdown auto-acts
Three UX fixes:
1. Beautiful footer on every page. Extracted the 4-column landing footer
into js/site-footer.js — reads the nav brand link to derive per-page
base ('./' at root, '../' in subdirs) so links resolve from any depth
without duplicating the HTML across 8 pages. Every page now ships an
empty <footer id="site-footer"></footer> and includes the injector;
inline footer + its CSS block removed from landing too so the source
stays in one place.
2. Wallet button redesign. Pill now shows a state dot (dim when not
signed in, green with subtle glow when signed in), a monospace short
address label when signed in ('qra6rs…7yl2') OR 'Wallet' when not,
a WC/seed badge on signed-in state, and a subtle caret. Full border
and hover states, proper aria-expanded/aria-label wiring. Dropdown
menu itself upgraded: grid layout per item (icon column + title +
hint), heavier backdrop blur, larger min-width, clearer typography.
3. Dropdown options actually do something. Removed the redundant 'Open
sign-in page →' item — the other three all land on portal too, so
listing 'open the page' as a fourth option was noise. On portal
arrival, the ?mode= handler now AUTO-TRIGGERS the primary action for
each mode:
new -> click Generate a phrase (12 words appear immediately)
import -> focus the seed textarea (cursor ready to paste)
wc -> click Connect wallet (WC session starts, URI shown)
No extra clicks between dropdown choice and the flow it names.
Cache-buster on the profile-menu.js include bumped to
?v=20260907wallet so cached copies pick up the new design; footer
injector at ?v=20260907rel.
2026-09-07 22:00:21 +02:00
|
|
|
|
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
2026-09-08 02:19:41 +02:00
|
|
|
|
<script defer src="../js/profile-menu.js?v=20260908tabs"></script>
|
2026-09-06 18:46:45 +02:00
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|