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).
581 lines
26 KiB
HTML
581 lines
26 KiB
HTML
<!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="./docs/">📚 Docs</a>
|
||
<a href="./theseus/">🧭 Theseus</a>
|
||
<a href="./register.html">🪪 Register</a>
|
||
<a href="./tld.html" class="here">🌐 TLD</a>
|
||
<a href="./brand/">🎨 Brand</a>
|
||
<a href="https://silentmode.st/" rel="noopener">Silent Mode ↗</a>
|
||
<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>
|
||
|
||
<footer>
|
||
TLD registration on <b>Sirius.X</b> ·
|
||
<a href="./">back to Sirius.X</a> ·
|
||
<a href="./docs/#tld-registry">how the TLD registry works</a> ·
|
||
<a href="./register.html">register a name under a TLD</a><br>
|
||
Alpha on chipnet. Your keys, your registry, your rules.
|
||
</footer>
|
||
|
||
<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>
|
||
<script defer src="./js/profile-menu.js?v=20260907rel"></script>
|
||
</body>
|
||
</html>
|