2026-09-07 22:36:02 +02:00
|
|
|
|
// 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) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" }[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 ----------
|
2026-09-07 23:52:12 +02:00
|
|
|
|
// signInOnly: when true, the flow is used for wallet onboarding from the nav
|
|
|
|
|
|
// dropdown — no name to mint, so instead of walking through Fund/Confirm/
|
|
|
|
|
|
// Register we stop at 'wallet is ready', write siriusProfile, and close.
|
|
|
|
|
|
const state = { name: null, signInOnly: false, wallet: null, session: null, client: null, quote: null, result: null };
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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; }
|
2026-09-07 23:52:12 +02:00
|
|
|
|
// Keep the WC session alive when signing in — later record edits or TLD
|
|
|
|
|
|
// mints will need it; only tear it down for the mint flow.
|
|
|
|
|
|
if (state.session && !state.signInOnly) { try { state.session.disconnect(); } catch {} state.session = null; }
|
|
|
|
|
|
state.signInOnly = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Persist wallet identity so profile-menu.js and any other tab see the
|
|
|
|
|
|
// signed-in state immediately (same as portal.html's writeProfile).
|
|
|
|
|
|
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"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// A wallet was just loaded/created/connected. If we are in sign-in-only
|
|
|
|
|
|
// mode, mark siriusProfile and close the modal here; otherwise return
|
|
|
|
|
|
// false so the caller keeps going to Fund → Confirm → Register.
|
|
|
|
|
|
function finishSignIn(w) {
|
|
|
|
|
|
writeProfile({ address: w.address, tokenAddress: w.tokenAddress, source: w.source ?? "seed" });
|
|
|
|
|
|
if (state.signInOnly) {
|
|
|
|
|
|
render(`<h3><span class="ok">✓</span> Signed in</h3>
|
|
|
|
|
|
<p class="sub">You are signed in as <span class="mono">${esc(w.address)}</span>. Names you own
|
|
|
|
|
|
will appear in the wallet dropdown; the same wallet signs record edits and new registrations.</p>
|
|
|
|
|
|
<div class="row end"><button class="btn acid" id="fin">Done</button></div>`);
|
|
|
|
|
|
$("fin").onclick = close;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
$("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;
|
2026-09-08 00:10:11 +02:00
|
|
|
|
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) };
|
2026-09-07 23:52:12 +02:00
|
|
|
|
state.signInOnly = false;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
open();
|
|
|
|
|
|
stepWallet();
|
|
|
|
|
|
}
|
2026-09-07 23:52:12 +02:00
|
|
|
|
|
|
|
|
|
|
// Sign-in-only entry point for the nav wallet dropdown. mode = 'new' opens
|
|
|
|
|
|
// the "create wallet" step directly, 'import' opens "import phrase", 'wc'
|
|
|
|
|
|
// starts a WizardConnect session. On success the flow writes siriusProfile
|
|
|
|
|
|
// and closes — no name context, no Fund/Confirm.
|
|
|
|
|
|
function startSignIn(mode) {
|
|
|
|
|
|
state.name = null;
|
|
|
|
|
|
state.signInOnly = true;
|
|
|
|
|
|
open();
|
|
|
|
|
|
if (mode === "new") stepCreate();
|
|
|
|
|
|
else if (mode === "import") stepImport();
|
|
|
|
|
|
else if (mode === "wc") stepExternal();
|
|
|
|
|
|
else stepWallet();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-07 22:36:02 +02:00
|
|
|
|
// Public API
|
|
|
|
|
|
window.siriusRegisterName = startFlow;
|
2026-09-08 00:10:11 +02:00
|
|
|
|
window.siriusRegisterTld = startTldFlow;
|
2026-09-07 23:52:12 +02:00
|
|
|
|
window.siriusSignInWallet = startSignIn;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
|
|
|
|
|
|
// ---------- step 1: wallet choice ----------
|
|
|
|
|
|
function stepWallet() {
|
|
|
|
|
|
const saved = BNS.BuiltInWallet.exists();
|
2026-09-08 00:10:11 +02:00
|
|
|
|
const kind = state.tld ? "TLD" : "name";
|
2026-09-07 22:36:02 +02:00
|
|
|
|
render(`
|
|
|
|
|
|
<h3>Register ${esc(state.name)}</h3>
|
2026-09-08 00:10:11 +02:00
|
|
|
|
<p class="sub">First, the wallet that will <b>own</b> the ${esc(kind)}. The certificate is minted straight
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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>`);
|
2026-09-07 23:52:12 +02:00
|
|
|
|
$("back").onclick = state.signInOnly ? close : stepWallet;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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);
|
2026-09-07 23:52:12 +02:00
|
|
|
|
$("cancel").onclick = async () => { try { await session.disconnect(); } catch {} (state.signInOnly ? close : stepWallet)(); };
|
2026-09-07 22:36:02 +02:00
|
|
|
|
|
|
|
|
|
|
try { await session.ready; } catch (e) { err(e); return; }
|
2026-09-07 23:52:12 +02:00
|
|
|
|
|
|
|
|
|
|
// Sign-in-only path: get the address and persist siriusProfile immediately,
|
|
|
|
|
|
// skip the whole "quote registration" branch since there is no name.
|
|
|
|
|
|
if (state.signInOnly) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const addresses = await session.getAddresses();
|
|
|
|
|
|
if (!addresses?.length) throw new Error("wallet returned no addresses");
|
|
|
|
|
|
state.session = session; // keep alive for record edits etc.
|
|
|
|
|
|
finishSignIn({
|
|
|
|
|
|
source: "wc",
|
|
|
|
|
|
address: addresses[0],
|
|
|
|
|
|
tokenAddress: BNS.toTokenAddress(addresses[0]),
|
|
|
|
|
|
watchedAddresses: addresses,
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (e) { err(e); }
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-08 00:10:11 +02:00
|
|
|
|
// 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(); }
|
|
|
|
|
|
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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() {
|
2026-09-07 23:52:12 +02:00
|
|
|
|
// The Register button flow lands here via stepWallet, so "Back" returns to
|
|
|
|
|
|
// the wallet-choice step. The sign-in dropdown calls stepCreate directly
|
|
|
|
|
|
// with no chosen name, so "Back" should just close the modal in that case.
|
|
|
|
|
|
const goBack = state.signInOnly ? close : stepWallet;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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>
|
|
|
|
|
|
`);
|
2026-09-07 23:52:12 +02:00
|
|
|
|
$("back").onclick = goBack;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
$("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; };
|
2026-09-07 23:52:12 +02:00
|
|
|
|
$("next").onclick = () => { if (!finishSignIn(state.wallet)) stepFund(); };
|
2026-09-07 22:36:02 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- built-in: import / unlock ----------
|
|
|
|
|
|
function stepImport() {
|
2026-09-07 23:52:12 +02:00
|
|
|
|
const goBack = state.signInOnly ? close : stepWallet;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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>
|
|
|
|
|
|
`);
|
2026-09-07 23:52:12 +02:00
|
|
|
|
$("back").onclick = goBack;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
$("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;
|
2026-09-07 23:52:12 +02:00
|
|
|
|
if (!finishSignIn(wallet)) stepFund();
|
2026-09-07 22:36:02 +02:00
|
|
|
|
} catch (e) { err(e); }
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function stepUnlock() {
|
2026-09-07 23:52:12 +02:00
|
|
|
|
const goBack = state.signInOnly ? close : stepWallet;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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>
|
|
|
|
|
|
`);
|
2026-09-07 23:52:12 +02:00
|
|
|
|
$("back").onclick = goBack;
|
2026-09-07 22:36:02 +02:00
|
|
|
|
$("pw").onkeydown = (e) => { if (e.key === "Enter") $("next").click(); };
|
|
|
|
|
|
$("next").onclick = async () => {
|
2026-09-07 23:52:12 +02:00
|
|
|
|
try {
|
|
|
|
|
|
state.wallet = await BNS.BuiltInWallet.load($("pw").value);
|
|
|
|
|
|
if (!finishSignIn(state.wallet)) stepFund();
|
|
|
|
|
|
} catch (e) { err(e); }
|
2026-09-07 22:36:02 +02:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- 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();
|
2026-09-08 00:10:11 +02:00
|
|
|
|
// 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();
|
2026-09-07 22:36:02 +02:00
|
|
|
|
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 & 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-08 00:10:11 +02:00
|
|
|
|
// ---------- 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-07 22:36:02 +02:00
|
|
|
|
// ---------- 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="<h1>hello</h1>"></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;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|