2026-08-29 21:57:33 +02:00
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< title > Portal — your names on sirius.x< / title >
< meta name = "description" content = "Sign in with your Bitcoin Cash seed phrase to see every BCNR name your wallet holds and manage records. Your keys never leave the browser." >
2026-08-31 05:40:47 +02:00
< link rel = "icon" href = "./assets/favicon.svg" type = "image/svg+xml" >
< link rel = "mask-icon" href = "./assets/favicon.svg" color = "#d6ff3d" >
2026-08-31 04:30:53 +02:00
< meta property = "og:type" content = "website" >
< meta property = "og:site_name" content = "sirius.x" >
< meta property = "og:title" content = "sirius.x portal — your BCNR names" >
< meta property = "og:description" content = "Sign in with your seed phrase to see every name your wallet holds and manage records. Keys never leave the browser." >
< meta property = "og:url" content = "https://silentmode.st/sirius-x/portal.html" >
sirius.x: /brand/ kit — logo, mono, avatar, banner, favicon, palette
brand/ ships every reusable mark plus a browseable kit page:
* logo.svg — full colour wordmark (star + sirius.x, .x in acid green)
* logo-mono.svg — currentColor version for print / one-colour merch
* favicon.svg — 64×64 rounded square, scales cleanly to 16×16
* avatar.svg — 512×512 square, hard corners (platforms round-mask)
* banner.svg — 1200×630 OG / Twitter Card canvas, crop-tolerant layout
* index.html — brand-kit page: asset previews + download links, full
colour palette with names/hex, typography sample, do/don't guidance,
MIT-attribution notice.
Nav: added 🎨 Brand link to every sirius.x page (landing, portal, docs,
theseus). og:image on every page updated to point at /brand/banner.svg
instead of the wordmark logo — social crawlers get a proper 1200×630
banner with title, tagline, and mark instead of a thin logo strip.
Kept /assets/favicon.svg and /assets/logo.svg in place so existing
external references (Nostr metadata, third-party embeds) don't break.
2026-08-31 05:44:25 +02:00
< meta property = "og:image" content = "https://silentmode.st/sirius-x/brand/banner.svg" >
2026-08-31 04:30:53 +02:00
< meta name = "twitter:card" content = "summary" >
2026-08-29 21:57:33 +02:00
< style >
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:820px;margin:0 auto;padding:0 1.2rem}
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px}
.topnav .brand .g{color:var(--acid)}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
2026-08-29 22:22:37 +02:00
.topnav a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
.topnav a.portal:hover{background:rgba(214,255,61,.18)}
2026-08-29 21:57:33 +02:00
header.hero{text-align:center;padding:3rem 1.2rem 1rem}
.mark{font-size:44px;line-height:1}
h1{font-size:clamp(1.6rem,4.5vw,2.2rem);margin:.3rem 0 .3rem}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1rem;max-width:580px;margin:0 auto}
h2{font-size:1.25rem;margin:0 0 .4rem}
section{padding:2rem 0;border-top:1px solid var(--line);margin-top:2rem}
section:first-of-type{border-top:none;margin-top:1rem}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin-top:1rem}
label{display:block;font-size:13px;color:var(--mut);margin:14px 0 5px}
textarea,input[type=password],input[type=text]{
width:100%;padding:11px 13px;border-radius:10px;border:1px solid #ffffff22;background:#0e131b;
color:var(--ink);font-size:14px;font-family:inherit;outline:none
}
textarea{min-height:74px;resize:vertical;font-family:ui-monospace,monospace;font-size:13px}
textarea:focus,input:focus{border-color:#4b7bec}
.btn{display:inline-block;text-decoration:none;padding:10px 18px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
.btn.small{padding:6px 12px;font-size:12.5px}
.btn:hover{filter:brightness(1.12)}
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
.row{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px;align-items:center}
.muted{color:var(--mut);font-size:13px}
.dim{color:var(--dim);font-size:12.5px}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px;word-break:break-all}
.badge{display:inline-block;font-size:11px;padding:2px 10px;border-radius:999px;font-weight:600}
.b-x{background:rgba(214,255,61,.14);color:var(--acid)}
.b-warn{background:rgba(255,199,95,.14);color:var(--warn)}
.err{color:var(--taken);font-size:13.5px;margin-top:10px}
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:12px}
.status.ok{border-left:3px solid var(--ok)}
.status.err{border-left:3px solid var(--taken);color:var(--taken)}
.hidden{display:none}
.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}
.authbox .card > .cta{margin-top:14px}
2026-08-31 21:44:20 +02:00
.name-row{display:grid;grid-template-columns:1fr auto auto auto;gap:10px;align-items:center;
2026-08-29 21:57:33 +02:00
padding:14px 16px;background:var(--panel2);border:1px solid var(--line);border-radius:12px;margin-top:8px}
.name-row .n{font-family:ui-monospace,monospace;font-size:15px}
.name-row .rec{color:var(--mut);font-family:ui-monospace,monospace;font-size:12px;grid-column:1/-1;margin-top:6px;word-break:break-all}
.name-row .rec .k{color:var(--dim)}
2026-08-31 21:44:20 +02:00
/* record editor modal */
.modal{position:fixed;inset:0;background:rgba(6,9,14,.82);backdrop-filter:blur(3px);
display:none;align-items:flex-start;justify-content:center;padding:3vh 1rem;overflow-y:auto;z-index:50}
.modal.open{display:flex}
.sheet{background:var(--panel);border:1px solid var(--line);border-radius:16px;max-width:620px;width:100%;
padding:24px 26px;position:relative}
.sheet h3{margin:0 0 .3rem;font-size:1.15rem;font-family:ui-monospace,monospace}
.sheet .sub{color:var(--mut);font-size:13px;margin:0 0 1.2rem}
.sheet .x{position:absolute;top:14px;right:16px;background:none;border:none;color:var(--dim);font-size:22px;cursor:pointer;line-height:1}
.sheet label{display:block;font-size:12px;color:var(--mut);margin:12px 0 4px;text-transform:uppercase;letter-spacing:.4px}
.sheet .field-hint{color:var(--dim);font-size:11.5px;margin-left:8px;text-transform:none;letter-spacing:0}
.sheet input[type=text],.sheet textarea{
width:100%;padding:9px 12px;border-radius:8px;border:1px solid #ffffff22;background:#0e131b;
color:var(--ink);font-size:13px;font-family:ui-monospace,monospace;outline:none
}
.sheet textarea{min-height:60px;resize:vertical}
.sheet input:focus,.sheet textarea:focus{border-color:#4b7bec}
.sheet .budget{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:var(--mut);margin-top:14px;padding:8px 12px;background:var(--panel2);border-radius:8px}
.sheet .budget.over{background:rgba(246,118,138,.15);color:var(--taken)}
.sheet .actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}
.sheet .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:120px;overflow-y:auto;display:none}
.sheet .steps-log.on{display:block}
.sheet .steps-log div::before{content:"› ";color:var(--acid)}
.sheet .msg{font-size:13px;margin-top:12px}
.sheet .msg.err{color:var(--taken)} .sheet .msg.ok{color:var(--ok)}
2026-08-29 21:57:33 +02:00
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
@media (max-width:520px){
.name-row{grid-template-columns:1fr auto}
}
< / style >
< / head >
< body >
< nav class = "topnav" >
< a class = "brand" href = "./" > ⭐ < span class = "g" > sirius.x< / span > < / a >
< a href = "./#try" > Try it< / a >
2026-08-29 22:00:37 +02:00
< a href = "./docs/" > 📚 Docs< / a >
2026-08-29 21:57:33 +02:00
< a href = "./theseus/" > 🧭 Theseus< / a >
2026-09-06 22:32:23 +02:00
< a href = "./register.html" > 🪪 Register< / a >
2026-08-31 05:40:47 +02:00
< a href = "./#roadmap" > Roadmap< / a >
sirius.x: /brand/ kit — logo, mono, avatar, banner, favicon, palette
brand/ ships every reusable mark plus a browseable kit page:
* logo.svg — full colour wordmark (star + sirius.x, .x in acid green)
* logo-mono.svg — currentColor version for print / one-colour merch
* favicon.svg — 64×64 rounded square, scales cleanly to 16×16
* avatar.svg — 512×512 square, hard corners (platforms round-mask)
* banner.svg — 1200×630 OG / Twitter Card canvas, crop-tolerant layout
* index.html — brand-kit page: asset previews + download links, full
colour palette with names/hex, typography sample, do/don't guidance,
MIT-attribution notice.
Nav: added 🎨 Brand link to every sirius.x page (landing, portal, docs,
theseus). og:image on every page updated to point at /brand/banner.svg
instead of the wordmark logo — social crawlers get a proper 1200×630
banner with title, tagline, and mark instead of a thin logo strip.
Kept /assets/favicon.svg and /assets/logo.svg in place so existing
external references (Nostr metadata, third-party embeds) don't break.
2026-08-31 05:44:25 +02:00
< a href = "./brand/" > 🎨 Brand< / a >
2026-08-29 21:57:33 +02:00
< a href = "https://silentmode.st/" rel = "noopener" > Silent Mode ↗< / a >
2026-08-29 22:22:37 +02:00
< a href = "./portal.html" class = "portal here" > 🔑 Sign in< / a >
2026-08-29 21:57:33 +02:00
< / nav >
< header class = "hero" >
< div class = "mark" > 🔑< / div >
< h1 > Your < span class = "g" > names< / span > portal< / h1 >
< p class = "tag" > Sign in with your Bitcoin Cash seed phrase or connect an external wallet to see
every BCNR name your wallet holds. Your keys never leave this browser tab.< / p >
< / header >
< div class = "wrap" >
< section id = "signin" >
< h2 > Sign in< / h2 >
< p class = "muted" > Pick one. Nothing is transmitted — resolution is a read-only chain query,
and signing (for record edits) happens locally in the browser or in your external wallet.< / p >
< div class = "authbox" style = "margin-top:14px" >
< div class = "card" >
< h3 > Import a seed phrase< / h3 >
< p > Paste your 12- or 24-word BIP-39 recovery phrase. It stays in memory only, wiped when you
close the tab or sign out.< / 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" > Sign in →< / button >
< span class = "dim" > Chipnet only for now.< / span >
< / div >
< div id = "signin-err" class = "err hidden" > < / div >
< / div >
< div class = "card" >
< h3 > Connect an external wallet< / h3 >
< p > Use Cashonize 0.9+ or Paytaca via WizardConnect. The wallet signs record edits — the seed
never touches this page.< / p >
< div class = "row" >
< button class = "btn ghost" id = "wc-btn" disabled > WizardConnect
< span class = "badge b-warn" style = "margin-left:6px" > soon< / span > < / button >
< / div >
< p class = "dim" style = "margin-top:8px" > Portal-side WizardConnect wiring is in progress; the
register.html flow already ships the WizardConnect adapter, and the portal will reuse it.< / p >
< / div >
< / div >
< div class = "status" style = "margin-top:14px" >
Don't have a wallet? Create one during
2026-09-06 22:32:23 +02:00
< a href = "./register.html" > name registration< / a > — the recovery phrase
2026-08-29 21:57:33 +02:00
is generated in your browser and encrypted with your password.
< / div >
< / section >
< section id = "me" class = "hidden" >
< h2 > Signed in< / h2 >
sirius.x/portal: register a TLD in-browser (M1.5 — chipnet, USD-priced)
Sign-in view grows a "Register a new TLD" card. User picks a label (validated
against [a-z0-9-]{1,16} and normalised as they type), sees live sats price
computed from a USD field via a static chipnet oracle rate (250,000 sats/USD
≈ $400/BCH), can check availability against navigate.st/api/tlds, then submit
a TREG signed with the built-in wallet — same code path seed-tld-beacon.mjs
uses server-side, but via the browser-exported registerTldWithBuiltInWallet.
* Live price display recalculates on USD-input change; service fee is
paid to REGISTRAR.serviceFee.address (same fee address name registrations
use — no per-TLD split yet since that's mainnet covenant territory).
* Check-availability polls the live /api/tlds endpoint before the user
commits sats to a doomed TREG.
* Progress log streams the shared onProgress steps
("checking-availability", "signing", "broadcast: <txid>") so users can
tell whether the transaction actually left the browser.
* Chipnet rate + form default of $5 make full-flow tests possible from
the operator wallet (~1.25M sats each); users can override the USD to
$0.01 for cheap iteration during testing.
UI verified end-to-end (label sanitisation, price recalc, availability
check against real API, buttons gate correctly on wallet state). Full
sign+broadcast requires a funded wallet — operator can verify.
Next: real oracle (CoinGecko / Kraken feed) + tiered pricing by label
length + admin panel for changing the rate config.
2026-09-06 18:18:41 +02:00
< div class = "card" id = "tld-register-card" >
< h3 style = "margin:0 0 .3rem;font-size:1.05rem" > Register a new TLD< / h3 >
< p class = "muted" style = "margin:0 0 .8rem" > TLDs are per-registry NFTs on the chipnet TLD beacon.
Public mints go through a service-fee output to fund the registry. Chipnet uses a placeholder
rate; mainnet will price by label length via a real oracle.< / p >
< label > Label < span class = "dim" style = "margin-left:6px" > 1– 16 chars, [a-z0-9-]< / span > < / label >
< input type = "text" id = "tld-label" placeholder = "myregistry" autocomplete = "off" spellcheck = "false" maxlength = "16" >
< label style = "margin-top:12px" > Service fee (USD) < span class = "dim" style = "margin-left:6px" > chipnet test rate< / span > < / label >
< input type = "text" id = "tld-usd" value = "5" >
< div class = "status" id = "tld-price" > = < b > 0< / b > sats · rate < b > 0< / b > sats/USD< / div >
< div class = "row" >
< button class = "btn acid small" id = "tld-check" disabled > Check availability< / button >
< button class = "btn small" id = "tld-submit" disabled > Register (~0 sats)< / button >
< / div >
< div id = "tld-log" class = "status" style = "display:none;font-family:ui-monospace,monospace;font-size:12.5px;margin-top:12px" > < / div >
< div id = "tld-msg" style = "margin-top:10px;font-size:13.5px" > < / div >
< / div >
< h2 style = "margin-top:2rem" > Wallet< / h2 >
2026-08-29 21:57:33 +02:00
< div class = "card" >
< label > Receive address< / label >
< div > < code id = "me-addr" > —< / code > < / div >
< label style = "margin-top:14px" > Token address (holds your name certificates)< / label >
< div > < code id = "me-taddr" > —< / code > < / div >
< div class = "row" >
< button class = "btn ghost small" id = "signout-btn" > Sign out< / button >
< span class = "dim" id = "me-info" > —< / span >
< / div >
< / div >
< h2 style = "margin-top:2rem" > Names you hold< / h2 >
< div class = "status" id = "names-status" > Loading names from the chain…< / div >
< div id = "names-list" > < / div >
< / section >
< / div >
2026-08-31 21:44:20 +02:00
<!-- record - editor modal — populated by JS when a name's Edit button fires -->
< div class = "modal" id = "editor" >
< div class = "sheet" >
< button class = "x" id = "ed-close" title = "close" > × < / button >
< h3 id = "ed-name" > —< / h3 >
< p class = "sub" > Edit records. Only your key can sign this UPD; the transaction fee is a few
cents in chain fees plus the beacon dust. Leave a field blank to omit that record.< / p >
< label > h < span class = "field-hint" > inline HTML — served as-is at the site root< / span > < / label >
< textarea id = "ed-h" placeholder = "<h1>My site</h1>" > < / textarea >
< label > s3 < span class = "field-hint" > Sia bucket key, trailing slash for directories< / span > < / label >
< input type = "text" id = "ed-s3" placeholder = "bns/myname/" >
< label > ip < span class = "field-hint" > IPv4 that serves this name over Host header< / span > < / label >
< input type = "text" id = "ed-ip" placeholder = "1.2.3.4" >
< label > tls < span class = "field-hint" > SHA-256 of your leaf cert — chain-pinned TLS trust< / span > < / label >
< input type = "text" id = "ed-tls" placeholder = "hex — 64 chars, no separators" >
< label > u < span class = "field-hint" > redirect target< / span > < / label >
< input type = "text" id = "ed-u" placeholder = "https://…" >
< div class = "budget" id = "ed-budget" > payload: < b > < span id = "ed-budget-used" > 0< / span > < / b > / < span id = "ed-budget-max" > ?< / span > bytes< / div >
< div class = "steps-log" id = "ed-log" > < / div >
< div class = "msg" id = "ed-msg" > < / div >
< div class = "actions" >
< button class = "btn ghost" id = "ed-cancel" > Cancel< / button >
< button class = "btn acid" id = "ed-save" > Save records →< / button >
< / div >
< / div >
< / div >
2026-08-29 21:57:33 +02:00
< footer >
Your keys, your names, your browser. ·
< a href = "./" > back to sirius.x< / a > ·
2026-09-06 22:32:23 +02:00
< a href = "./register.html" > register a new name< / a >
2026-08-29 21:57:33 +02:00
< / footer >
< script type = "importmap" >
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
< / script >
< script type = "module" >
// The registrar bundle exports everything we need: BuiltInWallet for local
// wallet unlock, resolveName/buildIndex for chain queries, getUtxosForAddresses
// for holdings. This portal is a read-first surface; record edits (UPD) will
// come next and will piggyback on the same signing path the registrar uses.
2026-08-31 03:35:39 +02:00
// Cache-buster bumped whenever the bundle changes. Chrome's in-memory ES
// module map ignores Cache-Control on the .js URL for the lifetime of a tab,
// so a plain "/js/bns-register.js" import can return a stale instance days
// after deploy. A query string forces the module loader to treat this as a
// new URL and fetch fresh bytes.
2026-09-06 18:53:17 +02:00
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260906fixlistunspent";
2026-08-29 21:57:33 +02:00
const $ = (id) => document.getElementById(id);
const esc = (s) => String(s).replace(/[& < >"']/g, (c) => ({"&":"& ","< ":"< ",">":"> ",'"':"" ","'":"' "}[c]));
let wallet = null;
// ---------- sign in ----------
$("signin-btn").addEventListener("click", async () => {
const btn = $("signin-btn");
const errBox = $("signin-err");
errBox.classList.add("hidden");
const raw = $("seed").value.trim();
if (!raw) { errBox.textContent = "Enter your seed phrase to sign in."; errBox.classList.remove("hidden"); return; }
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 = "Sign in →";
return;
}
// Wipe the textarea immediately — the seed is now in the wallet object only.
$("seed").value = "";
btn.disabled = false; btn.textContent = "Sign in →";
await enterPortal();
});
$("signout-btn").addEventListener("click", () => {
wallet = null;
$("me").classList.add("hidden");
$("signin").classList.remove("hidden");
$("names-list").innerHTML = "";
$("names-status").textContent = "Loading names from the chain…";
});
async function enterPortal() {
$("signin").classList.add("hidden");
$("me").classList.remove("hidden");
$("me-addr").textContent = wallet.address;
$("me-taddr").textContent = wallet.tokenAddress;
$("me-info").textContent = "watched addresses: " + wallet.watchedAddresses.length;
sirius.x/portal: register a TLD in-browser (M1.5 — chipnet, USD-priced)
Sign-in view grows a "Register a new TLD" card. User picks a label (validated
against [a-z0-9-]{1,16} and normalised as they type), sees live sats price
computed from a USD field via a static chipnet oracle rate (250,000 sats/USD
≈ $400/BCH), can check availability against navigate.st/api/tlds, then submit
a TREG signed with the built-in wallet — same code path seed-tld-beacon.mjs
uses server-side, but via the browser-exported registerTldWithBuiltInWallet.
* Live price display recalculates on USD-input change; service fee is
paid to REGISTRAR.serviceFee.address (same fee address name registrations
use — no per-TLD split yet since that's mainnet covenant territory).
* Check-availability polls the live /api/tlds endpoint before the user
commits sats to a doomed TREG.
* Progress log streams the shared onProgress steps
("checking-availability", "signing", "broadcast: <txid>") so users can
tell whether the transaction actually left the browser.
* Chipnet rate + form default of $5 make full-flow tests possible from
the operator wallet (~1.25M sats each); users can override the USD to
$0.01 for cheap iteration during testing.
UI verified end-to-end (label sanitisation, price recalc, availability
check against real API, buttons gate correctly on wallet state). Full
sign+broadcast requires a funded wallet — operator can verify.
Next: real oracle (CoinGecko / Kraken feed) + tiered pricing by label
length + admin panel for changing the rate config.
2026-09-06 18:18:41 +02:00
// Initialise the TLD-register card now that the wallet is available.
if (typeof updateTldPrice === "function") { updateTldPrice(); updateTldButtons(); }
2026-08-29 21:57:33 +02:00
await loadNames();
}
// ---------- load names ----------
2026-08-31 05:40:47 +02:00
// Approach: derive the scripthash for each watched address locally (the
// wallet code already knows how), send them in one POST to
// /api/holdings, and render whatever the gateway returns. Doing the
// wallet-holdings query server-side is what makes this work on browsers
// stuck behind firewalls that block chipnet electrum ports (50004,
// 50011, 62002) — the API is same-origin HTTPS on port 443, and the
// gateway can reach the full electrums the browser can't.
2026-08-29 21:57:33 +02:00
async function loadNames() {
const status = $("names-status");
const list = $("names-list");
list.innerHTML = "";
status.className = "status";
2026-08-31 05:40:47 +02:00
status.textContent = "Preparing your addresses…";
2026-08-29 21:57:33 +02:00
try {
const addresses = wallet.watchedAddresses;
2026-08-31 05:40:47 +02:00
const scripthashes = addresses.map((a) => BNS.addressToScripthash(a));
status.textContent = `Asking the gateway about ${addresses.length} addresses…`;
const r = await fetch("https://silentmode.st/api/holdings", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ scripthashes }),
});
if (!r.ok) {
const detail = await r.text().catch(() => "");
throw new Error(`API ${r.status}: ${detail.slice(0, 200)}`);
}
const { count, names: mine } = await r.json();
if (!count) {
2026-08-29 21:57:33 +02:00
status.className = "status";
Sweep: mobile Ariadne updates, Deviant brand + sites, Hephaestus bootstrap, snappymail
Broad-scope commit sweeping in work from parallel sessions plus this session's
dev-only utilities. No single unifying theme — this is the "commit everything
that's ready" pass. Grouped by area below.
AriadneResolver / mobile
* Small updates to BchFetcher, Bns, MainActivity, AriadneVpnService, and the
Android manifest — from a parallel mobile-resolver session.
* AriadneResolver/mobile/webpreview/ — new local dev webview (index.html +
server.mjs) for iterating on the mobile UI without a device.
Email / SnappyMail
* Email/snappymail-plugin/silentmode-addresses — new SnappyMail plugin
(README, CSS, PHP entry, JS) for surfacing @silentmode.st address
management inside the webmail UI.
Hephaestus (Forgejo + auth-proxy)
* auth-proxy/src/oidc.ts + docker-compose.yml updates.
* scripts/bootstrap-auth.sh — idempotent script that registers the wallet
auth-proxy as Forgejo's OIDC provider after `docker compose up -d`. All
secrets read from .env — no literals in the script.
TheseusNavigator / dev tools
* dev/list-tlds.mjs — enumerate every registered BNS name grouped by TLD via
our own indexer.
* dev/show-tlds-bch.mjs — dump the full records payload of the legacy
tlds.bch name (kept as a diagnostic for the list-in-one-NFT era).
Deviant sites + brand
* site/deviant/ — new microsites (anthemus, potidaea, brand, mindmap, main
index) plus the full brand kit under site/deviant/brand/ (logos, icons,
palette, social exports as SVG source + PNG renders).
* site-sirius-x/portal.html — updates.
Coordination
* _coordination/sessions/mobile-resolver.md — parallel-session notes.
Deliberately excluded from this commit:
* scratchpad/ — this session's temp drafts (Sia doc mockups, tlds UPD payloads).
* TheseusNavigator/_prev/ — ~372 MB of prior release binaries; belongs in
a separate artefacts store, not the git tree. Add to .gitignore next pass.
2026-08-30 10:38:49 +02:00
status.textContent = "No BCNR names found for this wallet. If you just registered, wait a minute for confirmation and reload.";
2026-08-29 21:57:33 +02:00
return;
}
status.className = "status ok";
2026-08-31 05:40:47 +02:00
status.textContent = `You own ${count} name${count === 1 ? "" : "s"}.`;
for (const e of mine) {
2026-08-29 21:57:33 +02:00
const div = document.createElement("div");
div.className = "name-row";
const records = e.records & & Object.keys(e.records).length
? Object.entries(e.records).map(([k, v]) => `< span class = "k" > ${esc(k)}=< / span > ${esc(String(v).slice(0, 80))}`).join(" · ")
Sweep: mobile Ariadne updates, Deviant brand + sites, Hephaestus bootstrap, snappymail
Broad-scope commit sweeping in work from parallel sessions plus this session's
dev-only utilities. No single unifying theme — this is the "commit everything
that's ready" pass. Grouped by area below.
AriadneResolver / mobile
* Small updates to BchFetcher, Bns, MainActivity, AriadneVpnService, and the
Android manifest — from a parallel mobile-resolver session.
* AriadneResolver/mobile/webpreview/ — new local dev webview (index.html +
server.mjs) for iterating on the mobile UI without a device.
Email / SnappyMail
* Email/snappymail-plugin/silentmode-addresses — new SnappyMail plugin
(README, CSS, PHP entry, JS) for surfacing @silentmode.st address
management inside the webmail UI.
Hephaestus (Forgejo + auth-proxy)
* auth-proxy/src/oidc.ts + docker-compose.yml updates.
* scripts/bootstrap-auth.sh — idempotent script that registers the wallet
auth-proxy as Forgejo's OIDC provider after `docker compose up -d`. All
secrets read from .env — no literals in the script.
TheseusNavigator / dev tools
* dev/list-tlds.mjs — enumerate every registered BNS name grouped by TLD via
our own indexer.
* dev/show-tlds-bch.mjs — dump the full records payload of the legacy
tlds.bch name (kept as a diagnostic for the list-in-one-NFT era).
Deviant sites + brand
* site/deviant/ — new microsites (anthemus, potidaea, brand, mindmap, main
index) plus the full brand kit under site/deviant/brand/ (logos, icons,
palette, social exports as SVG source + PNG renders).
* site-sirius-x/portal.html — updates.
Coordination
* _coordination/sessions/mobile-resolver.md — parallel-session notes.
Deliberately excluded from this commit:
* scratchpad/ — this session's temp drafts (Sia doc mockups, tlds UPD payloads).
* TheseusNavigator/_prev/ — ~372 MB of prior release binaries; belongs in
a separate artefacts store, not the git tree. Add to .gitignore next pass.
2026-08-30 10:38:49 +02:00
: '< span class = "k" > (no records set)< / span > ';
2026-08-31 21:44:20 +02:00
const recJson = esc(JSON.stringify(e.records || {}));
2026-08-29 21:57:33 +02:00
div.innerHTML =
`< div class = "n" > ${esc(e.name)}< / div > ` +
Sweep: mobile Ariadne updates, Deviant brand + sites, Hephaestus bootstrap, snappymail
Broad-scope commit sweeping in work from parallel sessions plus this session's
dev-only utilities. No single unifying theme — this is the "commit everything
that's ready" pass. Grouped by area below.
AriadneResolver / mobile
* Small updates to BchFetcher, Bns, MainActivity, AriadneVpnService, and the
Android manifest — from a parallel mobile-resolver session.
* AriadneResolver/mobile/webpreview/ — new local dev webview (index.html +
server.mjs) for iterating on the mobile UI without a device.
Email / SnappyMail
* Email/snappymail-plugin/silentmode-addresses — new SnappyMail plugin
(README, CSS, PHP entry, JS) for surfacing @silentmode.st address
management inside the webmail UI.
Hephaestus (Forgejo + auth-proxy)
* auth-proxy/src/oidc.ts + docker-compose.yml updates.
* scripts/bootstrap-auth.sh — idempotent script that registers the wallet
auth-proxy as Forgejo's OIDC provider after `docker compose up -d`. All
secrets read from .env — no literals in the script.
TheseusNavigator / dev tools
* dev/list-tlds.mjs — enumerate every registered BNS name grouped by TLD via
our own indexer.
* dev/show-tlds-bch.mjs — dump the full records payload of the legacy
tlds.bch name (kept as a diagnostic for the list-in-one-NFT era).
Deviant sites + brand
* site/deviant/ — new microsites (anthemus, potidaea, brand, mindmap, main
index) plus the full brand kit under site/deviant/brand/ (logos, icons,
palette, social exports as SVG source + PNG renders).
* site-sirius-x/portal.html — updates.
Coordination
* _coordination/sessions/mobile-resolver.md — parallel-session notes.
Deliberately excluded from this commit:
* scratchpad/ — this session's temp drafts (Sia doc mockups, tlds UPD payloads).
* TheseusNavigator/_prev/ — ~372 MB of prior release binaries; belongs in
a separate artefacts store, not the git tree. Add to .gitignore next pass.
2026-08-30 10:38:49 +02:00
'< span class = "badge b-x" > on chain< / span > ' +
2026-08-29 21:57:33 +02:00
`< a class = "btn small ghost" href = "https://silentmode.st/bns/${encodeURIComponent(e.name)}/" target = "_blank" rel = "noopener" > view →< / a > ` +
2026-08-31 21:44:20 +02:00
`< button class = "btn small acid" data-edit = "${esc(e.name)}" data-records = '${recJson}' > Edit< / button > ` +
2026-08-29 21:57:33 +02:00
`< div class = "rec" > ${records}< / div > `;
list.appendChild(div);
}
2026-08-31 21:44:20 +02:00
// Wire Edit buttons after render.
list.querySelectorAll("[data-edit]").forEach((btn) => {
btn.onclick = () => openEditor(btn.getAttribute("data-edit"), JSON.parse(btn.getAttribute("data-records") || "{}"));
});
2026-08-29 21:57:33 +02:00
} catch (e) {
status.className = "status err";
2026-08-31 05:40:47 +02:00
status.textContent = "Could not load names: " + (e.message || e);
2026-08-29 21:57:33 +02:00
}
}
2026-08-31 21:44:20 +02:00
// ---------- record editor ----------
// Modal that composes a UPD payload from a small form and signs+broadcasts
// with the same code path register.html uses (BNS.setRecordsWithBuiltInWallet).
// External-wallet (WizardConnect) support piggybacks the same adapter and is a
// follow-up — the built-in path is the one this session's wallet flow can already
// sign with.
const EDITABLE_KEYS = ["h", "s3", "ip", "tls", "u"];
let editorName = null;
function openEditor(name, records) {
editorName = name;
$("ed-name").textContent = name;
for (const k of EDITABLE_KEYS) $("ed-" + k).value = records[k] || "";
$("ed-msg").textContent = "";
$("ed-msg").className = "msg";
$("ed-log").className = "steps-log";
$("ed-log").innerHTML = "";
$("ed-save").disabled = false;
$("ed-save").textContent = "Save records →";
updateBudget();
$("editor").classList.add("open");
}
function closeEditor() {
$("editor").classList.remove("open");
editorName = null;
}
$("ed-close").addEventListener("click", closeEditor);
$("ed-cancel").addEventListener("click", closeEditor);
$("editor").addEventListener("click", (e) => { if (e.target === $("editor")) closeEditor(); });
// Bytes budget: BNS payload is capped at 200 bytes total, of which the envelope
// (op + name) uses some. Show the user how many bytes their records take vs the
// remaining budget, and disable Save if they've gone over.
function collectRecords() {
const r = {};
for (const k of EDITABLE_KEYS) {
const v = $("ed-" + k).value.trim();
if (v) r[k] = v;
}
return r;
}
function updateBudget() {
if (!editorName) return;
try {
const max = BNS.payloadBudget(editorName, "UPD");
// The records object encodes to JSON inside the payload. Rough size:
// JSON.stringify(records).length gives a close-enough client-side estimate
// (the actual envelope is composed by the bundle at send time).
const used = new TextEncoder().encode(JSON.stringify(collectRecords())).length;
$("ed-budget-max").textContent = max;
$("ed-budget-used").textContent = used;
const over = used > max;
$("ed-budget").classList.toggle("over", over);
$("ed-save").disabled = over;
} catch { /* budget helper not available yet — ignore */ }
}
for (const k of EDITABLE_KEYS) $("ed-" + k).addEventListener("input", updateBudget);
$("ed-save").addEventListener("click", async () => {
if (!editorName || !wallet) return;
const records = collectRecords();
const msg = $("ed-msg");
const log = $("ed-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; };
msg.textContent = ""; msg.className = "msg";
$("ed-save").disabled = true;
$("ed-save").textContent = "Signing…";
let el = null;
try {
el = await BNS.connect();
push("connected to chipnet");
const res = await BNS.setRecordsWithBuiltInWallet(el, {
wallet, name: editorName, records,
onProgress: (step) => push(step),
});
push("broadcast: " + (res.txid || res.txId || "(no txid)"));
msg.className = "msg ok";
msg.textContent = "Saved. Records will refresh on the chain within a block.";
$("ed-save").textContent = "Saved ✓";
// Refresh the names list in the background so the user sees the new
// records once they land (may take a few seconds via cache/mempool).
setTimeout(() => loadNames(), 2500);
} catch (e) {
msg.className = "msg err";
msg.textContent = "Failed: " + (e.message || e);
push("error: " + (e.message || e));
$("ed-save").disabled = false;
$("ed-save").textContent = "Save records →";
} finally {
try { el?.close?.(); } catch {}
}
});
sirius.x/portal: register a TLD in-browser (M1.5 — chipnet, USD-priced)
Sign-in view grows a "Register a new TLD" card. User picks a label (validated
against [a-z0-9-]{1,16} and normalised as they type), sees live sats price
computed from a USD field via a static chipnet oracle rate (250,000 sats/USD
≈ $400/BCH), can check availability against navigate.st/api/tlds, then submit
a TREG signed with the built-in wallet — same code path seed-tld-beacon.mjs
uses server-side, but via the browser-exported registerTldWithBuiltInWallet.
* Live price display recalculates on USD-input change; service fee is
paid to REGISTRAR.serviceFee.address (same fee address name registrations
use — no per-TLD split yet since that's mainnet covenant territory).
* Check-availability polls the live /api/tlds endpoint before the user
commits sats to a doomed TREG.
* Progress log streams the shared onProgress steps
("checking-availability", "signing", "broadcast: <txid>") so users can
tell whether the transaction actually left the browser.
* Chipnet rate + form default of $5 make full-flow tests possible from
the operator wallet (~1.25M sats each); users can override the USD to
$0.01 for cheap iteration during testing.
UI verified end-to-end (label sanitisation, price recalc, availability
check against real API, buttons gate correctly on wallet state). Full
sign+broadcast requires a funded wallet — operator can verify.
Next: real oracle (CoinGecko / Kraken feed) + tiered pricing by label
length + admin panel for changing the rate config.
2026-09-06 18:18:41 +02:00
// ---------- TLD register (chipnet MVP) ----------
// Simple oracle: static chipnet rate for testing. Replace with a live feed
// (CoinGecko, Kraken, or a Chainlink adapter) before mainnet. The `sats` output
// is what actually gets locked into a service-fee output on the TREG transaction.
const CHIPNET_SATS_PER_USD = 250_000; // 1 BCH ≈ $400 test rate
function usdToSats(usd) {
const n = Number(String(usd).replace(/[^\d.]/g, "")) || 0;
return Math.max(0, Math.round(n * CHIPNET_SATS_PER_USD));
}
function updateTldPrice() {
const usd = $("tld-usd").value.trim() || "0";
const sats = usdToSats(usd);
$("tld-price").innerHTML = `= < b > ${sats.toLocaleString()}< / b > sats · rate < b > ${CHIPNET_SATS_PER_USD.toLocaleString()}< / b > sats/USD (chipnet)`;
$("tld-submit").textContent = `Register (~${sats.toLocaleString()} sats)`;
}
function validateTldLabel(label) {
return /^[a-z0-9-]{1,16}$/.test(label);
}
function updateTldButtons() {
const label = $("tld-label").value.trim().toLowerCase();
const valid = validateTldLabel(label);
$("tld-check").disabled = !valid || !wallet;
$("tld-submit").disabled = !valid || !wallet;
}
$("tld-label").addEventListener("input", () => {
// normalise as user types
$("tld-label").value = $("tld-label").value.toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 16);
updateTldButtons();
});
$("tld-usd").addEventListener("input", updateTldPrice);
$("tld-check").addEventListener("click", async () => {
const label = $("tld-label").value.trim();
const msg = $("tld-msg"); msg.textContent = ""; msg.className = "";
try {
const r = await fetch("https://navigate.st/api/tlds");
if (!r.ok) throw new Error("api " + r.status);
const j = await r.json();
const taken = (j.tlds || []).some((t) => (t.tld || t) === label);
msg.className = taken ? "err" : "ok";
msg.textContent = taken ? `❌ .${label} is already registered` : `✅ .${label} is available`;
msg.style.color = taken ? "var(--taken)" : "var(--ok)";
} catch (e) {
msg.textContent = "could not check: " + (e.message || e);
msg.style.color = "var(--warn)";
}
});
$("tld-submit").addEventListener("click", async () => {
if (!wallet) return;
const label = $("tld-label").value.trim();
if (!validateTldLabel(label)) return;
const sats = BigInt(usdToSats($("tld-usd").value.trim() || "0"));
const msg = $("tld-msg"); msg.textContent = ""; msg.className = "";
const log = $("tld-log"); log.style.display = "block"; log.innerHTML = "";
const push = (t) => { const d = document.createElement("div"); d.textContent = "› " + t; log.appendChild(d); log.scrollTop = log.scrollHeight; };
$("tld-submit").disabled = true;
$("tld-submit").textContent = "Signing…";
let el = null;
try {
el = await BNS.connect();
push("connected to chipnet");
const res = await BNS.registerTldWithBuiltInWallet(el, {
wallet,
tld: label,
records: {},
serviceFee: {
address: BNS.REGISTRAR?.serviceFee?.address ?? null,
sats,
},
tldListUrl: "https://navigate.st/api/tlds",
onProgress: (step) => push(step),
});
push("broadcast: " + (res.txid || res.txId || "(no txid)"));
msg.style.color = "var(--ok)";
msg.textContent = `✅ .${label} minted. Category ${(res.category || "").slice(0, 16)}…`;
$("tld-submit").textContent = "Registered ✓";
// Trigger a name-list refresh — new TLD cert is an NFT held by this wallet.
setTimeout(() => loadNames(), 2500);
} catch (e) {
msg.style.color = "var(--taken)";
msg.textContent = "Failed: " + (e.message || e);
push("error: " + (e.message || e));
$("tld-submit").disabled = false;
updateTldPrice(); // reset button label
} finally {
try { el?.close?.(); } catch {}
}
});
// Initial paint on page load so the card shows the price even before sign-in.
updateTldPrice();
2026-08-29 21:57:33 +02:00
< / script >
2026-09-06 18:53:17 +02:00
< script defer src = "./js/profile-menu.js" > < / script >
2026-08-29 21:57:33 +02:00
< / body >
< / html >