feat(sirius-x): merge register.html into landing via shared mint flow

Register.html and the landing were running duplicate name-search UIs.
The register one was reported broken; the landing one already works
inline. Merged both into the landing:

- New js/register-flow.js — the full mint modal + wallet setup (Create /
  Import / Unlock / WizardConnect / Fund / Confirm / Register / Point
  it somewhere) extracted from register.html's <script type='module'>
  and turned into a shared module. Injects its own scoped modal HTML +
  CSS into the host page on load and exposes
  window.siriusRegisterName(fullName) as its public entry point.

- Landing search results now have Register buttons that call the shared
  flow directly. The whole 'search -> pick name -> mint' journey stays
  on one page, no redirects, no duplicate search UI to maintain.

- register.html reduced to a redirect stub: preserves ?q= if present,
  refreshes/JS-forwards to './' (landing), and shows a one-line
  'Name search moved to the home page' fallback for JS-off users.
  Old links keep working; no /register.html deep links are broken.

- All nav 'Register' entries now point at './#search-input' (or
  '../#search-input' on subpages), scrolling straight to the landing
  search box. Footer injector and every internal href updated the
  same way. Zero live href='.../register.html' left in the tree.

Verified: register.html?q=hello redirects to /?q=hello; landing
search 'fresh42abc' -> Register button -> modal opens with
'Register fresh42abc.bch' at the wallet-choice step.
This commit is contained in:
Local Dev 2026-09-07 22:36:02 +02:00
parent b25fc40b0d
commit 94bf3bcf68
10 changed files with 604 additions and 867 deletions

View file

@ -82,7 +82,7 @@
<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="../#search-input">🪪 Register</a>
<a href="../tld.html">🌐 TLD</a>
<a href="../theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="./" class="here">🛠 Admin</a>

View file

@ -78,7 +78,7 @@
<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="../#search-input">🪪 Register</a>
<a href="../tld.html">🌐 TLD</a>
<a href="../theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="../portal.html" class="portal">🔑 Sign in</a>

View file

@ -82,7 +82,7 @@
<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="../#search-input">🪪 Register</a>
<a href="../tld.html">🌐 TLD</a>
<a href="../theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="../portal.html" class="portal">🔑 Sign in</a>

View file

@ -93,7 +93,7 @@
<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="./#search-input">🪪 Register</a>
<a href="./tld.html">🌐 TLD</a>
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="./portal.html" class="portal">🔑 Sign in</a>
@ -163,7 +163,7 @@
: state === "taken" ? '<span class="badge b-taken">taken</span>'
: '<span class="badge b-check">checking…</span>';
const btn = state === "ok"
? `<a class="btn acid" href="./register.html?q=${encodeURIComponent(full.split(".")[0])}">Register →</a>`
? `<button class="btn acid" data-register="${esc(full)}">Register →</button>`
: state === "taken"
? `<a class="btn ghost" href="https://navigate.st/bns/${encodeURIComponent(full)}" target="_blank" rel="noopener">view →</a>`
: "<span></span>";
@ -189,6 +189,25 @@
.map((t) => ({ full: `${label}.${t}`, state: taken.has(`${label}.${t}`) ? "taken" : "ok" }))
.sort((a, b) => (a.state === "ok" ? 0 : 1) - (b.state === "ok" ? 0 : 1));
out.innerHTML = rows.map((x) => row(x.full, x.state)).join("");
// Wire Register buttons — hand the chosen name off to the shared mint
// flow (js/register-flow.js exposes window.siriusRegisterName).
out.querySelectorAll("[data-register]").forEach((b) => {
b.onclick = () => {
const name = b.getAttribute("data-register");
if (typeof window.siriusRegisterName === "function") {
window.siriusRegisterName(name);
} else {
// Module still loading — retry once it settles.
b.textContent = "loading…"; b.disabled = true;
const t = setInterval(() => {
if (typeof window.siriusRegisterName === "function") {
clearInterval(t); b.disabled = false; b.textContent = "Register →";
window.siriusRegisterName(name);
}
}, 100);
}
};
});
}
$("search-input").addEventListener("input", () => {
@ -219,7 +238,7 @@
<h3><span class="dot d-chain"></span> Register a name</h3>
<p>Pick a label, search across every TLD in the registry at once, mint the name
certificate straight into your own wallet. A few cents in chain fees, no yearly renewal.</p>
<div class="cta"><a class="btn acid" href="./register.html">Search a name →</a></div>
<div class="cta"><a class="btn acid" href="#search-input">Search a name →</a></div>
</div>
<div class="card">
<h3><span class="dot d-chain"></span> Register a TLD</h3>
@ -396,6 +415,14 @@ process.exit(0);"</pre>
<footer id="site-footer"></footer>
<!-- Shared mint flow — modal + wallet steps used to live in register.html.
The module injects its own modal into <body> and exposes
window.siriusRegisterName(fullName) for the search-result buttons. -->
<script type="importmap">
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
</script>
<script type="module" src="./js/register-flow.js?v=20260907merge"></script>
<script defer src="./js/site-footer.js?v=20260907rel"></script>
<script defer src="./js/profile-menu.js?v=20260907wallet"></script>
</body>

538
js/register-flow.js Normal file
View file

@ -0,0 +1,538 @@
// Reusable name-registration mint flow — the modal + wallet setup + on-chain
// mint sequence that used to live inside register.html. Any Sirius.X page can
// include this module and call `window.siriusRegisterName(fullName)` to open
// the flow for a specific `<label>.<tld>`. The landing's search cards call
// it directly; a legacy register.html redirect keeps old links working.
//
// Injects its own modal HTML into <body> and its own CSS into <head> on load
// so the host page needs nothing but a single script include.
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) => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[c]));
const sats = (v) => Number(v).toLocaleString("en-US");
// ---------- inject CSS + modal HTML ----------
// The style block is a straight copy of register.html's modal/sheet styling
// — kept scoped by prefixing every selector with #sirius-register-modal so it
// cannot collide with the host page's own .modal / .sheet classes.
(function injectAssets() {
if (document.getElementById("sirius-register-flow-css")) return;
const css = document.createElement("style");
css.id = "sirius-register-flow-css";
css.textContent = `
#sirius-register-modal{position:fixed;inset:0;background:rgba(6,9,14,.82);backdrop-filter:blur(3px);
display:none;align-items:flex-start;justify-content:center;padding:3vh 1rem;overflow-y:auto;z-index:100}
#sirius-register-modal.open{display:flex}
#sirius-register-modal .sheet{background:var(--panel,#141a24);border:1px solid var(--line,rgba(255,255,255,.09));
border-radius:16px;max-width:620px;width:100%;padding:26px 28px 28px;color:var(--ink,#e7eaf1)}
#sirius-register-modal .sheet h3{margin:0 0 .2rem;font-size:1.25rem}
#sirius-register-modal .sheet .sub{color:var(--mut,#8b98a9);font-size:14px;margin:0 0 1.2rem}
#sirius-register-modal .sheet label{display:block;font-size:13px;color:var(--mut,#8b98a9);margin:14px 0 5px}
#sirius-register-modal .sheet input[type=text],
#sirius-register-modal .sheet input[type=password],
#sirius-register-modal .sheet textarea,
#sirius-register-modal .sheet select{
width:100%;padding:11px 13px;border-radius:10px;border:1px solid #ffffff22;background:#0e131b;
color:var(--ink,#e7eaf1);font-size:14px;font-family:inherit;outline:none}
#sirius-register-modal .sheet textarea{min-height:76px;resize:vertical;font-family:ui-monospace,monospace;font-size:13px}
#sirius-register-modal .sheet input:focus,
#sirius-register-modal .sheet textarea:focus{border-color:#4b7bec}
#sirius-register-modal .row{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
#sirius-register-modal .row.end{justify-content:flex-end}
#sirius-register-modal .x{position:absolute;top:14px;right:18px;background:none;border:none;color:var(--dim,#5e6678);font-size:22px;cursor:pointer}
#sirius-register-modal .sheetwrap{position:relative;width:100%;max-width:620px}
#sirius-register-modal .phrase{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px}
#sirius-register-modal .phrase span{background:#0e131b;border:1px solid var(--line,rgba(255,255,255,.09));
border-radius:8px;padding:8px 10px;font-family:ui-monospace,monospace;font-size:13px}
#sirius-register-modal .phrase span b{color:var(--dim,#5e6678);font-weight:400;margin-right:6px;font-size:11px}
#sirius-register-modal .addr{display:flex;gap:8px;align-items:center;margin-top:8px}
#sirius-register-modal .addr code{flex:1;word-break:break-all;background:#0e131b;
border:1px solid var(--line,rgba(255,255,255,.09));border-radius:8px;padding:9px 11px;
font-family:ui-monospace,monospace;font-size:12.5px}
#sirius-register-modal table.price{width:100%;border-collapse:collapse;margin-top:12px;font-size:14px}
#sirius-register-modal table.price td{padding:7px 0;border-bottom:1px solid var(--line,rgba(255,255,255,.09));color:var(--mut,#8b98a9)}
#sirius-register-modal table.price td:last-child{text-align:right;font-family:ui-monospace,monospace;color:var(--ink,#e7eaf1)}
#sirius-register-modal table.price tr.total td{border-bottom:none;color:var(--ink,#e7eaf1);font-weight:600;padding-top:12px}
#sirius-register-modal table.price tr.total td:last-child{color:var(--acid,#d6ff3d)}
#sirius-register-modal table.price td small{display:block;color:var(--dim,#5e6678);font-size:11.5px}
#sirius-register-modal .steps-log{margin-top:14px;font-family:ui-monospace,monospace;font-size:12.5px;
color:var(--mut,#8b98a9);background:#0e131b;border:1px solid var(--line,rgba(255,255,255,.09));
border-radius:10px;padding:12px 14px;max-height:180px;overflow-y:auto}
#sirius-register-modal .steps-log div::before{content:" ";color:var(--acid,#d6ff3d)}
#sirius-register-modal .err{color:var(--taken,#f6768a);font-size:13.5px;margin-top:12px}
#sirius-register-modal .ok{color:var(--ok,#4fd1a5)}
#sirius-register-modal .pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:999px;
background:rgba(214,255,61,.13);color:var(--acid,#d6ff3d);margin-left:8px;vertical-align:middle}
#sirius-register-modal .pill.soon{background:rgba(255,255,255,.07);color:var(--dim,#5e6678)}
#sirius-register-modal .wopt{display:flex;gap:14px;align-items:flex-start;background:var(--panel2,#18202c);
border:1px solid var(--line,rgba(255,255,255,.09));border-radius:12px;padding:14px 16px;cursor:pointer;
text-align:left;width:100%;color:inherit;font:inherit;margin-top:10px}
#sirius-register-modal .wopt:hover{border-color:#4b7bec}
#sirius-register-modal .wopt:disabled{opacity:.45;cursor:not-allowed}
#sirius-register-modal .wopt .ic{font-size:22px;line-height:1}
#sirius-register-modal .wopt b{display:block;font-size:14.5px}
#sirius-register-modal .wopt span{color:var(--mut,#8b98a9);font-size:13px}
#sirius-register-modal .chk{display:flex;gap:9px;align-items:flex-start;margin-top:14px;font-size:13.5px;color:var(--mut,#8b98a9)}
#sirius-register-modal .chk input{margin-top:3px}
#sirius-register-modal .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}
#sirius-register-modal .btn.ghost{background:transparent;border:1px solid var(--line,rgba(255,255,255,.09));color:var(--ink,#e7eaf1)}
#sirius-register-modal .btn.acid{background:var(--acid,#d6ff3d);color:#0b0e14;font-weight:600}
#sirius-register-modal .btn:hover{filter:brightness(1.12)}
#sirius-register-modal .btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
#sirius-register-modal .mono{background:#0e131b;border:1px solid var(--line,rgba(255,255,255,.09));
border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
#sirius-register-modal .muted{color:var(--dim,#5e6678);font-size:13px}
#sirius-register-modal .note{background:var(--panel,#141a24);border:1px solid var(--line,rgba(255,255,255,.09));
border-left:3px solid var(--acid,#d6ff3d);border-radius:10px;padding:14px 18px;
color:var(--mut,#8b98a9);font-size:14px;margin-top:1.4rem}
#sirius-register-modal .note b{color:var(--ink,#e7eaf1)}
#sirius-register-modal .note.warn{border-left-color:var(--warn,#ffc75f)}
`;
document.head.appendChild(css);
const modal = document.createElement("div");
modal.id = "sirius-register-modal";
modal.innerHTML = `
<div class="sheetwrap">
<button class="x" id="sirius-reg-close" title="close">×</button>
<div class="sheet" id="sirius-reg-sheet"></div>
</div>
`;
document.body.appendChild(modal);
})();
// ---------- flow state ----------
const state = { name: null, wallet: null, session: null, client: null, quote: null, result: null };
const modal = $("sirius-register-modal");
const sheet = $("sirius-reg-sheet");
function open() { modal.classList.add("open"); }
function close() {
modal.classList.remove("open");
if (state.client) { try { state.client.close(); } catch {} state.client = null; }
if (state.session) { try { state.session.disconnect(); } catch {} state.session = null; }
}
$("sirius-reg-close").onclick = close;
modal.addEventListener("click", (e) => { if (e.target === modal) close(); });
function render(html) { sheet.innerHTML = html; }
function err(e) {
sheet.querySelectorAll(".err").forEach((n) => n.remove());
const box = document.createElement("div");
box.className = "err";
box.textContent = e?.message ?? String(e);
sheet.appendChild(box);
}
async function client() {
if (!state.client) state.client = await BNS.connect();
return state.client;
}
function startFlow(name) {
state.name = name;
open();
stepWallet();
}
// Public API
window.siriusRegisterName = startFlow;
// ---------- step 1: wallet choice ----------
function stepWallet() {
const saved = BNS.BuiltInWallet.exists();
render(`
<h3>Register ${esc(state.name)}</h3>
<p class="sub">First, the wallet that will <b>own</b> the name. The certificate is minted straight
to it we never hold it.</p>
${saved ? `<button class="wopt" id="w-unlock"><span class="ic">🔓</span><span>
<b>Unlock my browser wallet</b>
<span>You already created one on this device.</span></span></button>` : ``}
<button class="wopt" id="w-new"><span class="ic"></span><span>
<b>Create a new wallet<span class="pill">easiest</span></b>
<span>Made here in your browser. You get a recovery phrase to write down it is the only key.</span></span></button>
<button class="wopt" id="w-import"><span class="ic">🔑</span><span>
<b>Import a recovery phrase</b>
<span>Restore a wallet you already have, from its 12-word phrase.</span></span></button>
<button class="wopt" id="w-wiz"><span class="ic">🪄</span><span>
<b>Connect a wallet WizardConnect<span class="pill">most private</span></b>
<span>Cashonize 0.9+ or Paytaca. Encrypted end-to-end over Nostr; the relay sees only
ciphertext. Your keys never leave your wallet.</span></span></button>
<div class="note" style="margin-top:18px">Everything below runs on <b>chipnet</b>, Bitcoin Cash's
test network. The coins are free and worth nothing this is an alpha, and it is the honest
place to try it before real money is involved.</div>
`);
if (saved) $("w-unlock").onclick = stepUnlock;
$("w-new").onclick = stepCreate;
$("w-import").onclick = stepImport;
$("w-wiz").onclick = () => stepExternal("wizard");
}
// ---------- WizardConnect (external wallet) ----------
async function stepExternal() {
const label = "WizardConnect";
render(`<h3>Connect with ${esc(label)}</h3><p class="sub">Starting the connection…</p>`);
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) {
render(`<h3>Could not start ${esc(label)}</h3><p class="sub">${esc(e.message)}</p>
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
$("back").onclick = stepWallet;
return;
}
const uri = session.qrUri ?? session.uri;
render(`
<h3>Open your wallet</h3>
<p class="sub">Scan or paste this into ${esc(label)}-capable wallet, then approve the connection.</p>
<div class="addr"><code id="uri">${esc(uri)}</code><button class="btn ghost" id="copy">Copy</button></div>
<div class="note warn" style="margin-top:16px">Waiting for your wallet
The link stays valid until you close this page.</div>
<div class="row end"><button class="btn ghost" id="cancel">Cancel</button></div>
`);
$("copy").onclick = () => navigator.clipboard?.writeText(uri);
$("cancel").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
try { await session.ready; } catch (e) { err(e); return; }
stepExternalConfirm(session, label);
}
async function stepExternalConfirm(session, label) {
render(`<h3>Connected</h3><p class="sub">Reading your wallet and building the registration…</p>`);
try {
const c = await client();
const addresses = await session.getAddresses();
const owner = addresses[0];
const balance = await BNS.getBalance(c, addresses);
const genesis = BNS.describeGenesisRequirement(balance.hasGenesisInput);
if (balance.sats === 0n) {
render(`<h3>That wallet is empty</h3>
<p class="sub">Fund <span class="mono">${esc(owner)}</span> with chipnet coins and reconnect.</p>
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
$("back").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
return;
}
const quote = await BNS.quoteRegistration(c, { name: state.name, ownerAddress: owner, records: {} });
const rows = BNS.priceSummary(quote.costs).map((l) => `
<tr class="${l.total ? "total" : ""}"><td>${esc(l.label)}${l.note ? `<small>${esc(l.note)}</small>` : ""}</td>
<td>${sats(l.sats)} sat</td></tr>`).join("");
render(`
<h3>Confirm ${esc(quote.displayName)}</h3>
<p class="sub">Connected to ${esc(label)}. The certificate is minted to
<span class="mono">${esc(owner)}</span> your wallet, your key.</p>
<table class="price">${rows}</table>
${genesis.ready ? "" : `<div class="note warn" style="margin-top:16px">
<b>Two approvals needed.</b> ${esc(genesis.explanation)}</div>`}
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="go">Register approve in your wallet</button></div>
`);
$("back").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
$("go").onclick = async () => {
render(`<h3>Approve in your wallet</h3>
<p class="sub">${esc(label)} has sent the transaction to your device. Check the details there
and approve. Do not close this tab.</p><div class="steps-log" id="log"></div>`);
const log = $("log");
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); };
try {
const res = await BNS.registerWithExternalWallet(await client(), {
session, name: state.name, ownerAddress: owner, records: {}, onProgress: say,
});
state.result = res;
state.session = session;
stepDone();
} catch (e) {
err(e);
const row = document.createElement("div"); row.className = "row end";
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
sheet.appendChild(row);
$("back").onclick = () => stepExternalConfirm(session, label);
}
};
} catch (e) { err(e); }
}
// ---------- built-in: create ----------
function stepCreate() {
render(`
<h3>Create your wallet</h3>
<p class="sub">Generated in this browser. The phrase is never sent anywhere not to us, not to anyone.</p>
<label for="pw">Password (encrypts the wallet on this device)</label>
<input type="password" id="pw" autocomplete="new-password" placeholder="at least 8 characters">
<label for="pw2">Repeat password</label>
<input type="password" id="pw2" autocomplete="new-password">
<div class="note warn" style="margin-top:16px"><b>There is no reset.</b> The password protects this
device; the recovery phrase you will see next <i>is</i> the wallet. Lose both and the name is gone
forever that is what self-custody means.</div>
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next">Generate phrase </button></div>
`);
$("back").onclick = stepWallet;
$("next").onclick = async () => {
const pw = $("pw").value, pw2 = $("pw2").value;
if (pw.length < 8) return err(new Error("password must be at least 8 characters"));
if (pw !== pw2) return err(new Error("passwords do not match"));
try {
const wallet = await BNS.BuiltInWallet.create();
await wallet.save(pw);
state.wallet = wallet;
stepPhrase();
} catch (e) { err(e); }
};
}
function stepPhrase() {
const words = state.wallet.mnemonic.split(" ");
render(`
<h3>Write this down. Now.</h3>
<p class="sub">These 12 words are your wallet. Anyone who has them owns your names; if you lose them,
nobody including us can recover anything.</p>
<div class="phrase">${words.map((w,i)=>`<span><b>${i+1}</b>${esc(w)}</span>`).join("")}</div>
<div class="row"><button class="btn ghost" id="copy">Copy phrase</button></div>
<label class="chk"><input type="checkbox" id="ack">
<span>I have written the phrase down somewhere safe and offline. I understand it cannot be reset.</span></label>
<div class="row end"><button class="btn acid" id="next" disabled>Continue </button></div>
`);
$("copy").onclick = () => navigator.clipboard?.writeText(state.wallet.mnemonic);
$("ack").onchange = (e) => { $("next").disabled = !e.target.checked; };
$("next").onclick = stepFund;
}
// ---------- built-in: import / unlock ----------
function stepImport() {
render(`
<h3>Import a recovery phrase</h3>
<p class="sub">12 words, separated by spaces. It stays in this browser.</p>
<label for="mn">Recovery phrase</label>
<textarea id="mn" spellcheck="false" placeholder="word word word …"></textarea>
<label for="pw">Password to encrypt it on this device</label>
<input type="password" id="pw" autocomplete="new-password">
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next">Import </button></div>
`);
$("back").onclick = stepWallet;
$("next").onclick = async () => {
try {
if ($("pw").value.length < 8) throw new Error("password must be at least 8 characters");
const wallet = await BNS.BuiltInWallet.fromMnemonic($("mn").value);
await wallet.save($("pw").value);
state.wallet = wallet;
stepFund();
} catch (e) { err(e); }
};
}
function stepUnlock() {
render(`
<h3>Unlock your wallet</h3>
<p class="sub">Decrypts the wallet stored in this browser.</p>
<label for="pw">Password</label>
<input type="password" id="pw" autocomplete="current-password">
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next">Unlock </button></div>
`);
$("back").onclick = stepWallet;
$("pw").onkeydown = (e) => { if (e.key === "Enter") $("next").click(); };
$("next").onclick = async () => {
try { state.wallet = await BNS.BuiltInWallet.load($("pw").value); stepFund(); }
catch (e) { err(e); }
};
}
// ---------- fund ----------
let fundTimer = null;
async function stepFund() {
const w = state.wallet;
const faucets = BNS.REGISTRAR.faucets
.map((f) => `<a href="${esc(f.url)}" target="_blank" rel="noopener" style="color:var(--acid,#d6ff3d)">${esc(f.name)}</a>`).join(" · ");
render(`
<h3>Fund your wallet</h3>
<p class="sub">Send test coins to this address. It is yours derived from your phrase.</p>
<div class="addr"><code id="a">${esc(w.address)}</code>
<button class="btn ghost" id="copy">Copy</button></div>
<div class="muted" style="margin-top:10px">Free chipnet coins: ${faucets}</div>
<div class="note" id="bal" style="margin-top:16px">checking balance</div>
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next" disabled>Continue </button></div>
`);
$("copy").onclick = () => navigator.clipboard?.writeText(w.address);
$("back").onclick = () => { clearInterval(fundTimer); stepWallet(); };
$("next").onclick = () => { clearInterval(fundTimer); stepConfirm(); };
const poll = async () => {
try {
const b = await BNS.getBalance(await client(), w.watchedAddresses);
const enough = b.sats >= 20000n;
$("bal").innerHTML = b.sats > 0n
? `<b class="ok">${sats(b.sats)} sat</b> available (${b.coins} coin${b.coins===1?"":"s"})`
+ (enough ? "" : ` — a little more is needed to cover the registration.`)
: `Waiting for coins… this page is watching the chain live.`;
$("next").disabled = !enough;
} catch (e) { $("bal").textContent = "could not reach the chain: " + e.message; }
};
await poll();
clearInterval(fundTimer);
fundTimer = setInterval(poll, 6000);
}
// ---------- confirm ----------
async function stepConfirm() {
render(`<h3>Preparing…</h3><p class="sub">Building your registration transaction.</p>`);
try {
const c = await client();
const avail = await BNS.checkAvailability(c, state.name);
if (!avail.available) throw new Error(`"${avail.name}" was just registered by someone else`);
const quote = await BNS.quoteRegistration(c, {
name: state.name, ownerAddress: state.wallet.address, records: {},
});
state.quote = quote;
const rows = BNS.priceSummary(quote.costs).map((l) => `
<tr class="${l.total ? "total" : ""}"><td>${esc(l.label)}${l.note ? `<small>${esc(l.note)}</small>` : ""}</td>
<td>${sats(l.sats)} sat</td></tr>`).join("");
render(`
<h3>Confirm ${esc(quote.displayName)}</h3>
<p class="sub">One transaction. It mints the certificate to <b>your</b> address and pays the fees.</p>
<table class="price">${rows}</table>
<div class="note" style="margin-top:16px">The certificate will be minted to
<span class="mono">${esc(state.wallet.address)}</span> your key, your name.
You can set where it points immediately afterwards.</div>
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="go">Register sign &amp; broadcast</button></div>
`);
$("back").onclick = stepFund;
$("go").onclick = stepRegister;
} catch (e) {
render(`<h3>Could not prepare</h3><p class="sub">${esc(e.message)}</p>
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
$("back").onclick = stepFund;
}
}
// ---------- register ----------
async function stepRegister() {
render(`<h3>Registering ${esc(state.name)}</h3>
<p class="sub">Signing in your browser and broadcasting to the chain. Do not close this tab.</p>
<div class="steps-log" id="log"></div>`);
const log = $("log");
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); log.scrollTop = log.scrollHeight; };
const words = {
"checking-availability": "checking the name is still free",
"loading-coins": "loading your coins",
"preparing-wallet": "preparing your wallet (one-off setup transaction)",
"prepared": "wallet prepared",
"building": "building the registration transaction",
"signing": "signing with your key — in this browser",
"broadcasting": "broadcasting to the Bitcoin Cash network",
"registered": "registered",
};
try {
const res = await BNS.registerWithBuiltInWallet(await client(), {
wallet: state.wallet, name: state.name, records: {},
onProgress: (s) => say(words[s] ?? s),
});
state.result = res;
stepDone();
} catch (e) {
err(e);
const row = document.createElement("div"); row.className = "row end";
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
sheet.appendChild(row);
$("back").onclick = stepConfirm;
}
}
// ---------- done + point somewhere ----------
function stepDone() {
const r = state.result;
render(`
<h3><span class="ok"></span> ${esc(r.displayName)} is yours</h3>
<p class="sub">The certificate is on the chain, held by your key. Nobody can take it back.</p>
<table class="price">
<tr><td>Transaction</td><td>${esc(r.txid.slice(0,20))}</td></tr>
<tr><td>Certificate ID</td><td>${esc(r.category.slice(0,20))}</td></tr>
<tr><td>Owner</td><td>${esc(r.ownerAddress.slice(0,22))}</td></tr>
</table>
<h3 style="margin-top:24px;font-size:1.05rem">Point it somewhere</h3>
<p class="sub">Optional, and changeable any time only your key can.</p>
<label for="kind">Record type</label>
<select id="kind">
<option value="h">A tiny site stored on the chain itself (h)</option>
<option value="u">Redirect to a web address (u)</option>
<option value="ip">Your own server's IPv4 address (ip)</option>
<option value="tls">TLS certificate fingerprint (tls)</option>
</select>
<label for="val" id="vl">HTML kept small; it lives inside the transaction</label>
<textarea id="val" spellcheck="false" placeholder="&lt;h1&gt;hello&lt;/h1&gt;"></textarea>
<div class="muted" id="budget"></div>
<div class="row end"><button class="btn ghost" id="skip">Done for now</button>
<button class="btn acid" id="set">Publish record</button></div>
`);
const labels = {
h: ["HTML — kept small; it lives inside the transaction", "<h1>hello</h1>"],
u: ["The https:// address the name should open", "https://example.org"],
ip: ["IPv4 address of your server", "203.0.113.9"],
tls: ["SHA-256 fingerprint of your TLS certificate (hex)", "a1b2c3…"],
};
const budget = () => {
const used = new TextEncoder().encode($("val").value).length;
const max = 200 - 30 - r.name.length;
$("budget").textContent = `${used} / ~${max} bytes used`;
$("budget").style.color = used > max ? "var(--taken,#f6768a)" : "";
};
$("kind").onchange = () => { const [l, p] = labels[$("kind").value]; $("vl").textContent = l; $("val").placeholder = p; budget(); };
$("val").oninput = budget;
budget();
$("skip").onclick = close;
$("set").onclick = async () => {
const records = { [$("kind").value]: $("val").value };
const external = !state.wallet && state.session;
render(`<h3>Publishing record</h3><p class="sub">This moves the certificate — which is how the chain
proves you are the owner.${external ? " Approve it in your wallet." : ""}</p>
<div class="steps-log" id="log"></div>`);
const log = $("log");
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); };
try {
const upd = external
? await BNS.setRecordsWithExternalWallet(await client(), {
session: state.session, name: r.displayName, records, onProgress: say,
})
: await BNS.setRecordsWithBuiltInWallet(await client(), {
wallet: state.wallet, name: r.displayName, records, onProgress: say,
});
render(`<h3><span class="ok">✓</span> ${esc(r.displayName)} now points somewhere</h3>
<p class="sub">Update transaction <span class="mono">${esc(upd.txid.slice(0,20))}</span> is on the chain.
Resolvers pick it up within a block.</p>
<div class="note">Open it with the Silent Mode resolver, or preview it at
<a href="https://navigate.st/bns/${encodeURIComponent(r.displayName)}" target="_blank" rel="noopener" style="color:var(--acid,#d6ff3d)">navigate.st</a>.</div>
<div class="row end"><button class="btn acid" id="fin">Finish</button></div>`);
$("fin").onclick = close;
} catch (e) {
err(e);
const row = document.createElement("div"); row.className = "row end";
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
sheet.appendChild(row); $("back").onclick = stepDone;
}
};
}

View file

@ -31,7 +31,7 @@
<div>
<h5>Product</h5>
<ul>
<li><a href="${s("register.html")}">🪪 Register a name</a></li>
<li><a href="${s("#search-input")}">🪪 Register a name</a></li>
<li><a href="${s("tld.html")}">🌐 Register a TLD</a></li>
<li><a href="${s("portal.html")}">🔑 My names portal</a></li>
<li><a href="${s("theseus/")}">🧭 Theseus browser</a></li>

View file

@ -114,7 +114,7 @@
<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="./#search-input">🪪 Register</a>
<a href="./tld.html">🌐 TLD</a>
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="./portal.html" class="portal here">🔑 Sign in</a>
@ -191,7 +191,7 @@
</div>
<div class="status" style="margin-top:14px">
Don't have a wallet? Create one during
<a href="./register.html">name registration</a> — the recovery phrase
<a href="./#search-input">name registration</a> — the recovery phrase
is generated in your browser and encrypted with your password.
</div>
</section>

View file

@ -4,865 +4,37 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Register a name — Sirius.X</title>
<meta name="description" content="Own a domain name on the Bitcoin Cash blockchain. Permanent, no renewal, censorship-resistant. Search availability, mint the name certificate to your own wallet, and pick decentralized hosting.">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Sirius.X">
<meta property="og:title" content="Register a name on Bitcoin Cash">
<meta property="og:description" content="Search any label across every BCNR TLD. Mint a name certificate straight to your own wallet. No renewals, no registrar.">
<meta property="og:url" content="https://silentmode.st/sirius-x/register.html">
<meta property="og:image" content="https://silentmode.st/sirius-x/brand/banner.svg">
<meta name="twitter:card" content="summary">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--chain:#d6ff3d; --sia:#b39ddb; --srv:#4fd1a5; --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}
.wrap{max-width:920px;margin:0 auto;padding:0 1.2rem}
a{color:var(--acid)}
header{text-align:center;padding:4.5rem 1.2rem 1rem}
.mark{font-size:52px;line-height:1}
h1{font-size:clamp(1.9rem,5vw,2.7rem);margin:.4rem 0 .3rem}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.05rem;max-width:600px;margin:0 auto}
/* search */
.search{max-width:620px;margin:2rem auto 0;padding:0 1.2rem}
.searchrow{display:flex;gap:8px}
.searchrow input{flex:1;padding:14px 18px;border-radius:12px;border:1px solid #ffffff22;
background:#141a24;color:var(--ink);font-size:17px;outline:none}
.searchrow input:focus{border-color:#4b7bec}
.searchrow select{padding:0 12px;border-radius:12px;border:1px solid #ffffff22;background:#141a24;color:var(--ink);font-size:15px}
.hint{margin-top:8px;font-size:12.5px}
.result{margin-top:14px;min-height:28px;font-size:15px;display:grid;gap:8px}
.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:12px 16px;min-height:52px}
.result .card.checking{opacity:.55}
.result .name{font-family:ui-monospace,monospace;font-size:17px;overflow-wrap:anywhere}
.result .card .btn{padding:8px 14px;font-size:13px}
.badge{font-size:12px;padding:2px 10px;border-radius:999px;font-weight:600;white-space:nowrap}
.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)}
.b-nogate{background:rgba(255,199,95,.14);color:var(--warn)}
@media (max-width:520px){
.result .card{grid-template-columns:1fr;gap:8px}
.result .card .btn{justify-self:start}
}
.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:hover{filter:brightness(1.12)}
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
.muted{color:var(--dim);font-size:13px}
section{padding:2.6rem 0;border-top:1px solid var(--line);margin-top:2.5rem}
section:first-of-type{border-top:none;margin-top:1.5rem}
h2{font-size:1.4rem;margin:0 0 .4rem}
.lede{color:var(--mut);margin:0 0 1.5rem;max-width:660px}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
.card h3{margin:0 0 .4rem;font-size:1.05rem;display:flex;align-items:center;gap:8px}
.card p{margin:0;color:var(--mut);font-size:13.5px}
.card .price{color:var(--acid);font-size:13px;margin-top:.6rem;font-weight:600}
.dot{width:9px;height:9px;border-radius:50%;display:inline-block}
.d-chain{background:var(--chain)} .d-sia{background:var(--sia)} .d-srv{background:var(--srv)}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.4rem}
.note b{color:var(--ink)}
.note.warn{border-left-color:var(--warn)}
.note.danger{border-left-color:var(--taken)}
ol.steps{counter-reset:s;list-style:none;padding:0;margin:0;display:grid;gap:10px}
ol.steps li{counter-increment:s;display:grid;grid-template-columns:30px 1fr;gap:14px;align-items:start;
background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:13px 16px}
/* The counter owns column 1; everything else stacks in column 2. Without the
explicit placement the <span> auto-flows onto row 2 of column 1 — the 30px
one — and the description renders one word per line. */
ol.steps li::before{content:counter(s);grid-column:1;grid-row:1;
display:grid;place-items:center;width:24px;height:24px;border-radius:50%;
background:rgba(214,255,61,.14);color:var(--acid);font-size:12.5px;font-weight:700}
ol.steps li > *{grid-column:2;min-width:0}
ol.steps b{color:var(--ink)} ol.steps span{color:var(--mut);font-size:14px}
code.inline,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
/* ---- registration flow ---- */
.modal{position:fixed;inset:0;background:rgba(6,9,14,.82);backdrop-filter:blur(3px);
display:none;align-items:flex-start;justify-content:center;padding:3vh 1rem;overflow-y:auto;z-index:50}
.modal.open{display:flex}
.sheet{background:var(--panel);border:1px solid var(--line);border-radius:16px;max-width:620px;width:100%;
padding:26px 28px 28px}
.sheet h3{margin:0 0 .2rem;font-size:1.25rem}
.sheet .sub{color:var(--mut);font-size:14px;margin:0 0 1.2rem}
.sheet label{display:block;font-size:13px;color:var(--mut);margin:14px 0 5px}
.sheet input[type=text],.sheet input[type=password],.sheet textarea,.sheet select{
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}
.sheet textarea{min-height:76px;resize:vertical;font-family:ui-monospace,monospace;font-size:13px}
.sheet input:focus,.sheet textarea:focus{border-color:#4b7bec}
.row{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
.row.end{justify-content:flex-end}
.x{position:absolute;top:14px;right:18px;background:none;border:none;color:var(--dim);font-size:22px;cursor:pointer}
.sheetwrap{position:relative;width:100%;max-width:620px}
.phrase{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px}
.phrase span{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:8px 10px;
font-family:ui-monospace,monospace;font-size:13px}
.phrase span b{color:var(--dim);font-weight:400;margin-right:6px;font-size:11px}
.addr{display:flex;gap:8px;align-items:center;margin-top:8px}
.addr code{flex:1;word-break:break-all;background:#0e131b;border:1px solid var(--line);border-radius:8px;
padding:9px 11px;font-family:ui-monospace,monospace;font-size:12.5px}
table.price{width:100%;border-collapse:collapse;margin-top:12px;font-size:14px}
table.price td{padding:7px 0;border-bottom:1px solid var(--line);color:var(--mut)}
table.price td:last-child{text-align:right;font-family:ui-monospace,monospace;color:var(--ink)}
table.price tr.total td{border-bottom:none;color:var(--ink);font-weight:600;padding-top:12px}
table.price tr.total td:last-child{color:var(--acid)}
table.price td small{display:block;color:var(--dim);font-size:11.5px}
.steps-log{margin-top:14px;font-family:ui-monospace,monospace;font-size:12.5px;color:var(--mut);
background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;max-height:180px;overflow-y:auto}
.steps-log div::before{content:" ";color:var(--acid)}
.err{color:var(--taken);font-size:13.5px;margin-top:12px}
.ok{color:var(--ok)}
.pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(214,255,61,.13);
color:var(--acid);margin-left:8px;vertical-align:middle}
.pill.soon{background:rgba(255,255,255,.07);color:var(--dim)}
.wopt{display:flex;gap:14px;align-items:flex-start;background:var(--panel2);border:1px solid var(--line);
border-radius:12px;padding:14px 16px;cursor:pointer;text-align:left;width:100%;color:inherit;font:inherit;margin-top:10px}
.wopt:hover{border-color:#4b7bec}
.wopt:disabled{opacity:.45;cursor:not-allowed}
.wopt .ic{font-size:22px;line-height:1}
.wopt b{display:block;font-size:14.5px}
.wopt span{color:var(--mut);font-size:13px}
.chk{display:flex;gap:9px;align-items:flex-start;margin-top:14px;font-size:13.5px;color:var(--mut)}
.chk input{margin-top:3px}
</style>
<link rel="stylesheet" href="https://silentmode.st/shared.css">
<link rel="icon" type="image/svg+xml" href="./assets/favicon.svg">
<meta name="description" content="Search a Bitcoin Cash name and mint the certificate to your own wallet. The name-registration flow lives on the Sirius.X landing.">
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="./assets/favicon.svg" color="#d6ff3d">
<meta name="robots" content="noindex">
<!-- register.html used to host a duplicate search-and-mint flow. The Sirius.X
landing now owns both the search UI and the shared mint modal
(js/register-flow.js), so this page redirects there with any `?q=` label
preserved. The meta-refresh is a fallback for JS-off; the script picks
up first when JS is on. -->
<meta http-equiv="refresh" content="0; url=./">
<script>
(function () {
// Preserve ?q= if the user (or an old link) still points here with a label.
const q = new URLSearchParams(location.search).get("q");
const target = q ? `./?q=${encodeURIComponent(q)}` : "./";
location.replace(target);
})();
</script>
<style>
/* sirius.x nav override — /shared.css above sets a Silent Mode-flavoured
.topnav; these rules layer on top so the register page matches the rest of
sirius.x (portal, docs, brand, theseus). */
nav.topnav.sirius{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)}
nav.topnav.sirius .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px}
nav.topnav.sirius .brand .g{color:var(--acid)}
nav.topnav.sirius a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
nav.topnav.sirius a:hover{color:var(--ink);background:var(--panel)}
nav.topnav.sirius a.here{color:var(--acid);background:rgba(214,255,61,.08)}
nav.topnav.sirius a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
nav.topnav.sirius a.portal:hover{background:rgba(214,255,61,.18)}
body{margin:0;font:16px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
background:#0b0e14;color:#8b98a9;min-height:100vh;
display:flex;align-items:center;justify-content:center;padding:2rem;text-align:center}
a{color:#d6ff3d}
.mark{font-size:40px;line-height:1;margin-bottom:.6rem}
</style>
</head>
<body>
<nav class="topnav sirius">
<a class="brand" href="./"><span class="g">Sirius.X</span></a>
<a href="./#try">Try it</a>
<a href="./register.html" class="here">🪪 Register</a>
<a href="./tld.html">🌐 TLD</a>
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="./portal.html" class="portal">🔑 Sign in</a>
</nav>
<header>
<div class="mark">⛓️</div>
<h1>Own your <span class="g">name</span></h1>
<p class="tag">A domain name that lives on the Bitcoin Cash blockchain. You hold it — permanently,
with no renewal, that no registrar can revoke or seize.</p>
</header>
<div class="search">
<div class="searchrow">
<input id="q" placeholder="search a name…" spellcheck="false" autocomplete="off" autofocus>
<div>
<div class="mark"></div>
<p>Redirecting to <a href="./">Sirius.X</a></p>
<p style="font-size:12.5px;color:#5e6678;margin-top:1rem">Name search moved to the home page.</p>
</div>
<div class="hint muted">One search checks the name across every TLD. Available names appear first.</div>
<div class="result" id="result"><span class="muted">Type a name to check availability on the chain.</span></div>
</div>
<div class="wrap">
<section>
<h2>How registration works</h2>
<p class="lede">You are not buying a name from us — you are minting a name <b>certificate you own</b> on the
Bitcoin Cash chain. We provide the tool; the chain gives you the name.</p>
<ol class="steps">
<li><b>You hold the key.</b> <span>The name is a certificate on the chain — a title deed — controlled by your
wallet. Whoever holds the key owns the name: not us, not a registrar. It cannot be taken back.</span></li>
<li><b>You pay the chain, not a middleman.</b> <span>Registration is a single Bitcoin Cash transaction —
a few cents in network fees, plus a small service fee. No yearly renewal, ever.</span></li>
<li><b>You point it anywhere.</b> <span>Attach a record: store the site on the chain itself, on the Sia
network, or on any server you run. Update it any time — only your key can.</span></li>
</ol>
<div class="note"><b>Self-custody, by design.</b> Because you hold the key, only you can transfer or
change the name. That is the whole point — it also means <b>keep your recovery phrase safe</b>;
there is no company that can reset it for you. We never take custody of your name or your funds,
and your recovery phrase never leaves your browser.</div>
</section>
<section id="hosting">
<h2>Hosting — where your site lives</h2>
<p class="lede">Point your name at any of these. Mix and match; change it whenever you like.</p>
<div class="grid">
<div class="card">
<h3><span class="dot d-chain"></span> On the blockchain</h3>
<p>Tiny sites live <i>inside the transaction itself</i> — no server, no host, permanent as the chain.
Best for a page, a profile, a link hub.</p>
<div class="price">≈ free · a few cents once</div>
</div>
<div class="card">
<h3><span class="dot d-sia"></span> Sia network</h3>
<p>Full multi-file sites, encrypted and scattered across independent storage hosts worldwide.
No hosting company to bill or suspend you.</p>
<div class="price">≈ $13 / TB / month (a website: pennies)</div>
</div>
<div class="card">
<h3><span class="dot d-srv"></span> Your own server</h3>
<p>Already run a server or VPS? Point the name straight at it. Works with anything you host —
the name just resolves to your IP.</p>
<div class="price">your server's cost</div>
</div>
<div class="card">
<h3><span class="dot d-sia"></span> IPFS <span class="muted">(soon)</span></h3>
<p>Content-addressed peer-to-peer storage. The name pins a content hash; anyone can help serve it.
Coming as a record type.</p>
<div class="price">free + optional pinning</div>
</div>
</div>
</section>
<section>
<h2>Anonymous VPS — if you want your own server</h2>
<p class="lede">For the "your own server" option, or to run a node. Privacy-first providers that take
crypto and ask for no identity — consistent with owning a censorship-resistant name.</p>
<div class="grid">
<div class="card"><h3>MyNymBox</h3><p>No-KYC VPS, web hosting, and anonymous domain registration.
Pays in Monero / Bitcoin / Lightning. Free WHOIS privacy.</p><div class="price">from ~€3.50/mo</div></div>
<div class="card"><h3>Njalla</h3><p>Privacy-by-proxy domains and VPS — they hold the registration on your
behalf, you keep control. Crypto accepted.</p><div class="price">domains ~€15/yr</div></div>
<div class="card"><h3>1984 / others</h3><p>Iceland-based, privacy-respecting hosting. Several no-KYC
providers exist — pick one outside your jurisdiction, pay in Monero.</p><div class="price">varies</div></div>
</div>
<div class="note">These are independent third parties, listed for convenience — not affiliated with Silent
Mode. For a name you never have to renew and nobody can seize, the name itself lives on the chain; a VPS
only matters if you choose to host content on your own server.</div>
</section>
</div>
<footer id="site-footer"></footer>
<!-- One shared libauth for every bundle. The browser's module loader guarantees
its top-level-await WASM init settles before any importer runs — which a
per-bundle copy does not. See Argus/src/web/libauth-entry.js. -->
<script type="importmap">
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
</script>
<!-- ───────────────────────── registration flow ───────────────────────── -->
<div class="modal" id="modal">
<div class="sheetwrap">
<button class="x" id="close" title="close">×</button>
<div class="sheet" id="sheet"></div>
</div>
</div>
<script type="module">
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260907tldext";
const API = "https://navigate.st/api/name/";
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;" }[c]));
const sats = (v) => Number(v).toLocaleString("en-US");
// ---------- availability search (multi-TLD) ----------
// The user types one label; we fan out to every TLD in TLDS in parallel and
// render results as they land, available first. Same lookup() call the previous
// single-TLD version used — API for speed, chain via BNS as authoritative
// fallback. The TLD list here is deliberately client-side: it mirrors the
// on-chain seed set (Decentralized.DNS/DESIGN-tld-registry.md §9) plus TLDs
// under active discussion. TLDs marked `onchain:false` show a "not-yet-a-TLD"
// warning so nobody registers a name under a TLD that isn't in the registry
// (the enforcement gate would reject it later).
const TLDS = [
{ tld: "bch", onchain: true },
{ tld: "p2p", onchain: true },
{ tld: "bit", onchain: true },
{ tld: "nav", onchain: true },
{ tld: "test", onchain: true },
{ tld: "x", onchain: true },
{ tld: "asm", onchain: true },
{ tld: "neo", onchain: true },
{ tld: "gt", onchain: true },
{ tld: "sc", onchain: true },
{ tld: "sia", onchain: true },
{ tld: "dex", onchain: true },
{ tld: "cex", onchain: true },
{ tld: "nt", onchain: true },
{ tld: "dea", onchain: true },
{ tld: "hub", onchain: true },
{ tld: "os", onchain: true },
{ tld: "vpn", onchain: true },
// .com is held by the operator on-chain but excluded from the public
// registrar UI (2026-08-29 decision) — the TLD certificate stays minted so
// nobody else can claim it, but no second-level names are offered.
];
let timer, lastReq = 0;
function clean(v){ return v.toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0,32); }
// Availability, two ways. The API is a convenience (fast, cached); the chain is
// the authority. If the API is unreachable — down, blocked, censored — fall
// straight through to electrum, which is the same source the API reads. A name
// service that stops working when one HTTP host does would rather miss the point.
async function lookup(full) {
try {
const r = await fetch(API + encodeURIComponent(full));
if (!r.ok) throw new Error("api " + r.status);
return await r.json();
} catch {
const a = await BNS.checkAvailability(await client(), full);
return { name: a.name, registered: !a.available, records: a.entry?.records ?? {}, viaChain: true };
}
}
// One row per TLD. Cards start in "checking" state; each fetch replaces its own
// card as it resolves. Available cards are then moved to the top and Register
// buttons wired — this happens after every fetch so the ordering settles as
// results come in, and the user sees available names first without a full
// re-render flash.
function rowFor(tld, state, full, extra = "") {
const btnHtml = state === "ok"
? `<button class="btn acid" data-register="${esc(full)}">Register →</button>`
: state === "taken"
? `<a class="btn ghost" href="https://navigate.st/bns/${encodeURIComponent(full)}" target="_blank" rel="noopener">view →</a>`
: `<span></span>`;
const badge = state === "ok" ? '<span class="badge b-ok">available</span>'
: state === "taken" ? '<span class="badge b-taken">taken</span>'
: state === "nogate" ? '<span class="badge b-nogate">not on TLD registry yet</span>'
: state === "error" ? '<span class="badge b-check">check failed</span>'
: '<span class="badge b-check">checking…</span>';
return `<div class="card ${state === "checking" ? "checking" : ""}" data-tld="${esc(tld)}" data-state="${esc(state)}">`
+ `<span class="name">${esc(full)}</span>${badge}${btnHtml}${extra}</div>`;
}
function resort(res) {
// Available first, then taken/nogate/error/checking. Stable within groups.
const rank = { ok: 0, taken: 1, nogate: 2, error: 3, checking: 4 };
const cards = [...res.querySelectorAll(".card")].sort((a, b) => (rank[a.dataset.state] ?? 9) - (rank[b.dataset.state] ?? 9));
for (const c of cards) res.appendChild(c);
}
async function check() {
const label = clean($("q").value);
const res = $("result");
if (!label) { res.innerHTML = '<span class="muted">Type a name to check availability on the chain.</span>'; return; }
const my = ++lastReq;
// Seed the result grid with a checking card per TLD, in canonical order.
res.innerHTML = TLDS.map(({ tld }) => rowFor(tld, "checking", label + "." + tld)).join("");
// Fan out lookups. Update each card as its result arrives; re-sort after each.
await Promise.all(TLDS.map(async ({ tld, onchain }) => {
const full = label + "." + tld;
let state = "error";
if (!onchain) {
// The TLD is not on the TLD beacon (design's enforcement gate would
// reject any REG). Skip the API round-trip and mark clearly; wiring an
// available/taken lookup here would mislead the user into a doomed reg.
state = "nogate";
} else {
try {
const j = await lookup(full);
if (my !== lastReq) return; // stale keystroke
if (j.error) state = "error";
else state = j.registered ? "taken" : "ok";
} catch { state = "error"; }
}
if (my !== lastReq) return;
const card = res.querySelector(`.card[data-tld="${tld}"]`);
if (!card) return;
card.outerHTML = rowFor(tld, state, full);
resort(res);
// Rewire Register buttons after DOM churn.
res.querySelectorAll("[data-register]").forEach((b) => {
b.onclick = () => startFlow(b.getAttribute("data-register"));
});
}));
}
$("q").addEventListener("input", () => {
clearTimeout(timer);
// Show an immediate placeholder so the input never looks unresponsive
// during the 350ms debounce window. `check()` overwrites this the moment
// it runs.
const raw = clean($("q").value);
if (raw) $("result").innerHTML = `<span class="muted">Checking <b>${esc(raw)}</b> across every TLD…</span>`;
timer = setTimeout(check, 350);
});
$("q").addEventListener("keydown", (e) => { if (e.key === "Enter") { clearTimeout(timer); check(); } });
// ---------- ?q= deep-link ----------
// The landing page has a search box that submits to register.html?q=<label>,
// so people can search from the sirius.x home without clicking through. If
// the URL carries a `q`, prefill the input and trigger the same lookup.
(() => {
const q = new URLSearchParams(location.search).get("q");
if (!q) return;
const clean = q.toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 63);
if (!clean) return;
$("q").value = clean;
// Small delay so the results container is in the layout before we scroll.
setTimeout(() => { check(); $("q").scrollIntoView({ behavior: "smooth", block: "start" }); }, 100);
})();
// ---------- flow state ----------
const state = { name: null, wallet: null, session: null, client: null, quote: null, result: null };
const modal = $("modal"), sheet = $("sheet");
function open(){ modal.classList.add("open"); }
function close(){
modal.classList.remove("open");
if (state.client) { try { state.client.close(); } catch {} state.client = null; }
if (state.session) { try { state.session.disconnect(); } catch {} state.session = null; }
}
$("close").onclick = close;
modal.addEventListener("click", (e) => { if (e.target === modal) close(); });
function render(html){ sheet.innerHTML = html; }
function err(e){
sheet.querySelectorAll(".err").forEach((n) => n.remove()); // one error at a time, not a pile
const box = document.createElement("div");
box.className = "err";
box.textContent = e?.message ?? String(e);
sheet.appendChild(box);
}
async function client(){
if (!state.client) state.client = await BNS.connect();
return state.client;
}
function startFlow(name){
state.name = name;
open();
stepWallet();
}
// ---------- step 1: choose a wallet ----------
function stepWallet(){
const saved = BNS.BuiltInWallet.exists();
render(`
<h3>Register ${esc(state.name)}</h3>
<p class="sub">First, the wallet that will <b>own</b> the name. The certificate is minted
straight to it — we never hold it.</p>
${saved ? `<button class="wopt" id="w-unlock"><span class="ic">🔓</span><span>
<b>Unlock my browser wallet</b>
<span>You already created one on this device.</span></span></button>` : ``}
<button class="wopt" id="w-new"><span class="ic"></span><span>
<b>Create a new wallet<span class="pill">easiest</span></b>
<span>Made here in your browser. You get a recovery phrase to write down — it is the only key.</span></span></button>
<button class="wopt" id="w-import"><span class="ic">🔑</span><span>
<b>Import a recovery phrase</b>
<span>Restore a wallet you already have, from its 12-word phrase.</span></span></button>
<button class="wopt" id="w-wiz"><span class="ic">🪄</span><span>
<b>Connect a wallet — WizardConnect<span class="pill">most private</span></b>
<span>Cashonize 0.9+ or Paytaca. Encrypted end-to-end over Nostr; the relay sees only
ciphertext. Your keys never leave your wallet.</span></span></button>
<div class="note" style="margin-top:18px">Everything below runs on <b>chipnet</b>, Bitcoin Cash's
test network. The coins are free and worth nothing — this is an alpha, and it is the honest place
to try it before real money is involved.</div>
`);
if (saved) $("w-unlock").onclick = stepUnlock;
$("w-new").onclick = stepCreate;
$("w-import").onclick = stepImport;
$("w-wiz").onclick = () => stepExternal("wizard");
}
// ---------- external wallets (WizardConnect) ----------
// Takes the identical transaction the built-in path builds; only the signing
// transport differs. The buyer approves on their own device.
async function stepExternal(){
const label = "WizardConnect";
render(`<h3>Connect with ${esc(label)}</h3><p class="sub">Starting the connection…</p>`);
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,
// libauth lives in the main bundle only — see the note in wizardconnect.js
crypto: {
pubkeyToAddress: BNS.pubkeyToAddress,
pubkeyToLockingBytecode: BNS.pubkeyToLockingBytecode,
binToHex: BNS.binToHex,
txidOfHex: BNS.txidOfHex,
},
});
} catch (e) {
render(`<h3>Could not start ${esc(label)}</h3><p class="sub">${esc(e.message)}</p>
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
$("back").onclick = stepWallet;
return;
}
const uri = session.qrUri ?? session.uri;
render(`
<h3>Open your wallet</h3>
<p class="sub">Scan or paste this into ${esc(label)}-capable wallet, then approve the connection.</p>
<div class="addr"><code id="uri">${esc(uri)}</code><button class="btn ghost" id="copy">Copy</button></div>
<div class="note warn" style="margin-top:16px">Waiting for your wallet…
The link stays valid until you close this page.</div>
<div class="row end"><button class="btn ghost" id="cancel">Cancel</button></div>
`);
$("copy").onclick = () => navigator.clipboard?.writeText(uri);
$("cancel").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
try {
await session.ready;
} catch (e) {
err(e);
return;
}
stepExternalConfirm(session, label);
}
async function stepExternalConfirm(session, label){
render(`<h3>Connected</h3><p class="sub">Reading your wallet and building the registration…</p>`);
try {
const c = await client();
const addresses = await session.getAddresses();
const owner = addresses[0];
const balance = await BNS.getBalance(c, addresses);
const genesis = BNS.describeGenesisRequirement(balance.hasGenesisInput);
if (balance.sats === 0n) {
render(`<h3>That wallet is empty</h3>
<p class="sub">Fund <span class="mono">${esc(owner)}</span> with chipnet coins and reconnect.</p>
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
$("back").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
return;
}
const quote = await BNS.quoteRegistration(c, { name: state.name, ownerAddress: owner, records: {} });
const rows = BNS.priceSummary(quote.costs).map((l) => `
<tr class="${l.total ? "total" : ""}"><td>${esc(l.label)}${l.note ? `<small>${esc(l.note)}</small>` : ""}</td>
<td>${sats(l.sats)} sat</td></tr>`).join("");
render(`
<h3>Confirm ${esc(quote.displayName)}</h3>
<p class="sub">Connected to ${esc(label)}. The certificate is minted to
<span class="mono">${esc(owner)}</span> — your wallet, your key.</p>
<table class="price">${rows}</table>
${genesis.ready ? "" : `<div class="note warn" style="margin-top:16px">
<b>Two approvals needed.</b> ${esc(genesis.explanation)}</div>`}
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="go">Register — approve in your wallet</button></div>
`);
$("back").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
$("go").onclick = async () => {
render(`<h3>Approve in your wallet</h3>
<p class="sub">${esc(label)} has sent the transaction to your device. Check the details there
and approve. Do not close this tab.</p><div class="steps-log" id="log"></div>`);
const log = $("log");
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); };
try {
const res = await BNS.registerWithExternalWallet(await client(), {
session, name: state.name, ownerAddress: owner, records: {}, onProgress: say,
});
state.result = res;
state.session = session; // keep it open — the record step signs too
stepDone();
} catch (e) {
err(e);
const row = document.createElement("div"); row.className = "row end";
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
sheet.appendChild(row);
$("back").onclick = () => stepExternalConfirm(session, label);
}
};
} catch (e) {
err(e);
}
}
// ---------- step 2a: create ----------
function stepCreate(){
render(`
<h3>Create your wallet</h3>
<p class="sub">Generated in this browser. The phrase is never sent anywhere — not to us, not to anyone.</p>
<label for="pw">Password (encrypts the wallet on this device)</label>
<input type="password" id="pw" autocomplete="new-password" placeholder="at least 8 characters">
<label for="pw2">Repeat password</label>
<input type="password" id="pw2" autocomplete="new-password">
<div class="note warn" style="margin-top:16px"><b>There is no reset.</b> The password protects this
device; the recovery phrase you will see next <i>is</i> the wallet. Lose both and the name is gone
forever — that is what self-custody means.</div>
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next">Generate phrase →</button></div>
`);
$("back").onclick = stepWallet;
$("next").onclick = async () => {
const pw = $("pw").value, pw2 = $("pw2").value;
if (pw.length < 8) return err(new Error("password must be at least 8 characters"));
if (pw !== pw2) return err(new Error("passwords do not match"));
try {
const wallet = await BNS.BuiltInWallet.create();
await wallet.save(pw);
state.wallet = wallet;
stepPhrase();
} catch (e) { err(e); }
};
}
function stepPhrase(){
const words = state.wallet.mnemonic.split(" ");
render(`
<h3>Write this down. Now.</h3>
<p class="sub">These 12 words are your wallet. Anyone who has them owns your names; if you lose them,
nobody — including us — can recover anything.</p>
<div class="phrase">${words.map((w,i)=>`<span><b>${i+1}</b>${esc(w)}</span>`).join("")}</div>
<div class="row"><button class="btn ghost" id="copy">Copy phrase</button></div>
<label class="chk"><input type="checkbox" id="ack">
<span>I have written the phrase down somewhere safe and offline. I understand it cannot be reset.</span></label>
<div class="row end"><button class="btn acid" id="next" disabled>Continue →</button></div>
`);
$("copy").onclick = () => navigator.clipboard?.writeText(state.wallet.mnemonic);
$("ack").onchange = (e) => { $("next").disabled = !e.target.checked; };
$("next").onclick = stepFund;
}
// ---------- step 2b: import / unlock ----------
function stepImport(){
render(`
<h3>Import a recovery phrase</h3>
<p class="sub">12 words, separated by spaces. It stays in this browser.</p>
<label for="mn">Recovery phrase</label>
<textarea id="mn" spellcheck="false" placeholder="word word word …"></textarea>
<label for="pw">Password to encrypt it on this device</label>
<input type="password" id="pw" autocomplete="new-password">
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next">Import →</button></div>
`);
$("back").onclick = stepWallet;
$("next").onclick = async () => {
try {
if ($("pw").value.length < 8) throw new Error("password must be at least 8 characters");
const wallet = await BNS.BuiltInWallet.fromMnemonic($("mn").value);
await wallet.save($("pw").value);
state.wallet = wallet;
stepFund();
} catch (e) { err(e); }
};
}
function stepUnlock(){
render(`
<h3>Unlock your wallet</h3>
<p class="sub">Decrypts the wallet stored in this browser.</p>
<label for="pw">Password</label>
<input type="password" id="pw" autocomplete="current-password">
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next">Unlock →</button></div>
`);
$("back").onclick = stepWallet;
$("pw").onkeydown = (e) => { if (e.key === "Enter") $("next").click(); };
$("next").onclick = async () => {
try { state.wallet = await BNS.BuiltInWallet.load($("pw").value); stepFund(); }
catch (e) { err(e); }
};
}
// ---------- step 3: fund ----------
let fundTimer = null;
async function stepFund(){
const w = state.wallet;
const faucets = BNS.REGISTRAR.faucets
.map((f) => `<a href="${esc(f.url)}" target="_blank" rel="noopener">${esc(f.name)}</a>`).join(" · ");
render(`
<h3>Fund your wallet</h3>
<p class="sub">Send test coins to this address. It is yours — derived from your phrase.</p>
<div class="addr"><code id="a">${esc(w.address)}</code>
<button class="btn ghost" id="copy">Copy</button></div>
<div class="muted" style="margin-top:10px">Free chipnet coins: ${faucets}</div>
<div class="note" id="bal" style="margin-top:16px">checking balance…</div>
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="next" disabled>Continue →</button></div>
`);
$("copy").onclick = () => navigator.clipboard?.writeText(w.address);
$("back").onclick = () => { clearInterval(fundTimer); stepWallet(); };
$("next").onclick = () => { clearInterval(fundTimer); stepConfirm(); };
const poll = async () => {
try {
const b = await BNS.getBalance(await client(), w.watchedAddresses);
const enough = b.sats >= 20000n;
$("bal").innerHTML = b.sats > 0n
? `<b class="ok">${sats(b.sats)} sat</b> available (${b.coins} coin${b.coins===1?"":"s"})`
+ (enough ? "" : ` — a little more is needed to cover the registration.`)
: `Waiting for coins… this page is watching the chain live.`;
$("next").disabled = !enough;
} catch (e) { $("bal").textContent = "could not reach the chain: " + e.message; }
};
await poll();
clearInterval(fundTimer);
fundTimer = setInterval(poll, 6000);
}
// ---------- step 4: confirm ----------
async function stepConfirm(){
render(`<h3>Preparing…</h3><p class="sub">Building your registration transaction.</p>`);
try {
const c = await client();
const avail = await BNS.checkAvailability(c, state.name);
if (!avail.available) throw new Error(`"${avail.name}" was just registered by someone else`);
const quote = await BNS.quoteRegistration(c, {
name: state.name,
ownerAddress: state.wallet.address,
records: {},
});
state.quote = quote;
const rows = BNS.priceSummary(quote.costs).map((l) => `
<tr class="${l.total ? "total" : ""}"><td>${esc(l.label)}${l.note ? `<small>${esc(l.note)}</small>` : ""}</td>
<td>${sats(l.sats)} sat</td></tr>`).join("");
render(`
<h3>Confirm ${esc(quote.displayName)}</h3>
<p class="sub">One transaction. It mints the certificate to <b>your</b> address and pays the fees.</p>
<table class="price">${rows}</table>
<div class="note" style="margin-top:16px">The certificate will be minted to
<span class="mono">${esc(state.wallet.address)}</span> — your key, your name.
You can set where it points immediately afterwards.</div>
<div class="row end"><button class="btn ghost" id="back">Back</button>
<button class="btn acid" id="go">Register — sign &amp; broadcast</button></div>
`);
$("back").onclick = stepFund;
$("go").onclick = stepRegister;
} catch (e) {
render(`<h3>Could not prepare</h3><p class="sub">${esc(e.message)}</p>
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
$("back").onclick = stepFund;
}
}
// ---------- step 5: register ----------
async function stepRegister(){
render(`<h3>Registering ${esc(state.name)}</h3>
<p class="sub">Signing in your browser and broadcasting to the chain. Do not close this tab.</p>
<div class="steps-log" id="log"></div>`);
const log = $("log");
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); log.scrollTop = log.scrollHeight; };
const words = {
"checking-availability": "checking the name is still free",
"loading-coins": "loading your coins",
"preparing-wallet": "preparing your wallet (one-off setup transaction)",
"prepared": "wallet prepared",
"building": "building the registration transaction",
"signing": "signing with your key — in this browser",
"broadcasting": "broadcasting to the Bitcoin Cash network",
"registered": "registered",
};
try {
const res = await BNS.registerWithBuiltInWallet(await client(), {
wallet: state.wallet,
name: state.name,
records: {},
onProgress: (s) => say(words[s] ?? s),
});
state.result = res;
stepDone();
} catch (e) {
err(e);
const row = document.createElement("div");
row.className = "row end";
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
sheet.appendChild(row);
$("back").onclick = stepConfirm;
}
}
// ---------- step 6: done + point it somewhere ----------
function stepDone(){
const r = state.result;
render(`
<h3><span class="ok"></span> ${esc(r.displayName)} is yours</h3>
<p class="sub">The certificate is on the chain, held by your key. Nobody can take it back.</p>
<table class="price">
<tr><td>Transaction</td><td>${esc(r.txid.slice(0,20))}…</td></tr>
<tr><td>Certificate ID</td><td>${esc(r.category.slice(0,20))}…</td></tr>
<tr><td>Owner</td><td>${esc(r.ownerAddress.slice(0,22))}…</td></tr>
</table>
<h3 style="margin-top:24px;font-size:1.05rem">Point it somewhere</h3>
<p class="sub">Optional, and changeable any time — only your key can.</p>
<label for="kind">Record type</label>
<select id="kind">
<option value="h">A tiny site stored on the chain itself (h)</option>
<option value="u">Redirect to a web address (u)</option>
<option value="ip">Your own server's IPv4 address (ip)</option>
<option value="tls">TLS certificate fingerprint (tls)</option>
</select>
<label for="val" id="vl">HTML — kept small; it lives inside the transaction</label>
<textarea id="val" spellcheck="false" placeholder="&lt;h1&gt;hello&lt;/h1&gt;"></textarea>
<div class="muted" id="budget"></div>
<div class="row end"><button class="btn ghost" id="skip">Done for now</button>
<button class="btn acid" id="set">Publish record</button></div>
`);
const labels = {
h: ["HTML — kept small; it lives inside the transaction", "<h1>hello</h1>"],
u: ["The https:// address the name should open", "https://example.org"],
ip: ["IPv4 address of your server", "203.0.113.9"],
tls: ["SHA-256 fingerprint of your TLS certificate (hex)", "a1b2c3…"],
};
const budget = () => {
const used = new TextEncoder().encode($("val").value).length;
const max = 200 - 30 - r.name.length;
$("budget").textContent = `${used} / ~${max} bytes used`;
$("budget").style.color = used > max ? "var(--taken)" : "";
};
$("kind").onchange = () => { const [l, p] = labels[$("kind").value]; $("vl").textContent = l; $("val").placeholder = p; budget(); };
$("val").oninput = budget;
budget();
$("skip").onclick = close;
$("set").onclick = async () => {
const records = { [$("kind").value]: $("val").value };
const external = !state.wallet && state.session;
render(`<h3>Publishing record</h3><p class="sub">This moves the certificate — which is how the chain
proves you are the owner.${external ? " Approve it in your wallet." : ""}</p>
<div class="steps-log" id="log"></div>`);
const log = $("log");
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); };
try {
const upd = external
? await BNS.setRecordsWithExternalWallet(await client(), {
session: state.session, name: r.displayName, records, onProgress: say,
})
: await BNS.setRecordsWithBuiltInWallet(await client(), {
wallet: state.wallet, name: r.displayName, records, onProgress: say,
});
render(`<h3><span class="ok"></span> ${esc(r.displayName)} now points somewhere</h3>
<p class="sub">Update transaction <span class="mono">${esc(upd.txid.slice(0,20))}…</span> is on the chain.
Resolvers pick it up within a block.</p>
<div class="note">Open it with the Silent Mode resolver, or preview it at
<a href="https://navigate.st/bns/${encodeURIComponent(r.displayName)}" target="_blank" rel="noopener">navigate.st</a>.</div>
<div class="row end"><button class="btn acid" id="fin">Finish</button></div>`);
$("fin").onclick = close;
} catch (e) {
err(e);
const row = document.createElement("div"); row.className = "row end";
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
sheet.appendChild(row); $("back").onclick = stepDone;
}
};
}
</script>
<script defer src="./js/site-footer.js?v=20260907rel"></script>
<script defer src="./js/profile-menu.js?v=20260907wallet"></script>
</body>
</html>

