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
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > Register a TLD — Sirius.X< / title >
< meta name = "description" content = "Mint a top-level domain on Bitcoin Cash. Any wallet can register a TLD; second-level names can then be sold under it." >
< link rel = "icon" href = "./assets/favicon.svg" type = "image/svg+xml" >
< link rel = "mask-icon" href = "./assets/favicon.svg" color = "#d6ff3d" >
< meta property = "og:type" content = "website" >
< meta property = "og:site_name" content = "Sirius.X" >
< meta property = "og:title" content = "Register a TLD on Bitcoin Cash" >
< meta property = "og:description" content = "Mint a TLD certificate. One BCH transaction, no renewal, no registrar." >
< meta property = "og:url" content = "https://silentmode.st/sirius-x/tld.html" >
< meta property = "og:image" content = "https://silentmode.st/sirius-x/brand/banner.svg" >
< 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}
html{scroll-behavior:smooth}
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:820px;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:3.4rem 1.2rem 1rem}
.mark{font-size:44px;line-height:1}
h1{font-size:clamp(1.7rem,4.5vw,2.3rem);margin:.3rem 0 .3rem}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1rem;max-width:600px;margin:0 auto}
h2{font-size:1.25rem;margin:0 0 .4rem}
h3{font-size:1rem;margin:0 0 .35rem}
section{padding:2rem 0;border-top:1px solid var(--line);margin-top:2rem}
section:first-of-type{border-top:none;margin-top:1rem}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin-top:1rem}
label{display:block;font-size:13px;color:var(--mut);margin:14px 0 5px}
.field-hint{color:var(--dim);font-size:11.5px;margin-left:8px}
textarea,input[type=text],input[type=password]{
width:100%;padding:11px 13px;border-radius:10px;border:1px solid #ffffff22;background:#0e131b;
color:var(--ink);font-size:14px;font-family:inherit;outline:none
}
textarea{min-height:74px;resize:vertical;font-family:ui-monospace,monospace;font-size:13px}
textarea:focus,input:focus{border-color:#4b7bec}
.btn{display:inline-block;text-decoration:none;padding:10px 18px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;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:6px 12px;font-size:12.5px}
.btn:hover{filter:brightness(1.12)}
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
.row{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px;align-items:center}
.muted{color:var(--mut);font-size:13px}
.dim{color:var(--dim);font-size:12.5px}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px;word-break:break-all}
.badge{display:inline-block;font-size:11px;padding:2px 10px;border-radius:999px;font-weight:600}
.b-ok{background:rgba(79,209,165,.15);color:var(--ok)}
.b-taken{background:rgba(246,118,138,.15);color:var(--taken)}
.b-check{background:rgba(139,152,169,.14);color:var(--mut)}
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:12px}
.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}
.err{color:var(--taken);font-size:13.5px;margin-top:10px}
.authbox{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}
.authbox .card{margin:0}
.authbox h3{margin:0 0 .3rem;font-size:1rem}
.authbox p{margin:0;color:var(--mut);font-size:13px}
/* TLD search cell */
.search{max-width:600px;margin:1.5rem auto 0}
.searchrow{display:flex;gap:8px}
.searchrow input{flex:1;padding:14px 18px;font-size:17px;border-radius:12px;border:1px solid #ffffff22;background:#141a24;color:var(--ink)}
.searchrow input:focus{border-color:#4b7bec;outline:none}
.searchhint{font-size:12.5px;color:var(--mut);margin-top:8px;text-align:center}
.result-card{display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center;
background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 18px;margin-top:12px}
.result-card .lbl{font-family:ui-monospace,monospace;font-size:18px;color:var(--ink)}
.result-card .lbl .dot{color:var(--acid)}
/* TLD registry table */
table.tlds{width:100%;border-collapse:collapse;margin-top:1rem;font-size:13px}
table.tlds th,table.tlds td{padding:8px 12px;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;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Registration progress */
.steps-log{margin-top:12px;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:12px}
.msg.ok{color:var(--ok)} .msg.err{color:var(--taken)}
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
< / style >
< / head >
< body >
< nav class = "topnav" >
< a class = "brand" href = "./" > ⭐ < span class = "g" > Sirius.X< / span > < / a >
< a href = "./#try" > Try it< / a >
< a href = "./register.html" > 🪪 Register< / a >
< a href = "./tld.html" class = "here" > 🌐 TLD< / a >
feat(sirius-x): consistent top nav order, Theseus.X branding on theseus subpage
Nav consistency: subpages had Try it → Theseus → Register → TLD while
the landing had Try it → Register → TLD → Theseus. Reordered every
page to the landing's order, so the top nav is identical everywhere:
Sirius.X | Try it | 🪪 Register | 🌐 TLD | 🧭 Theseus | 🔑 Sign in
(Admin adds its own 🛠 Admin between Theseus and Sign in since Admin
is the current page.)
Theseus subpage now carries Theseus.X's own branding: the compass
favicon (N-half red, S-half acid) copied from silentmode.st/bns/
theseus.x/assets/ lives under site-sirius-x/theseus/assets/, and the
subpage references it for <link rel=icon>, <link rel=mask-icon>, and
og:image. og:site_name flipped from 'Sirius.X' to 'Theseus Navigator'.
Hero mark swapped from the generic 🧭 emoji to the actual compass
SVG at 72px with a soft acid glow, so the page reads as Theseus at a
glance instead of borrowing Sirius's star.
Sign-in end-to-end verified on the live site:
- Landing dropdown → New wallet → portal auto-generates 12-word phrase
- Ack + Continue → me section revealed, siriusProfile written with
{address, tokenAddress, source: 'seed', signedInAt}
- Nav pill updates to 'qzc2vx…yn93 seed ▾' without reload
- Sign out → profile cleared, portal reset, pill back to 'Wallet ▾'
- Wallet address derives deterministically from the fresh mnemonic
(BuiltInWallet.create → deriveSeedFromBip39Mnemonic → HD path)
2026-09-07 22:05:45 +02:00
< a href = "./theseus/" > 🧭 Theseus< / 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 = "./portal.html" class = "portal" > 🔑 Sign in< / a >
< / nav >
< header class = "hero" >
< div class = "mark" > 🌐< / div >
< h1 > Register a < span class = "g" > TLD< / span > < / h1 >
< p class = "tag" > A TLD is a certificate on the Bitcoin Cash chain. You mint it, you own it — any
second-level name registered under it lives in your TLD's registry. One BCH transaction,
no renewal, no ICANN.< / p >
< / header >
< div class = "wrap" >
< section id = "search-section" >
< h2 > Search a TLD< / h2 >
< p class = "muted" > Type a label (a– z, 0– 9, hyphen; up to 16 chars). We check the on-chain TLD
registry live and, if the label is free, show a register button.< / p >
< div class = "search" >
< div class = "searchrow" >
< input id = "q" placeholder = "your-tld" spellcheck = "false" autocomplete = "off" autofocus maxlength = "16" >
< / div >
< div class = "searchhint" > Available TLDs appear with a register button. Taken labels show which category holds them.< / div >
< / div >
< div id = "result" > < / div >
< / section >
< section id = "signin-section" class = "hidden" >
< h2 > Sign in to mint< / h2 >
< p class = "muted" > The TLD certificate lands in the token address of the wallet that signs the mint —
that wallet becomes the registry operator.< / p >
< div class = "authbox" style = "margin-top:14px" >
< div class = "card" id = "card-new" >
< h3 > 🆕 New wallet< / h3 >
< p > Generate a fresh 12-word phrase in this tab. Nothing is saved — write it down before continuing.< / p >
< div class = "row" >
< button class = "btn acid" id = "new-btn" > Generate a phrase< / button >
< / div >
< div id = "new-phrase" class = "hidden" style = "margin-top:14px" >
< label > Your new recovery phrase< / label >
< textarea id = "new-seed" readonly > < / textarea >
< label style = "margin-top:10px;display:flex;gap:9px;align-items:flex-start;font-size:13.5px" >
< input type = "checkbox" id = "new-ack" style = "margin-top:3px" >
< span > I have written this phrase down. There is no reset if I lose it.< / span >
< / label >
< div class = "row" >
< button class = "btn acid" id = "new-signin-btn" disabled > Continue →< / button >
< button class = "btn ghost" id = "new-copy-btn" > Copy phrase< / button >
< / div >
< / div >
< div id = "new-err" class = "err hidden" > < / div >
< / div >
< div class = "card" id = "card-import" >
< h3 > 📥 Import a seed phrase< / h3 >
< p > Paste your 12- or 24-word phrase. It stays in memory only.< / p >
< 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" > Continue →< / button >
< / div >
< div id = "signin-err" class = "err hidden" > < / div >
< / div >
< div class = "card" id = "card-wc" >
< h3 > 🔗 WizardConnect< / h3 >
< p > Cashonize 0.9+ or Paytaca. The wallet signs the mint on your device.< / p >
< div class = "row" >
< button class = "btn acid" id = "wc-btn" > Connect wallet< / button >
< / div >
< div id = "wc-uri-box" class = "hidden" style = "margin-top:14px" >
< label > Paste this into your WC-capable wallet:< / label >
< div style = "display:flex;gap:8px;align-items:center;margin-top:6px" >
< code id = "wc-uri" style = "flex:1;font-size:12px" > < / code >
< button class = "btn ghost small" id = "wc-copy" type = "button" > Copy< / button >
< / div >
< p class = "dim" style = "margin-top:8px" > Waiting for your wallet…< / p >
< div class = "row" > < button class = "btn ghost small" id = "wc-cancel" type = "button" > Cancel< / button > < / div >
< / div >
< div id = "wc-err" class = "err hidden" > < / div >
< / div >
< / div >
< / section >
< section id = "mint-section" class = "hidden" >
< h2 > Mint < span id = "mint-label" class = "mono" style = "color:var(--acid)" > —< / span > < / h2 >
< div class = "card" >
< div class = "status" id = "mint-wallet" > Signed in as < code id = "mint-addr" > —< / code > < / div >
< label > Service fee (USD) < span class = "field-hint" > chipnet placeholder rate; mainnet will use an oracle< / span > < / label >
< input type = "text" id = "mint-usd" value = "5" style = "max-width:200px" >
< div class = "status" id = "mint-price" > = < b > 0< / b > sats · rate < b > 0< / b > sats/USD< / div >
< div class = "row" >
< button class = "btn acid" id = "mint-submit" disabled > Register< / button >
< button class = "btn ghost small" id = "mint-back" > ← Back to search< / button >
< button class = "btn ghost small" id = "mint-signout" > Sign out< / button >
< / div >
< div class = "steps-log" id = "mint-log" > < / div >
< div id = "mint-msg" class = "msg" > < / div >
< / div >
< / section >
< section id = "listing" >
< h2 > Registered TLDs< / h2 >
< p class = "muted" > Fetched from the on-chain TLD registry via the public gateway. Refresh the page for the latest.< / p >
< div id = "tlds-status" class = "status" > Loading…< / div >
< table class = "tlds" id = "tlds-table" style = "display:none" >
< thead > < tr > < th > TLD< / th > < th > Category< / th > < / tr > < / thead >
< tbody > < / tbody >
< / table >
< / 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 >
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
< script type = "importmap" >
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
< / script >
< script type = "module" >
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260907tldext";
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s).replace(/[& < >"']/g, (c) => ({"&":"& ","< ":"< ",">":"> ",'"':"" ","'":"' "}[c]));
// ---------- oracle stub ----------
// Same chipnet-only static rate as the portal. Mainnet swaps this for a real
// price feed; the sats output is what actually gets locked into the mint tx.
const CHIPNET_SATS_PER_USD = 250_000; // ~$400/BCH test rate
const usdToSats = (u) => {
const n = Number(String(u).replace(/[^\d.]/g, "")) || 0;
return Math.max(0, Math.round(n * CHIPNET_SATS_PER_USD));
};
// ---------- state ----------
let wallet = null; // built-in wallet or WC wrapper
let candidateLabel = null; // TLD label the user chose to register
// ---------- TLD list (live) ----------
async function loadTldList() {
const status = $("tlds-status"), tbody = $("tlds-table").querySelector("tbody");
try {
const r = await fetch("https://navigate.st/api/tlds");
if (!r.ok) throw new Error("api " + r.status);
const j = await r.json();
const tlds = (j.tlds || []).map((t) => (typeof t === "string" ? { tld: t } : t));
tlds.sort((a, b) => a.tld.localeCompare(b.tld));
tbody.innerHTML = tlds.map((t) =>
`< tr > < td class = "tld" > .${esc(t.tld)}< / td > < td class = "cat" > ${esc(t.certificate || t.category || "")}< / td > < / tr > `
).join("");
$("tlds-table").style.display = "";
status.className = "status ok";
status.textContent = `${tlds.length} TLDs registered on chipnet.`;
return tlds.map((t) => t.tld);
} catch (e) {
status.className = "status err";
status.textContent = "Could not load TLD list: " + (e.message || e);
return null;
}
}
let registeredTlds = null;
loadTldList().then((list) => { registeredTlds = list; refreshSearch(); });
// ---------- search + result ----------
function validLabel(s) { return /^[a-z0-9-]{1,16}$/.test(s); }
function normLabel(s) { return String(s).toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 16); }
function renderResult(label, state) {
const res = $("result");
if (!label) { res.innerHTML = ""; return; }
if (state === "checking") {
res.innerHTML = `< div class = "result-card" >
< div class = "lbl" > < span class = "dot" > .< / span > ${esc(label)}< / div >
< span class = "badge b-check" > checking…< / span >
< span > < / span >
< / div > `;
return;
}
if (state === "taken") {
res.innerHTML = `< div class = "result-card" >
< div class = "lbl" > < span class = "dot" > .< / span > ${esc(label)}< / div >
< span class = "badge b-taken" > taken< / span >
< span > < / span >
< / div >
< p class = "dim" style = "margin-top:8px" > Someone already minted < code > .${esc(label)}< / code > . Try a different label — TLDs are first-come, first-served.< / p > `;
return;
}
// available
res.innerHTML = `< div class = "result-card" >
< div class = "lbl" > < span class = "dot" > .< / span > ${esc(label)}< / div >
< span class = "badge b-ok" > available< / span >
< button class = "btn acid" id = "go-register" > Register .${esc(label)} →< / button >
< / div > `;
$("go-register").onclick = () => beginRegister(label);
}
let currentLabel = "";
function refreshSearch() {
const raw = normLabel($("q").value);
currentLabel = raw;
if (!raw) { renderResult("", null); return; }
if (!validLabel(raw)) { renderResult(raw, "invalid"); return; }
// If we already have the registry snapshot, decide locally — no API round-trip.
if (registeredTlds !== null) {
renderResult(raw, registeredTlds.includes(raw) ? "taken" : "ok");
return;
}
renderResult(raw, "checking");
}
$("q").addEventListener("input", () => {
$("q").value = normLabel($("q").value);
refreshSearch();
});
$("q").addEventListener("keydown", (e) => {
if (e.key === "Enter" & & currentLabel & & registeredTlds & & !registeredTlds.includes(currentLabel)) {
beginRegister(currentLabel);
}
});
// ---------- flow: search → wallet → mint ----------
function beginRegister(label) {
candidateLabel = label;
$("mint-label").textContent = "." + label;
// Show the sign-in section if not signed in; otherwise straight to mint.
if (!wallet) {
$("signin-section").classList.remove("hidden");
$("signin-section").scrollIntoView({ behavior: "smooth", block: "start" });
} else {
showMintSection();
}
}
function showMintSection() {
$("signin-section").classList.add("hidden");
$("mint-section").classList.remove("hidden");
$("mint-addr").textContent = wallet.address;
updateMintPrice();
updateMintButton();
$("mint-section").scrollIntoView({ behavior: "smooth", block: "start" });
}
function backToSearch() {
$("mint-section").classList.add("hidden");
$("search-section").scrollIntoView({ behavior: "smooth", block: "start" });
}
$("mint-back").addEventListener("click", backToSearch);
// ---------- wallet: import ----------
$("signin-btn").addEventListener("click", async () => {
const raw = $("seed").value.trim();
const errBox = $("signin-err");
errBox.classList.add("hidden");
if (!raw) { errBox.textContent = "Paste your seed phrase to continue."; errBox.classList.remove("hidden"); return; }
const btn = $("signin-btn");
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 = "Continue →";
return;
}
$("seed").value = "";
btn.disabled = false; btn.textContent = "Continue →";
writeProfile(wallet);
showMintSection();
});
// ---------- wallet: new ----------
$("new-btn").addEventListener("click", async () => {
const btn = $("new-btn"), errBox = $("new-err");
errBox.classList.add("hidden");
btn.disabled = true; btn.textContent = "Generating…";
try {
const fresh = await BNS.BuiltInWallet.create();
wallet = fresh;
$("new-seed").value = fresh.mnemonic;
$("new-phrase").classList.remove("hidden");
btn.textContent = "Phrase generated";
} catch (e) {
errBox.textContent = "Could not generate: " + (e.message || e);
errBox.classList.remove("hidden");
btn.disabled = false; btn.textContent = "Generate a phrase";
}
});
$("new-ack").addEventListener("change", (e) => { $("new-signin-btn").disabled = !e.target.checked; });
$("new-copy-btn").addEventListener("click", () => navigator.clipboard?.writeText($("new-seed").value));
$("new-signin-btn").addEventListener("click", () => {
if (!wallet) return;
$("new-seed").value = "";
$("new-phrase").classList.add("hidden");
writeProfile(wallet);
showMintSection();
});
// ---------- wallet: WC ----------
$("wc-btn").addEventListener("click", async () => {
const btn = $("wc-btn"), errBox = $("wc-err"), uriBox = $("wc-uri-box");
errBox.classList.add("hidden");
btn.disabled = true; btn.textContent = "Starting…";
let session;
try {
const mod = await import("https://silentmode.st/js/wizardconnect.js");
session = await mod.openWizardSession({
dappName: BNS.REGISTRAR.wizardConnect.dappName,
dappIcon: BNS.REGISTRAR.wizardConnect.dappIcon,
prefix: BNS.REGISTRAR.addressPrefix,
crypto: {
pubkeyToAddress: BNS.pubkeyToAddress,
pubkeyToLockingBytecode: BNS.pubkeyToLockingBytecode,
binToHex: BNS.binToHex,
txidOfHex: BNS.txidOfHex,
},
});
} catch (e) {
errBox.textContent = "Could not start WC: " + (e.message || e);
errBox.classList.remove("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
return;
}
$("wc-uri").textContent = session.qrUri ?? session.uri;
uriBox.classList.remove("hidden");
btn.textContent = "Waiting for wallet…";
try { await session.ready; }
catch (e) {
errBox.textContent = "Wallet did not approve: " + (e.message || e);
errBox.classList.remove("hidden");
uriBox.classList.add("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
try { await session.disconnect(); } catch {}
return;
}
const addrs = await session.getAddresses();
if (!addrs?.length) {
errBox.textContent = "Wallet returned no addresses.";
errBox.classList.remove("hidden");
uriBox.classList.add("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
try { await session.disconnect(); } catch {}
return;
}
wallet = {
source: "wc",
address: addrs[0],
tokenAddress: BNS.toTokenAddress(addrs[0]),
watchedAddresses: addrs,
session,
};
writeProfile(wallet);
uriBox.classList.add("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
showMintSection();
});
$("wc-cancel").addEventListener("click", async () => {
if (wallet?.session) { try { await wallet.session.disconnect(); } catch {} }
$("wc-uri-box").classList.add("hidden");
$("wc-btn").disabled = false; $("wc-btn").textContent = "Connect wallet";
});
$("wc-copy").addEventListener("click", () => navigator.clipboard?.writeText($("wc-uri").textContent));
// ---------- profile persistence (mirrors portal.html) ----------
function writeProfile(w) {
try {
localStorage.setItem("siriusProfile", JSON.stringify({
address: w.address, tokenAddress: w.tokenAddress,
source: w.source ?? "seed", signedInAt: Date.now(),
}));
} catch {}
window.dispatchEvent(new Event("siriusProfileChanged"));
}
function clearProfile() {
try { localStorage.removeItem("siriusProfile"); } catch {}
window.dispatchEvent(new Event("siriusProfileChanged"));
}
$("mint-signout").addEventListener("click", async () => {
if (wallet?.source === "wc" & & wallet.session) {
try { await wallet.session.disconnect(); } catch {}
}
wallet = null;
clearProfile();
$("mint-section").classList.add("hidden");
$("signin-section").classList.remove("hidden");
});
// ---------- mint ----------
function updateMintPrice() {
const usd = $("mint-usd").value.trim() || "0";
const sats = usdToSats(usd);
$("mint-price").innerHTML = `= < b > ${sats.toLocaleString()}< / b > sats · rate < b > ${CHIPNET_SATS_PER_USD.toLocaleString()}< / b > sats/USD (chipnet)`;
$("mint-submit").textContent = `Register .${candidateLabel} (~${sats.toLocaleString()} sats)`;
}
function updateMintButton() {
$("mint-submit").disabled = !wallet || !candidateLabel;
}
$("mint-usd").addEventListener("input", updateMintPrice);
$("mint-submit").addEventListener("click", async () => {
if (!wallet || !candidateLabel) return;
const sats = BigInt(usdToSats($("mint-usd").value.trim() || "0"));
const msg = $("mint-msg"); msg.textContent = ""; msg.className = "msg";
const log = $("mint-log"); log.className = "steps-log on"; log.innerHTML = "";
const push = (t) => { const d = document.createElement("div"); d.textContent = t; log.appendChild(d); log.scrollTop = log.scrollHeight; };
$("mint-submit").disabled = true;
$("mint-submit").textContent = "Signing…";
let el = null;
try {
el = await BNS.connect();
push("connected to chipnet");
const serviceFee = { address: BNS.REGISTRAR?.serviceFee?.address ?? null, sats };
const res = wallet.source === "wc"
? await BNS.registerTldWithExternalWallet(el, {
session: wallet.session, tld: candidateLabel, records: {},
serviceFee, tldListUrl: "https://navigate.st/api/tlds",
onProgress: push,
})
: await BNS.registerTldWithBuiltInWallet(el, {
wallet, tld: candidateLabel, records: {},
serviceFee, tldListUrl: "https://navigate.st/api/tlds",
onProgress: push,
});
push("broadcast: " + (res.txid || "(no txid)"));
msg.className = "msg ok";
msg.innerHTML = `✅ < b > .${esc(candidateLabel)}< / b > minted. Category < code > ${esc((res.category || "").slice(0, 16))}…< / code > < br >
The certificate now lives in your token address; names can be registered under it from
< a href = "./register.html?q=" > the name-registration page< / a > .`;
$("mint-submit").textContent = "Registered ✓";
// Refresh the live TLD table so the new one shows up.
setTimeout(() => loadTldList().then((l) => { registeredTlds = l; refreshSearch(); }), 3500);
} catch (e) {
msg.className = "msg err";
const raw = String(e.message || e);
if (/wallet is empty/i.test(raw)) {
msg.innerHTML = `Failed: wallet is empty. Fund < code > ${esc(wallet.address)}< / code > with chipnet coins first — free from
< a href = "https://tbch.googol.cash/" target = "_blank" rel = "noopener" > tbch.googol.cash< / a > or
< a href = "https://chipnet.bch.ninja/faucet" target = "_blank" rel = "noopener" > chipnet.bch.ninja/faucet< / a > .`;
} else {
msg.textContent = "Failed: " + raw;
}
push("error: " + raw);
$("mint-submit").disabled = false;
updateMintPrice();
} finally {
try { el?.close?.(); } catch {}
}
});
// ---------- ?q= deep-link (from landing search too, if someone comes with a label) ----------
(() => {
const q = normLabel(new URLSearchParams(location.search).get("q") || "");
if (!q) return;
$("q").value = q;
// wait for registeredTlds to load
const wait = setInterval(() => {
if (registeredTlds === null) return;
clearInterval(wait);
refreshSearch();
setTimeout(() => $("q").scrollIntoView({ behavior: "smooth", block: "start" }), 100);
}, 150);
})();
< / 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 >
< script defer src = "./js/profile-menu.js?v=20260907wallet" > < / script >
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
< / body >
< / html >