feat(sirius-x): pencil Register icon + TLD mint via shared modal
Two UX polishes: 1. Register-tab icon flipped from 🪪 (identification-card emoji, renders as a hollow box in system-ui fonts without extended emoji) to ✏️ (pencil), which reads as 'edit/create' and ships in every emoji font worth targeting. Changed across the nav on all 8 pages plus the footer injector's Product column. Kept the Register nav item — six items total in the nav, and from any subpage it's one click to the search on landing. 2. tld.html no longer duplicates the wallet-choice + mint flow inline. The Register button on a search result now hands the label off to the shared register-flow.js modal — same modal that name registration uses — via a new window.siriusRegisterTld(label, {serviceFeeSats}) entry point. register-flow.js grew: - startTldFlow / window.siriusRegisterTld: sets state.tld and walks the modal through wallet-choice → Fund → stepConfirmTld → stepRegisterTld → stepDoneTld - stepConfirmTld: shows label + fee + owner, no per-name quote - stepRegisterTld: dispatches to registerTldWithBuiltInWallet or registerTldWithExternalWallet based on state.wallet/state.session - stepDoneTld: 'X is yours' with txid + certificate id + operator address, no record editor (a TLD certificate has no records to set immediately) - stepWallet / stepExternalConfirm branch on state.tld so the modal heading and the WC-confirm path route correctly tld.html trimmed: removed the entire signin-section + mint-section plus their inline handlers (~250 lines gone). Now just search + registered-TLDs list + a delegator to the shared modal. If a user is already signed in via the wallet dropdown, the modal recognises the saved wallet ('Unlock my browser wallet' button appears) instead of asking them to sign in again on this page.
This commit is contained in:
parent
5e0c971c23
commit
bce4aa3529
9 changed files with 224 additions and 451 deletions
|
|
@ -82,7 +82,7 @@
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="../#try">Try it</a>
|
<a href="../#try">Try it</a>
|
||||||
<a href="../#search-input">🪪 Register</a>
|
<a href="../#search-input">✏️ Register</a>
|
||||||
<a href="../tld.html">🌐 TLD</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="../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>
|
<a href="./" class="here">🛠 Admin</a>
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="../#try">Try it</a>
|
<a href="../#try">Try it</a>
|
||||||
<a href="../#search-input">🪪 Register</a>
|
<a href="../#search-input">✏️ Register</a>
|
||||||
<a href="../tld.html">🌐 TLD</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="../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>
|
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="../#try">Try it</a>
|
<a href="../#try">Try it</a>
|
||||||
<a href="../#search-input">🪪 Register</a>
|
<a href="../#search-input">✏️ Register</a>
|
||||||
<a href="../tld.html">🌐 TLD</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="../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>
|
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="#try">Try it</a>
|
<a href="#try">Try it</a>
|
||||||
<a href="./#search-input">🪪 Register</a>
|
<a href="./#search-input">✏️ Register</a>
|
||||||
<a href="./tld.html">🌐 TLD</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="./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>
|
<a href="./portal.html" class="portal">🔑 Sign in</a>
|
||||||
|
|
@ -421,7 +421,7 @@ process.exit(0);"</pre>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="./js/register-flow.js?v=20260907signin"></script>
|
<script type="module" src="./js/register-flow.js?v=20260908tld"></script>
|
||||||
|
|
||||||
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
||||||
<script defer src="./js/profile-menu.js?v=20260907inline"></script>
|
<script defer src="./js/profile-menu.js?v=20260907inline"></script>
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,19 @@ async function client() {
|
||||||
|
|
||||||
function startFlow(name) {
|
function startFlow(name) {
|
||||||
state.name = name;
|
state.name = name;
|
||||||
|
state.tld = null;
|
||||||
|
state.signInOnly = false;
|
||||||
|
open();
|
||||||
|
stepWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a TLD mint. Same wallet-choice + fund flow as a name register, but
|
||||||
|
// the mint step calls registerTldWith(Built|External)Wallet instead of the
|
||||||
|
// name variant, and the "done" screen skips the record editor since a TLD
|
||||||
|
// certificate goes to the operator's token address as-is.
|
||||||
|
function startTldFlow(label, opts = {}) {
|
||||||
|
state.name = `.${label}`; // used for headings only
|
||||||
|
state.tld = { label, serviceFeeSats: BigInt(opts.serviceFeeSats ?? 0n) };
|
||||||
state.signInOnly = false;
|
state.signInOnly = false;
|
||||||
open();
|
open();
|
||||||
stepWallet();
|
stepWallet();
|
||||||
|
|
@ -190,14 +203,16 @@ function startSignIn(mode) {
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
window.siriusRegisterName = startFlow;
|
window.siriusRegisterName = startFlow;
|
||||||
|
window.siriusRegisterTld = startTldFlow;
|
||||||
window.siriusSignInWallet = startSignIn;
|
window.siriusSignInWallet = startSignIn;
|
||||||
|
|
||||||
// ---------- step 1: wallet choice ----------
|
// ---------- step 1: wallet choice ----------
|
||||||
function stepWallet() {
|
function stepWallet() {
|
||||||
const saved = BNS.BuiltInWallet.exists();
|
const saved = BNS.BuiltInWallet.exists();
|
||||||
|
const kind = state.tld ? "TLD" : "name";
|
||||||
render(`
|
render(`
|
||||||
<h3>Register ${esc(state.name)}</h3>
|
<h3>Register ${esc(state.name)}</h3>
|
||||||
<p class="sub">First, the wallet that will <b>own</b> the name. The certificate is minted straight
|
<p class="sub">First, the wallet that will <b>own</b> the ${esc(kind)}. The certificate is minted straight
|
||||||
to it — we never hold it.</p>
|
to it — we never hold it.</p>
|
||||||
|
|
||||||
${saved ? `<button class="wopt" id="w-unlock"><span class="ic">🔓</span><span>
|
${saved ? `<button class="wopt" id="w-unlock"><span class="ic">🔓</span><span>
|
||||||
|
|
@ -303,6 +318,10 @@ async function stepExternalConfirm(session, label) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLD external path: keep the session, hand off to the TLD confirm/mint
|
||||||
|
// steps which know to call registerTldWithExternalWallet.
|
||||||
|
if (state.tld) { state.session = session; return stepConfirmTld(); }
|
||||||
|
|
||||||
const quote = await BNS.quoteRegistration(c, { name: state.name, ownerAddress: owner, records: {} });
|
const quote = await BNS.quoteRegistration(c, { name: state.name, ownerAddress: owner, records: {} });
|
||||||
const rows = BNS.priceSummary(quote.costs).map((l) => `
|
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>
|
<tr class="${l.total ? "total" : ""}"><td>${esc(l.label)}${l.note ? `<small>${esc(l.note)}</small>` : ""}</td>
|
||||||
|
|
@ -479,6 +498,10 @@ async function stepConfirm() {
|
||||||
render(`<h3>Preparing…</h3><p class="sub">Building your registration transaction.</p>`);
|
render(`<h3>Preparing…</h3><p class="sub">Building your registration transaction.</p>`);
|
||||||
try {
|
try {
|
||||||
const c = await client();
|
const c = await client();
|
||||||
|
// TLD flow skips the name-availability check and the per-name quote —
|
||||||
|
// registerTldWith*Wallet builds the tx internally and checks the on-chain
|
||||||
|
// TLD registry itself. Just show a simple confirm screen with the fee.
|
||||||
|
if (state.tld) return stepConfirmTld();
|
||||||
const avail = await BNS.checkAvailability(c, state.name);
|
const avail = await BNS.checkAvailability(c, state.name);
|
||||||
if (!avail.available) throw new Error(`"${avail.name}" was just registered by someone else`);
|
if (!avail.available) throw new Error(`"${avail.name}" was just registered by someone else`);
|
||||||
const quote = await BNS.quoteRegistration(c, {
|
const quote = await BNS.quoteRegistration(c, {
|
||||||
|
|
@ -509,6 +532,73 @@ async function stepConfirm() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- TLD confirm ----------
|
||||||
|
async function stepConfirmTld() {
|
||||||
|
const feeSats = state.tld.serviceFeeSats;
|
||||||
|
const owner = state.wallet ? state.wallet.address : (state.session ? (await state.session.getAddresses())[0] : "your address");
|
||||||
|
render(`
|
||||||
|
<h3>Confirm <b>${esc(state.name)}</b></h3>
|
||||||
|
<p class="sub">One transaction. It mints the TLD certificate to <b>your</b> token address; you become
|
||||||
|
the registry operator for every second-level name under it.</p>
|
||||||
|
<table class="price">
|
||||||
|
<tr><td>Service fee${feeSats === 0n ? " <small>(none on chipnet)</small>" : ""}</td>
|
||||||
|
<td>${sats(feeSats)} sat</td></tr>
|
||||||
|
<tr><td>Beacon dust + chain fee <small>(covered by wallet)</small></td>
|
||||||
|
<td>~1,300 sat</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="note" style="margin-top:16px">Minted to <span class="mono">${esc(owner)}</span>.
|
||||||
|
That wallet becomes the operator for this TLD.</div>
|
||||||
|
<div class="row end"><button class="btn ghost" id="back">Back</button>
|
||||||
|
<button class="btn acid" id="go">Register — sign & broadcast</button></div>
|
||||||
|
`);
|
||||||
|
$("back").onclick = stepFund;
|
||||||
|
$("go").onclick = stepRegisterTld;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stepRegisterTld() {
|
||||||
|
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 serviceFee = { address: BNS.REGISTRAR?.serviceFee?.address ?? null, sats: state.tld.serviceFeeSats };
|
||||||
|
try {
|
||||||
|
const res = state.session
|
||||||
|
? await BNS.registerTldWithExternalWallet(await client(), {
|
||||||
|
session: state.session, tld: state.tld.label, records: {},
|
||||||
|
serviceFee, tldListUrl: "https://navigate.st/api/tlds", onProgress: say,
|
||||||
|
})
|
||||||
|
: await BNS.registerTldWithBuiltInWallet(await client(), {
|
||||||
|
wallet: state.wallet, tld: state.tld.label, records: {},
|
||||||
|
serviceFee, tldListUrl: "https://navigate.st/api/tlds", onProgress: say,
|
||||||
|
});
|
||||||
|
state.result = res;
|
||||||
|
stepDoneTld();
|
||||||
|
} 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 = stepConfirmTld;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stepDoneTld() {
|
||||||
|
const r = state.result;
|
||||||
|
render(`
|
||||||
|
<h3><span class="ok">✓</span> <b>.${esc(r.tld)}</b> is yours</h3>
|
||||||
|
<p class="sub">The TLD certificate is on the chain, held by your key. Second-level names under
|
||||||
|
<b>.${esc(r.tld)}</b> now live in your registry.</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>Operator</td><td>${esc(r.ownerAddress.slice(0,22))}…</td></tr>
|
||||||
|
</table>
|
||||||
|
<div class="row end"><button class="btn acid" id="fin">Done</button></div>
|
||||||
|
`);
|
||||||
|
$("fin").onclick = close;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- register ----------
|
// ---------- register ----------
|
||||||
async function stepRegister() {
|
async function stepRegister() {
|
||||||
render(`<h3>Registering ${esc(state.name)}</h3>
|
render(`<h3>Registering ${esc(state.name)}</h3>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h5>Product</h5>
|
<h5>Product</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="${s("#search-input")}">🪪 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("tld.html")}">🌐 Register a TLD</a></li>
|
||||||
<li><a href="${s("portal.html")}">🔑 My names portal</a></li>
|
<li><a href="${s("portal.html")}">🔑 My names portal</a></li>
|
||||||
<li><a href="${s("theseus/")}">🧭 Theseus browser</a></li>
|
<li><a href="${s("theseus/")}">🧭 Theseus browser</a></li>
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="./#try">Try it</a>
|
<a href="./#try">Try it</a>
|
||||||
<a href="./#search-input">🪪 Register</a>
|
<a href="./#search-input">✏️ Register</a>
|
||||||
<a href="./tld.html">🌐 TLD</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="./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>
|
<a href="./portal.html" class="portal here">🔑 Sign in</a>
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="../">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="../#try">Try it</a>
|
<a href="../#try">Try it</a>
|
||||||
<a href="../#search-input">🪪 Register</a>
|
<a href="../#search-input">✏️ Register</a>
|
||||||
<a href="../tld.html">🌐 TLD</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="./" 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>
|
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||||
|
|
|
||||||
567
tld.html
567
tld.html
|
|
@ -40,25 +40,13 @@
|
||||||
h1 .g{color:var(--acid)}
|
h1 .g{color:var(--acid)}
|
||||||
.tag{color:var(--mut);font-size:1rem;max-width:600px;margin:0 auto}
|
.tag{color:var(--mut);font-size:1rem;max-width:600px;margin:0 auto}
|
||||||
h2{font-size:1.25rem;margin:0 0 .4rem}
|
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{padding:2rem 0;border-top:1px solid var(--line);margin-top:2rem}
|
||||||
section:first-of-type{border-top:none;margin-top:1rem}
|
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{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.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
|
||||||
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
|
.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:hover{filter:brightness(1.12)}
|
||||||
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
|
.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}
|
.muted{color:var(--mut);font-size:13px}
|
||||||
.dim{color:var(--dim);font-size:12.5px}
|
.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}
|
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}
|
||||||
|
|
@ -70,44 +58,34 @@
|
||||||
.status.ok{border-left:3px solid var(--ok)}
|
.status.ok{border-left:3px solid var(--ok)}
|
||||||
.status.err{border-left:3px solid var(--taken);color:var(--taken)}
|
.status.err{border-left:3px solid var(--taken);color:var(--taken)}
|
||||||
.status.warn{border-left:3px solid var(--warn);color:var(--warn)}
|
.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 */
|
/* TLD search cell */
|
||||||
.search{max-width:600px;margin:1.5rem auto 0}
|
.search{max-width:600px;margin:1.5rem auto 0}
|
||||||
.searchrow{display:flex;gap:8px}
|
.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{flex:1;padding:14px 18px;font-size:17px;border-radius:12px;border:1px solid #ffffff22;background:#141a24;color:var(--ink);font-family:inherit;outline:none}
|
||||||
.searchrow input:focus{border-color:#4b7bec;outline:none}
|
.searchrow input:focus{border-color:#4b7bec}
|
||||||
.searchhint{font-size:12.5px;color:var(--mut);margin-top:8px;text-align:center}
|
.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;
|
.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}
|
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{font-family:ui-monospace,monospace;font-size:18px;color:var(--ink)}
|
||||||
.result-card .lbl .dot{color:var(--acid)}
|
.result-card .lbl .dot{color:var(--acid)}
|
||||||
/* TLD registry table */
|
/* Registered-TLDs table */
|
||||||
table.tlds{width:100%;border-collapse:collapse;margin-top:1rem;font-size:13px}
|
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,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 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 .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}
|
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}
|
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>
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
||||||
<a href="./#try">Try it</a>
|
<a href="./#try">Try it</a>
|
||||||
<a href="./#search-input">🪪 Register</a>
|
<a href="./#search-input">✏️ Register</a>
|
||||||
<a href="./tld.html" class="here">🌐 TLD</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="./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>
|
<a href="./portal.html" class="portal">🔑 Sign in</a>
|
||||||
|
|
@ -125,89 +103,17 @@
|
||||||
|
|
||||||
<section id="search-section">
|
<section id="search-section">
|
||||||
<h2>Search a TLD</h2>
|
<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
|
<p class="muted">Type a label (a–z, 0–9, hyphen; up to 16 chars). Available TLDs get a Register
|
||||||
registry live and, if the label is free, show a register button.</p>
|
button that opens the shared wallet-choice modal — the same flow name registration uses.</p>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="searchrow">
|
<div class="searchrow">
|
||||||
<input id="q" placeholder="your-tld" spellcheck="false" autocomplete="off" autofocus maxlength="16">
|
<input id="q" placeholder="your-tld" spellcheck="false" autocomplete="off" autofocus maxlength="16">
|
||||||
</div>
|
</div>
|
||||||
<div class="searchhint">Available TLDs appear with a register button. Taken labels show which category holds them.</div>
|
<div class="searchhint">Register minting to your token address; you become the registry operator.</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="result"></div>
|
<div id="result"></div>
|
||||||
</section>
|
</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">
|
<section id="listing">
|
||||||
<h2>Registered TLDs</h2>
|
<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>
|
<p class="muted">Fetched from the on-chain TLD registry via the public gateway. Refresh the page for the latest.</p>
|
||||||
|
|
@ -222,351 +128,128 @@
|
||||||
|
|
||||||
<footer id="site-footer"></footer>
|
<footer id="site-footer"></footer>
|
||||||
|
|
||||||
<script type="importmap">
|
<script>
|
||||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
// Simple oracle stub: static chipnet rate for testing. Same number the
|
||||||
</script>
|
// portal uses. Mainnet swaps for a real price feed.
|
||||||
|
const CHIPNET_SATS_PER_USD = 250_000;
|
||||||
|
const DEFAULT_USD = 5;
|
||||||
|
|
||||||
<script type="module">
|
const $ = (id) => document.getElementById(id);
|
||||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260907tldext";
|
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
||||||
|
const validLabel = (s) => /^[a-z0-9-]{1,16}$/.test(s);
|
||||||
|
const normLabel = (s) => String(s).toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 16);
|
||||||
|
|
||||||
const $ = (id) => document.getElementById(id);
|
// ---------- registered-TLD list ----------
|
||||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
let registeredTlds = null;
|
||||||
|
async function loadTldList() {
|
||||||
// ---------- oracle stub ----------
|
const status = $("tlds-status"), tbody = $("tlds-table").querySelector("tbody");
|
||||||
// Same chipnet-only static rate as the portal. Mainnet swaps this for a real
|
try {
|
||||||
// price feed; the sats output is what actually gets locked into the mint tx.
|
const r = await fetch("https://navigate.st/api/tlds");
|
||||||
const CHIPNET_SATS_PER_USD = 250_000; // ~$400/BCH test rate
|
if (!r.ok) throw new Error("api " + r.status);
|
||||||
const usdToSats = (u) => {
|
const j = await r.json();
|
||||||
const n = Number(String(u).replace(/[^\d.]/g, "")) || 0;
|
const tlds = (j.tlds || []).map((t) => (typeof t === "string" ? { tld: t } : t));
|
||||||
return Math.max(0, Math.round(n * CHIPNET_SATS_PER_USD));
|
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>`
|
||||||
// ---------- state ----------
|
).join("");
|
||||||
let wallet = null; // built-in wallet or WC wrapper
|
$("tlds-table").style.display = "";
|
||||||
let candidateLabel = null; // TLD label the user chose to register
|
status.className = "status ok";
|
||||||
|
status.textContent = `${tlds.length} TLDs registered on chipnet.`;
|
||||||
// ---------- TLD list (live) ----------
|
registeredTlds = tlds.map((t) => t.tld);
|
||||||
async function loadTldList() {
|
refreshSearch();
|
||||||
const status = $("tlds-status"), tbody = $("tlds-table").querySelector("tbody");
|
} catch (e) {
|
||||||
try {
|
status.className = "status err";
|
||||||
const r = await fetch("https://navigate.st/api/tlds");
|
status.textContent = "Could not load TLD list: " + (e.message || e);
|
||||||
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="./#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);
|
|
||||||
} 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 {}
|
|
||||||
}
|
}
|
||||||
});
|
loadTldList();
|
||||||
|
|
||||||
// ---------- ?q= deep-link (from landing search too, if someone comes with a label) ----------
|
// ---------- search result renderer ----------
|
||||||
(() => {
|
function renderResult(label, state) {
|
||||||
const q = normLabel(new URLSearchParams(location.search).get("q") || "");
|
const res = $("result");
|
||||||
if (!q) return;
|
if (!label) { res.innerHTML = ""; return; }
|
||||||
$("q").value = q;
|
if (state === "checking") {
|
||||||
// wait for registeredTlds to load
|
res.innerHTML = `<div class="result-card">
|
||||||
const wait = setInterval(() => {
|
<div class="lbl"><span class="dot">.</span>${esc(label)}</div>
|
||||||
if (registeredTlds === null) return;
|
<span class="badge b-check">checking…</span><span></span></div>`;
|
||||||
clearInterval(wait);
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- search wiring ----------
|
||||||
|
let currentLabel = "";
|
||||||
|
function refreshSearch() {
|
||||||
|
const raw = normLabel($("q").value);
|
||||||
|
currentLabel = raw;
|
||||||
|
if (!raw) { renderResult("", null); return; }
|
||||||
|
if (!validLabel(raw)) { renderResult(raw, "invalid"); return; }
|
||||||
|
if (registeredTlds !== null) {
|
||||||
|
renderResult(raw, registeredTlds.includes(raw) ? "taken" : "ok");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderResult(raw, "checking");
|
||||||
|
}
|
||||||
|
$("q").addEventListener("input", () => {
|
||||||
|
$("q").value = normLabel($("q").value);
|
||||||
refreshSearch();
|
refreshSearch();
|
||||||
setTimeout(() => $("q").scrollIntoView({ behavior: "smooth", block: "start" }), 100);
|
});
|
||||||
}, 150);
|
$("q").addEventListener("keydown", (e) => {
|
||||||
})();
|
if (e.key === "Enter" && currentLabel && registeredTlds && !registeredTlds.includes(currentLabel)) {
|
||||||
|
beginRegister(currentLabel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------- mint delegation ----------
|
||||||
|
// Register button hands the label off to the shared modal (register-flow.js
|
||||||
|
// — window.siriusRegisterTld). That modal owns the wallet-choice, fund,
|
||||||
|
// confirm, and mint steps — same UX as name registration.
|
||||||
|
function beginRegister(label) {
|
||||||
|
const call = () => window.siriusRegisterTld(label, {
|
||||||
|
serviceFeeSats: BigInt(DEFAULT_USD * CHIPNET_SATS_PER_USD),
|
||||||
|
});
|
||||||
|
if (typeof window.siriusRegisterTld === "function") { call(); return; }
|
||||||
|
// Module still loading — retry until it lands.
|
||||||
|
const btn = $("go-register");
|
||||||
|
const orig = btn?.textContent;
|
||||||
|
if (btn) { btn.disabled = true; btn.textContent = "loading…"; }
|
||||||
|
const t = setInterval(() => {
|
||||||
|
if (typeof window.siriusRegisterTld === "function") {
|
||||||
|
clearInterval(t);
|
||||||
|
if (btn) { btn.disabled = false; btn.textContent = orig; }
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- ?q= deep-link (from landing links etc.) ----------
|
||||||
|
(() => {
|
||||||
|
const q = normLabel(new URLSearchParams(location.search).get("q") || "");
|
||||||
|
if (!q) return;
|
||||||
|
$("q").value = q;
|
||||||
|
const wait = setInterval(() => {
|
||||||
|
if (registeredTlds === null) return;
|
||||||
|
clearInterval(wait);
|
||||||
|
refreshSearch();
|
||||||
|
setTimeout(() => $("q").scrollIntoView({ behavior: "smooth", block: "start" }), 100);
|
||||||
|
}, 150);
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script type="module" src="./js/register-flow.js?v=20260908tld"></script>
|
||||||
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
||||||
<script defer src="./js/profile-menu.js?v=20260907inline"></script>
|
<script defer src="./js/profile-menu.js?v=20260907inline"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue