582 lines
26 KiB
HTML
582 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>
|