View file

@ -87,7 +87,7 @@
<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="../#search-input">🪪 Register</a>
<a href="../tld.html">🌐 TLD</a>
<a href="./" class="here"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="../portal.html" class="portal">🔑 Sign in</a>
@ -141,7 +141,7 @@
</ol>
<div class="note"><b>What Theseus is NOT.</b> Not a wallet — for that, use Deviant, Cashonize,
Paytaca, or the built-in browser wallet at
<a href="../register.html">sirius.x/register.html</a>.
<a href="../#search-input">the name search</a>.
Not a hosting service — for that, register a name and point its records at any host (Sia, VPS,
your own server). Theseus just makes those names openable in a normal-feeling browser.</div>
</section>

View file

@ -107,7 +107,7 @@
<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="./#search-input">🪪 Register</a>
<a href="./tld.html" class="here">🌐 TLD</a>
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
<a href="./portal.html" class="portal">🔑 Sign in</a>
@ -531,7 +531,7 @@ $("mint-submit").addEventListener("click", async () => {
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>.`;
<a href="./#search-input">the name search</a>.`;
$("mint-submit").textContent = "Registered ✓";
// Refresh the live TLD table so the new one shows up.
setTimeout(() => loadTldList().then((l) => { registeredTlds = l; refreshSearch(); }), 3500);