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" >
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
< title > Portal — your names on Sirius.X< / title >
2026-08-29 21:57:33 +02:00
< 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" >
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
< meta property = "og:site_name" content = "Sirius.X" >
< meta property = "og:title" content = "Sirius.X portal — your BCNR names" >
2026-08-31 04:30:53 +02:00
< 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" >
feat(sirius-x): Deviant sky palette + Fraunces/Ubuntu reader toggle
Design translated from the /design canvas review. Sirius.X now shares
Deviant's "signal" theme background (layered sky-rim/mid gradients over
#050810) and the Silent Mode family typography stack.
Typography:
Fraunces (opsz variable serif) headings h1/h2/h3
DM Sans body / UI
JetBrains Mono code, hashes, mono cells
Ubuntu opt-in Bitcoin Cash brand font,
swaps everything at :root when
<html data-font-cmp="ubuntu">
All via Google Fonts, one <link> per page.
Font picker (js/theme.js):
Small pill in the topnav (Fraunces default / Ubuntu alt) — persisted
to localStorage.sirius-font. Sets the data-font-cmp attribute; the
swap CSS lives in each page's <style> block so !important overrides
page-local font-family declarations at the necessary specificity.
Mono elements stay JetBrains Mono under both fonts.
Palette (cool sky vs old flat dark navy):
--bg #0b0e14 → #050810
--panel2 #18202c → #0a0f1c (matches Deviant --sky-mid)
--line rgba(255,255,255,.09) → .06
--ink #e7eaf1 → #f1f4fa
--mut #8b98a9 → #b8c2d4
--dim #5e6678 → #6a7488
new: --sky-rim #131a2a, --chain-native #0AC18E (BCH brand green,
reserved for chain-native badges/proofs like the Bitcoin.Cash
body copy)
Copy — "no renewals" softened everywhere it appeared. Renewals will
be a per-TLD policy set by each TLD owner; the flat claim was wrong
across the board. Landing card, tld.html hero and body, plus the
og:description all updated.
Applied to: index.html, portal.html, tld.html, theseus/index.html,
brand/index.html, docs/index.html, admin/index.html.
2026-09-10 00:25:18 +02:00
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
< link href = "https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500;9..144,600;9..144,700&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Ubuntu:wght@400;500;700&display=swap" rel = "stylesheet" >
2026-08-29 21:57:33 +02:00
< style >
:root{
feat(sirius-x): Deviant sky palette + Fraunces/Ubuntu reader toggle
Design translated from the /design canvas review. Sirius.X now shares
Deviant's "signal" theme background (layered sky-rim/mid gradients over
#050810) and the Silent Mode family typography stack.
Typography:
Fraunces (opsz variable serif) headings h1/h2/h3
DM Sans body / UI
JetBrains Mono code, hashes, mono cells
Ubuntu opt-in Bitcoin Cash brand font,
swaps everything at :root when
<html data-font-cmp="ubuntu">
All via Google Fonts, one <link> per page.
Font picker (js/theme.js):
Small pill in the topnav (Fraunces default / Ubuntu alt) — persisted
to localStorage.sirius-font. Sets the data-font-cmp attribute; the
swap CSS lives in each page's <style> block so !important overrides
page-local font-family declarations at the necessary specificity.
Mono elements stay JetBrains Mono under both fonts.
Palette (cool sky vs old flat dark navy):
--bg #0b0e14 → #050810
--panel2 #18202c → #0a0f1c (matches Deviant --sky-mid)
--line rgba(255,255,255,.09) → .06
--ink #e7eaf1 → #f1f4fa
--mut #8b98a9 → #b8c2d4
--dim #5e6678 → #6a7488
new: --sky-rim #131a2a, --chain-native #0AC18E (BCH brand green,
reserved for chain-native badges/proofs like the Bitcoin.Cash
body copy)
Copy — "no renewals" softened everywhere it appeared. Renewals will
be a per-TLD policy set by each TLD owner; the flat claim was wrong
across the board. Landing card, tld.html hero and body, plus the
og:description all updated.
Applied to: index.html, portal.html, tld.html, theseus/index.html,
brand/index.html, docs/index.html, admin/index.html.
2026-09-10 00:25:18 +02:00
--bg:#050810; --panel:#141a24; --panel2:#0a0f1c; --line:rgba(255,255,255,.06);
--sky-mid:#0a0f1c; --sky-rim:#131a2a;
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
--chain-native:#0AC18E;
2026-08-29 21:57:33 +02:00
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
feat(sirius-x): Deviant sky palette + Fraunces/Ubuntu reader toggle
Design translated from the /design canvas review. Sirius.X now shares
Deviant's "signal" theme background (layered sky-rim/mid gradients over
#050810) and the Silent Mode family typography stack.
Typography:
Fraunces (opsz variable serif) headings h1/h2/h3
DM Sans body / UI
JetBrains Mono code, hashes, mono cells
Ubuntu opt-in Bitcoin Cash brand font,
swaps everything at :root when
<html data-font-cmp="ubuntu">
All via Google Fonts, one <link> per page.
Font picker (js/theme.js):
Small pill in the topnav (Fraunces default / Ubuntu alt) — persisted
to localStorage.sirius-font. Sets the data-font-cmp attribute; the
swap CSS lives in each page's <style> block so !important overrides
page-local font-family declarations at the necessary specificity.
Mono elements stay JetBrains Mono under both fonts.
Palette (cool sky vs old flat dark navy):
--bg #0b0e14 → #050810
--panel2 #18202c → #0a0f1c (matches Deviant --sky-mid)
--line rgba(255,255,255,.09) → .06
--ink #e7eaf1 → #f1f4fa
--mut #8b98a9 → #b8c2d4
--dim #5e6678 → #6a7488
new: --sky-rim #131a2a, --chain-native #0AC18E (BCH brand green,
reserved for chain-native badges/proofs like the Bitcoin.Cash
body copy)
Copy — "no renewals" softened everywhere it appeared. Renewals will
be a per-TLD policy set by each TLD owner; the flat claim was wrong
across the board. Landing card, tld.html hero and body, plus the
og:description all updated.
Applied to: index.html, portal.html, tld.html, theseus/index.html,
brand/index.html, docs/index.html, admin/index.html.
2026-09-10 00:25:18 +02:00
--serif:'Fraunces',Georgia,serif;
--sans:'DM Sans','Inter',system-ui,sans-serif;
--mono:'JetBrains Mono',ui-monospace,monospace;
2026-08-29 21:57:33 +02:00
}
*{box-sizing:border-box}
feat(sirius-x): Deviant sky palette + Fraunces/Ubuntu reader toggle
Design translated from the /design canvas review. Sirius.X now shares
Deviant's "signal" theme background (layered sky-rim/mid gradients over
#050810) and the Silent Mode family typography stack.
Typography:
Fraunces (opsz variable serif) headings h1/h2/h3
DM Sans body / UI
JetBrains Mono code, hashes, mono cells
Ubuntu opt-in Bitcoin Cash brand font,
swaps everything at :root when
<html data-font-cmp="ubuntu">
All via Google Fonts, one <link> per page.
Font picker (js/theme.js):
Small pill in the topnav (Fraunces default / Ubuntu alt) — persisted
to localStorage.sirius-font. Sets the data-font-cmp attribute; the
swap CSS lives in each page's <style> block so !important overrides
page-local font-family declarations at the necessary specificity.
Mono elements stay JetBrains Mono under both fonts.
Palette (cool sky vs old flat dark navy):
--bg #0b0e14 → #050810
--panel2 #18202c → #0a0f1c (matches Deviant --sky-mid)
--line rgba(255,255,255,.09) → .06
--ink #e7eaf1 → #f1f4fa
--mut #8b98a9 → #b8c2d4
--dim #5e6678 → #6a7488
new: --sky-rim #131a2a, --chain-native #0AC18E (BCH brand green,
reserved for chain-native badges/proofs like the Bitcoin.Cash
body copy)
Copy — "no renewals" softened everywhere it appeared. Renewals will
be a per-TLD policy set by each TLD owner; the flat claim was wrong
across the board. Landing card, tld.html hero and body, plus the
og:description all updated.
Applied to: index.html, portal.html, tld.html, theseus/index.html,
brand/index.html, docs/index.html, admin/index.html.
2026-09-10 00:25:18 +02:00
body{margin:0;
background:radial-gradient(ellipse 1200px 600px at 50% -5%,var(--sky-rim) 0%,transparent 60%),
radial-gradient(ellipse 900px 500px at 100% 100%,var(--sky-mid) 0%,transparent 55%),
radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),
var(--bg);
background-attachment:fixed;
color:var(--ink);font:16px/1.6 var(--sans);min-height:100vh;font-feature-settings:"ss01","ss02"}
h1,h2,h3{font-family:var(--serif);font-feature-settings:"ss01","ss02"}
code,.mono{font-family:var(--mono)}
html[data-font-cmp="ubuntu"] body,
html[data-font-cmp="ubuntu"] h1, html[data-font-cmp="ubuntu"] h2, html[data-font-cmp="ubuntu"] h3,
html[data-font-cmp="ubuntu"] p, html[data-font-cmp="ubuntu"] a, html[data-font-cmp="ubuntu"] button,
html[data-font-cmp="ubuntu"] span, html[data-font-cmp="ubuntu"] div,
html[data-font-cmp="ubuntu"] label, html[data-font-cmp="ubuntu"] input, html[data-font-cmp="ubuntu"] li{
font-family:'Ubuntu',system-ui,sans-serif !important
}
html[data-font-cmp="ubuntu"] code, html[data-font-cmp="ubuntu"] .mono{font-family:var(--mono) !important}
.font-picker{display:inline-flex;align-items:center;gap:2px;padding:2px;margin:0 10px;
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:999px;
font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase}
.font-picker button{background:transparent;border:none;color:var(--dim);
padding:4px 10px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:inherit;
letter-spacing:inherit;text-transform:inherit;transition:color .12s}
.font-picker button:hover{color:var(--ink)}
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
/* Profile-section tabs — horizontal menu inside the signed-in section.
Sticky under the topnav so context stays visible on scroll. Panes are
just < div data-pane = "..." > siblings; JS toggles a `hidden` class. */
.profile-tabs{position:sticky;top:52px;z-index:15;display:flex;gap:6px;
padding:12px 0;margin:0 0 1.4rem;background:rgba(5,8,16,.86);
backdrop-filter:blur(10px);border-bottom:1px solid var(--line);
flex-wrap:wrap;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase}
.profile-tabs button{background:transparent;border:1px solid transparent;color:var(--mut);
padding:8px 14px;border-radius:999px;cursor:pointer;font-family:inherit;
font-size:inherit;letter-spacing:inherit;text-transform:inherit;
display:inline-flex;align-items:center;gap:8px;transition:all .12s}
.profile-tabs button:hover{color:var(--ink);background:rgba(255,255,255,.03);border-color:var(--line)}
.profile-tabs button.active{background:var(--acid);color:var(--bg);font-weight:600;border-color:var(--acid)}
.profile-tabs button .count{font-family:var(--mono);font-size:11px;padding:1px 7px;border-radius:999px;
background:rgba(255,255,255,.08);color:var(--ink);letter-spacing:.02em;text-transform:none;font-weight:500}
.profile-tabs button.active .count{background:rgba(5,8,16,.24);color:var(--bg)}
.pane{animation:paneIn .18s ease-out}
.pane[hidden]{display:none}
@keyframes paneIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
/* Overview stat grid */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-top:1rem}
.stat{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
.stat .lbl{font-family:var(--mono);font-size:10.5px;letter-spacing:.24em;text-transform:uppercase;color:var(--dim);margin-bottom:8px;
display:flex;align-items:center;gap:6px}
.stat .lbl:before{content:"";width:5px;height:5px;background:var(--acid);border-radius:50%}
.stat .val{font-family:var(--serif);font-size:32px;font-weight:600;color:var(--ink);letter-spacing:-.01em;line-height:1}
.stat .sub{color:var(--mut);font-size:12.5px;margin-top:4px}
2026-08-29 21:57:33 +02:00
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}
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
.name-row{display:grid;grid-template-columns:1fr auto 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" >
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
< a class = "brand" href = "./" > ⭐ < span class = "g" > Sirius.X< / span > < / a >
2026-08-29 21:57:33 +02:00
< a href = "./#try" > Try it< / a >
2026-09-08 00:10:11 +02:00
< a href = "./#search-input" > ✏️ Register< / a >
feat(sirius-x): public tld.html — search + mint a TLD end-to-end
Missing user-facing surface: minting a TLD required either the portal
(sign in first, register second, tucked under 'Signed in') or the
operator-only admin panel. New tld.html is a public, single-purpose
page that lets anyone go straight from label to certificate.
Flow: type a label → live check against /api/tlds → 'Register .foo →'
appears → sign-in cards (New / Import / WizardConnect) revealed inline
→ mint section shows label, address, price → submit walks through
'connected → checking-availability → loading-coins → building →
signing → broadcasting → registered'.
Wire is proven end-to-end (search returns a registerable card for
free labels, wallet flow generates a valid seed, address, submit
reaches 'wallet is empty' with a friendly faucet hint for the
signed-in address). Downstream of loading-coins is the same build/
sign/broadcast path exercised by every name registration.
Dispatch on wallet.source uses registerTldWithExternalWallet when
signed in via WC, else registerTldWithBuiltInWallet. Live TLD table
under the search shows every currently registered TLD with its
category; refreshes on successful mint.
Nav: '🌐 TLD' added to all 7 pages between Register and Brand.
Landing's 'Register a TLD' card now points here (previously linked
to the operator admin, which locked out anyone who isn't operator).
2026-09-07 20:39:11 +02:00
< a href = "./tld.html" > 🌐 TLD< / a >
feat(sirius-x/nav): use theseus.x compass SVG in Theseus button, not emoji
The nav's 'Theseus' link showed a generic 🧭 emoji, which renders
differently in every browser/OS and does not match theseus.x's
actual brand mark. Replaced with a small inline SVG of theseus.x's
compass (rounded dark square, N-half red, S-half acid) at 14x14 so
the button now carries theseus.x's real logo — recognisable and
identical everywhere.
Inlined the SVG rather than pointing at a shared asset so there's no
extra request per page and no relative-path arithmetic between root
and subdir pages. Applied to all 8 pages: landing, portal, register,
tld, theseus, docs, brand, admin.
2026-09-07 22:17:33 +02:00
< 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 >
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" >
2026-09-08 18:30:52 +02:00
< h2 > Sign in or create a wallet< / h2 >
< p class = "muted" > The same wallet that will hold your names does the signing. Pick a tab —
everything happens on this page; keys never leave your browser.< / p >
<!-- register - flow.js paints the tab bar + active form into this container
via window.siriusRenderSignInInline(). No modal, no launcher. -->
< div id = "signin-inline" style = "margin-top:1rem" > < / div >
2026-09-08 02:35:56 +02:00
< div id = "signin-err" class = "err hidden" > < / div >
2026-08-29 21:57:33 +02:00
< / section >
< section id = "me" class = "hidden" >
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
<!-- Horizontal tab menu. Counts populate from loadNames() (Names) and,
once /api/holdings returns TLDs, from a second query. -->
< nav class = "profile-tabs" role = "tablist" aria-label = "Portal sections" >
< button role = "tab" data-pane = "overview" class = "active" > Overview< / button >
< button role = "tab" data-pane = "names" > Names < span class = "count" id = "tab-names-count" > 0< / span > < / button >
< button role = "tab" data-pane = "tlds" > TLDs < span class = "count" id = "tab-tlds-count" > 0< / span > < / button >
< button role = "tab" data-pane = "wallet" > Wallet< / button >
< button role = "tab" data-pane = "settings" > Settings< / button >
< / nav >
<!-- OVERVIEW -->
< div class = "pane" data-pane = "overview" >
< h2 style = "margin:0" > Overview< / h2 >
< p class = "muted" style = "margin:.3rem 0 0" > A snapshot of what this wallet holds on chain.< / p >
< div class = "stat-grid" >
< div class = "stat" >
< div class = "lbl" > Names held< / div >
< div class = "val" id = "stat-names" > 0< / div >
< div class = "sub" > CashTokens NFT certificates< / div >
< / div >
< div class = "stat" >
< div class = "lbl" > TLDs owned< / div >
< div class = "val" id = "stat-tlds" > 0< / div >
< div class = "sub" > Per-TLD registry NFTs< / div >
< / div >
< div class = "stat" >
< div class = "lbl" > Wallet balance< / div >
< div class = "val" id = "stat-balance" > —< / div >
< div class = "sub" id = "stat-balance-sub" > chipnet sats< / div >
< / div >
< div class = "stat" >
< div class = "lbl" > Chain height< / div >
< div class = "val" id = "stat-height" > —< / div >
< div class = "sub" > Last seen block< / div >
< / div >
< / div >
< div class = "row" style = "margin-top:1.4rem" >
< a class = "btn acid small" href = "./#search-input" > + Register a name< / a >
< a class = "btn small" href = "./tld.html" > + Register a TLD< / a >
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 >
< / div >
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
<!-- NAMES -->
< div class = "pane" data-pane = "names" hidden >
< h2 style = "margin:0" > Names you hold< / h2 >
< p class = "muted" style = "margin:.3rem 0 1rem" > Every BCNR name your wallet's addresses currently hold a CashTokens NFT for. Edit records inline to publish an on-chain UPD.< / p >
< div class = "status" id = "names-status" > Loading names from the chain…< / div >
< div id = "names-list" > < / div >
< / div >
<!-- TLDs -->
< div class = "pane" data-pane = "tlds" hidden >
< h2 style = "margin:0" > TLDs you own< / h2 >
< p class = "muted" style = "margin:.3rem 0 1rem" > Every per-TLD registry NFT held by this wallet. Set the < b > price< / b > every name under your TLD sells for, switch the TLD < b > on or off< / b > in the public registry (off = a < b > private TLD< / b > : nobody else can buy names under it, you still can from here), and tune policy — < b > fee< / b > , < b > min length< / b > , < b > reserved labels< / b > , < b > renewal period< / b > . Every change is one signed TUPD on chain; you receive 90% of each sale.< / p >
< div class = "status" id = "tlds-status" > Loading TLDs from the chain…< / div >
< div id = "tlds-list" > < / div >
< h2 style = "margin-top:2rem" > Register a new TLD< / h2 >
< div class = "card" id = "tld-register-card" >
< 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 >
2026-08-29 21:57:33 +02:00
< / div >
< / div >
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
<!-- WALLET -->
< div class = "pane" data-pane = "wallet" hidden >
< h2 style = "margin:0" > Wallet< / h2 >
< p class = "muted" style = "margin:.3rem 0 1rem" > Your addresses and current balance. Keys stay in this browser.< / p >
< div class = "card" >
< label > Receive address < span class = "dim" style = "margin-left:6px" > chipnet< / span > < / label >
< div > < code id = "me-addr" > —< / code > < / div >
< label style = "margin-top:14px" > Token address < span class = "dim" style = "margin-left:6px" > holds your name certificates< / span > < / label >
< div > < code id = "me-taddr" > —< / code > < / div >
< div class = "row" style = "justify-content:space-between" >
< span class = "dim" id = "me-info" > —< / span >
< a class = "btn ghost small" href = "https://chipnet.imaginary.cash/faucet" target = "_blank" rel = "noopener" > Faucet →< / a >
< / div >
< / div >
< / div >
<!-- SETTINGS -->
< div class = "pane" data-pane = "settings" hidden >
< h2 style = "margin:0" > Settings< / h2 >
< p class = "muted" style = "margin:.3rem 0 1rem" > Reader preferences live per-browser; wallet changes require a signature.< / p >
< div class = "card" >
< label > Reading font< / label >
< p class = "dim" style = "margin:0 0 8px;font-size:12.5px" > Fraunces (Silent Mode family) or Ubuntu (Bitcoin Cash brand font). The pill lives in the top nav.< / p >
< label style = "margin-top:14px" > Session PIN< / label >
< p class = "dim" style = "margin:0 0 8px;font-size:12.5px" > PIN unlocks the wallet without re-typing your password. Wipes after 3 wrong attempts.< / p >
< label style = "margin-top:14px" > Sign out< / label >
< p class = "dim" style = "margin:0 0 10px;font-size:12.5px" > Clears the signed-in state in this browser. Your on-chain names are unaffected.< / p >
< div class = "row" >
< button class = "btn ghost small" id = "signout-btn" > Sign out< / button >
< / div >
< / div >
< / div >
2026-08-29 21:57:33 +02:00
< / 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 >
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
<!-- DNS - record editor modal — composes a signed records manifest v1 and
POSTs to /api/records/< name > . No chain tx per change; the manifest
is verified against the on-chain NFT owner via signature. See
docs/#signed-records for the story, DESIGN-signed-records-manifest.md
for the wire format. -->
< div class = "modal" id = "dns-editor" >
< div class = "sheet" >
< button class = "x" id = "dns-ed-close" title = "close" > × < / button >
< h3 > DNS records for < span id = "dns-ed-name" style = "font-family:var(--mono);color:var(--acid)" > —< / span > < / h3 >
< p class = "sub" > DNS-style records live in a signed manifest on Sia — not on chain.
< b > Free per edit< / b > , no size cap. The signature proves you own the name; the
gateway rejects anything not signed by the current NFT holder. See
< a href = "./docs/#signed-records" target = "_blank" > docs< / a > .< / p >
< label > A < span class = "field-hint" > IPv4 addresses, one per line< / span > < / label >
< textarea id = "dns-ed-A" placeholder = "1.2.3.4" > < / textarea >
< label > AAAA < span class = "field-hint" > IPv6 addresses, one per line< / span > < / label >
< textarea id = "dns-ed-AAAA" placeholder = "2001:db8::1" > < / textarea >
< label > MX < span class = "field-hint" > one per line: "priority hostname" (e.g. "10 mail.example.com")< / span > < / label >
< textarea id = "dns-ed-MX" placeholder = "10 mail.example.com" > < / textarea >
< label > TXT < span class = "field-hint" > one string per line (SPF, DKIM, verification tokens…)< / span > < / label >
< textarea id = "dns-ed-TXT" placeholder = "v=spf1 include:_spf.silentmode.st -all" > < / textarea >
< label > CNAME < span class = "field-hint" > one target host, or blank< / span > < / label >
< input type = "text" id = "dns-ed-CNAME" placeholder = "target.example.com" >
< label > NS < span class = "field-hint" > nameservers, one per line< / span > < / label >
< textarea id = "dns-ed-NS" placeholder = "ns1.example.com" > < / textarea >
< div class = "steps-log" id = "dns-ed-log" > < / div >
< div class = "msg" id = "dns-ed-msg" > < / div >
< div class = "actions" >
< button class = "btn ghost" id = "dns-ed-cancel" > Cancel< / button >
< button class = "btn acid" id = "dns-ed-save" > Sign & publish →< / button >
< / div >
< / div >
< / div >
<!-- TLD - policy editor modal — mirrors the record editor but signs a TUPD
against the TREG NFT for the selected TLD. Fields map to the policy
record schema described in DESIGN-tld-registry.md. -->
< div class = "modal" id = "tld-editor" >
< div class = "sheet" >
< button class = "x" id = "tld-ed-close" title = "close" > × < / button >
< h3 > Policy for < span id = "tld-ed-name" style = "color:var(--acid);font-family:var(--mono)" > .—< / span > < / h3 >
< p class = "sub" > Set policy for names registered under this TLD. Only your key can sign this TUPD;
the transaction fee is a few cents in chain dust. Leave a field blank to omit that policy
(defaults apply — open policy, tier pricing).< / p >
< label > price < span class = "field-hint" > USD per name — flat, shown to every buyer; blank = length tiers. You receive 90% of each sale.< / span > < / label >
< input type = "text" id = "tld-ed-price" placeholder = "7" inputmode = "decimal" >
< label > listing < span class = "field-hint" > off hides the TLD from search and blocks new registrations until you switch it back on< / span > < / label >
< select id = "tld-ed-hidden" style = "width:100%;box-sizing:border-box;padding:10px 12px;border-radius:10px;background:var(--panel,#141a24);color:var(--ink,#e7eaf1);border:1px solid var(--line,rgba(255,255,255,.12));font:inherit;margin:4px 0 12px" >
< option value = "" > On — listed, names for sale< / option >
< option value = "1" > Off — hidden, not for sale< / option >
< / select >
< label > min_len < span class = "field-hint" > reject names shorter than N chars< / span > < / label >
< input type = "text" id = "tld-ed-minlen" placeholder = "1" >
< label > fee_bps < span class = "field-hint" > service fee owner takes, in basis points (500 = 5%)< / span > < / label >
< input type = "text" id = "tld-ed-feebps" placeholder = "500" >
< label > policy < span class = "field-hint" > open · frozen · reserved-only< / span > < / label >
< input type = "text" id = "tld-ed-policy" placeholder = "open" >
< label > reserved < span class = "field-hint" > space-separated labels only owner can mint< / span > < / label >
< input type = "text" id = "tld-ed-reserved" placeholder = "admin root bank" >
< label > renewal_period < span class = "field-hint" > days between renewals, 0 = never expire< / span > < / label >
< input type = "text" id = "tld-ed-renewal" placeholder = "0" >
< label > renewal_fee < span class = "field-hint" > renewal price in sats, 0 = free< / span > < / label >
< input type = "text" id = "tld-ed-renewalfee" placeholder = "0" >
< label > site < span class = "field-hint" > where the TLD's own landing lives< / span > < / label >
< input type = "text" id = "tld-ed-site" placeholder = "https://silentmode.st/…" >
< div class = "budget" id = "tld-ed-budget" > payload: < b > < span id = "tld-ed-budget-used" > 0< / span > < / b > / < span id = "tld-ed-budget-max" > ?< / span > bytes< / div >
< div class = "steps-log" id = "tld-ed-log" > < / div >
< div class = "msg" id = "tld-ed-msg" > < / div >
< div class = "actions" >
< button class = "btn ghost" id = "tld-ed-cancel" > Cancel< / button >
< button class = "btn acid" id = "tld-ed-save" > Save policy →< / button >
< / div >
< / div >
< / div >
feat(sirius-x): footer everywhere, redesigned wallet button, dropdown auto-acts
Three UX fixes:
1. Beautiful footer on every page. Extracted the 4-column landing footer
into js/site-footer.js — reads the nav brand link to derive per-page
base ('./' at root, '../' in subdirs) so links resolve from any depth
without duplicating the HTML across 8 pages. Every page now ships an
empty <footer id="site-footer"></footer> and includes the injector;
inline footer + its CSS block removed from landing too so the source
stays in one place.
2. Wallet button redesign. Pill now shows a state dot (dim when not
signed in, green with subtle glow when signed in), a monospace short
address label when signed in ('qra6rs…7yl2') OR 'Wallet' when not,
a WC/seed badge on signed-in state, and a subtle caret. Full border
and hover states, proper aria-expanded/aria-label wiring. Dropdown
menu itself upgraded: grid layout per item (icon column + title +
hint), heavier backdrop blur, larger min-width, clearer typography.
3. Dropdown options actually do something. Removed the redundant 'Open
sign-in page →' item — the other three all land on portal too, so
listing 'open the page' as a fourth option was noise. On portal
arrival, the ?mode= handler now AUTO-TRIGGERS the primary action for
each mode:
new -> click Generate a phrase (12 words appear immediately)
import -> focus the seed textarea (cursor ready to paste)
wc -> click Connect wallet (WC session starts, URI shown)
No extra clicks between dropdown choice and the flow it names.
Cache-buster on the profile-menu.js include bumped to
?v=20260907wallet so cached copies pick up the new design; footer
injector at ?v=20260907rel.
2026-09-07 22:00:21 +02:00
< footer id = "site-footer" > < / footer >
2026-08-29 21:57:33 +02:00
< 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.
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260916dns";
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;
2026-09-07 00:15:43 +02:00
// Persist signed-in state so the profile dropdown (profile-menu.js) can flip
// from "onboarding" to "signed in" and any tab on the same origin (sirius.x)
// reflects the current session. The wallet keys themselves never leave this
// page — localStorage only holds a small address-only breadcrumb, no seed.
function writeProfile(w) {
try {
localStorage.setItem("siriusProfile", JSON.stringify({
address: w.address,
tokenAddress: w.tokenAddress,
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
source: w.source ?? "seed", // "seed" | "wc"
2026-09-07 00:15:43 +02:00
signedInAt: Date.now(),
}));
} catch {}
// profile-menu.js listens for this to re-render the nav dropdown in the
// same tab (the "storage" event only fires in *other* tabs).
window.dispatchEvent(new Event("siriusProfileChanged"));
}
function clearProfile() {
try { localStorage.removeItem("siriusProfile"); } catch {}
window.dispatchEvent(new Event("siriusProfileChanged"));
}
2026-09-08 02:35:56 +02:00
// ---------- sign in (via shared modal) ----------
// The old inline cards are gone; portal.html now delegates the whole wallet
// UX to the shared register-flow.js modal (window.siriusSignInWallet). This
// keeps portal in lockstep with the landing, tld page, and the wallet
// dropdown — one tabbed modal, everywhere.
//
// Signed-in state is picked up from siriusProfile + window.siriusWallet:
// - profile only (returning user with a saved wallet, no live wallet in
// memory) -> auto-open the modal at the Unlock tab
// - profile + wallet -> call enterPortal() and show the names list
// - no profile -> stay on the launcher card
async function adoptWalletFromModal() {
const profile = readProfile();
if (!profile) return false;
if (window.siriusWallet) {
wallet = window.siriusWallet;
await enterPortal();
return true;
2026-08-29 21:57:33 +02:00
}
feat(sirius-x/signin): PIN escrow — encrypted-at-rest quick unlock, 3-strike password fallback
Removes the sessionStorage plaintext-mnemonic cache (fixed under the
same commit) and replaces it with a PIN-encrypted blob in localStorage.
No plaintext secret ever touches disk or memory outside the live
BuiltInWallet object.
New js/pin-escrow.js — WebCrypto PBKDF2(50k) + AES-GCM(256). Public
API on window.siriusPin: savePinBlob(mnemonic, pin), tryUnlock(pin),
hasPin(), attemptsUsed(), attemptsRemaining(), clear(), MAX_ATTEMPTS.
Iteration count is lighter than BuiltInWallet's 250k because a 4-6
digit PIN's key space is small anyway; the point is 'not plaintext at
rest,' not brute-force resistance — the durable secret is the full
password.
register-flow.js:
- After a fresh password unlock (Import / Create / Unlock), stepSetPin
offers a 4-6 digit PIN with confirm — skippable with 'Not now'.
Never overwrites an existing PIN blob.
- stepUnlock now shows a numeric PIN pad when a PIN blob is present;
the password field only appears when the user opts to 'Use password
instead' or after the blob was wiped.
- Wrong PIN → increment counter, surface 'N attempts left'. Third
wrong PIN → wipe blob and route to a 'PIN reset' screen that hands
off to the password form.
- Correct PIN → decrypt the mnemonic in-browser, rebuild the
BuiltInWallet, finishSignIn(). Attempt counter resets to 0.
profile-menu.js:
- Sign-out clears the PIN blob (via siriusPin.clear()) alongside the
siriusProfile so the device isn't quick-unlockable with a stale PIN.
All pages that host the sign-in flow now include pin-escrow.js. Same
tabbed layout in stepUnlock — the PIN pad and the password field both
live under 🔓 Unlock, transparent tab-switch works exactly as before.
Verified live:
- Fresh Import → 'Set a PIN' step → 4242 confirmed → blob written
- Reload → PIN pad, 3 attempts remaining
- Correct PIN 4242 → signed in, counter resets to 0
- 3 wrong PIN attempts → 'Wrong PIN — N attempts left' per attempt,
then 'PIN locked — enter your full password to continue', blob
wiped, next reload shows the password form
- localStorage contains only ciphertext + salt + iv + counter; no
plaintext mnemonic anywhere on disk or in sessionStorage.
2026-09-09 01:31:18 +02:00
// No live wallet in memory — the mnemonic is NEVER cached in plaintext.
// Fall through so the caller shows the Unlock tab; PIN-encrypted blob
// (if the user set a PIN earlier) or the full password decrypts locally
// in-browser.
2026-09-08 02:35:56 +02:00
return false;
}
function readProfile() {
try { return JSON.parse(localStorage.getItem("siriusProfile") || "null"); } catch { return null; }
}
2026-09-08 18:30:52 +02:00
async function ensureSignInFlow() {
if (typeof window.siriusRenderSignInInline !== "function") {
2026-09-08 02:35:56 +02:00
// register-flow.js hasn't landed yet — wait briefly.
2026-09-08 18:30:52 +02:00
for (let i = 0; i < 60 & & typeof window . siriusRenderSignInInline ! = = " function " ; i + + ) {
2026-09-08 02:35:56 +02:00
await new Promise((r) => setTimeout(r, 50));
}
}
2026-09-08 18:30:52 +02:00
return typeof window.siriusRenderSignInInline === "function";
2026-09-08 02:35:56 +02:00
}
2026-09-08 18:30:52 +02:00
// Paint the tabbed sign-in as the page's main content. The user picks a tab
// (Unlock / Create / Import / WizardConnect) and the form for that path
// renders in place — no modal, no launcher.
async function paintInlineSignIn(mode) {
if (!(await ensureSignInFlow())) return;
const hasSaved = (() => { try { return !!localStorage.getItem("bns.wallet.v1"); } catch { return false; } })();
const startTab = mode || (hasSaved ? "unlock" : "import");
window.siriusRenderSignInInline($("signin-inline"), startTab);
}
2026-09-08 02:35:56 +02:00
// Re-check on every profile change (fired by register-flow after finishSignIn
// and by profile-menu after sign-out). Same-tab and cross-tab both fire this.
window.addEventListener("siriusProfileChanged", async () => {
if (readProfile()) { await adoptWalletFromModal(); }
else { signOutUi(); }
2026-09-07 00:15:43 +02:00
});
2026-09-08 02:35:56 +02:00
window.addEventListener("storage", async (e) => {
if (e.key !== "siriusProfile") return;
if (readProfile()) { await adoptWalletFromModal(); }
else { signOutUi(); }
});
2026-09-08 18:30:52 +02:00
async function signOutUi() {
2026-09-08 02:35:56 +02:00
wallet = null;
window.siriusWallet = null;
$("me").classList.add("hidden");
$("signin").classList.remove("hidden");
$("names-list").innerHTML = "";
$("names-status").textContent = "Loading names from the chain…";
2026-09-08 18:30:52 +02:00
// Re-paint the sign-in tabs so a returning user can go straight to Unlock
// (or Import if no saved wallet).
await paintInlineSignIn();
2026-09-08 02:35:56 +02:00
}
2026-09-07 00:15:43 +02:00
2026-09-08 02:35:56 +02:00
// (old inline seed/create/wc handlers deleted — see the modal launcher above)
/* HANDLERS_REMOVED_MARKER
2026-09-07 00:15:43 +02:00
$("new-btn").addEventListener("click", async () => {
const btn = $("new-btn");
const errBox = $("new-err");
errBox.classList.add("hidden");
btn.disabled = true; btn.textContent = "Generating…";
let fresh;
try { fresh = await BNS.BuiltInWallet.create(); }
catch (e) {
errBox.textContent = "Could not generate a phrase: " + (e.message || e);
errBox.classList.remove("hidden");
btn.disabled = false; btn.textContent = "Generate a phrase";
return;
}
wallet = fresh;
$("new-seed").value = fresh.mnemonic;
$("new-phrase").classList.remove("hidden");
btn.disabled = true; btn.textContent = "Phrase generated";
});
$("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", async () => {
if (!wallet) return;
// Wipe the visible phrase immediately — it's in the wallet object now.
$("new-seed").value = "";
$("new-phrase").classList.add("hidden");
writeProfile(wallet);
2026-08-29 21:57:33 +02:00
await enterPortal();
});
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
// ---------- WizardConnect ----------
// Ports the WC flow from register.html into a sign-in surface. The portal
// doesn't need to mint — it only reads holdings and signs UPDs — so this is
// simpler than the register flow: open a session, show the URI/QR, wait for
// approval, wrap the returned addresses in a wallet-shaped object with a
// `source: "wc"` flag so downstream code (record editor, sign-out) can
// dispatch to the external-wallet path.
$("wc-btn").addEventListener("click", async () => {
const btn = $("wc-btn");
const errBox = $("wc-err");
const 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,
// libauth is only bundled once (in the main bundle); wizardconnect.js
// needs these pubkey/hash helpers passed through.
crypto: {
pubkeyToAddress: BNS.pubkeyToAddress,
pubkeyToLockingBytecode: BNS.pubkeyToLockingBytecode,
binToHex: BNS.binToHex,
txidOfHex: BNS.txidOfHex,
},
});
} catch (e) {
errBox.textContent = "Could not start WizardConnect: " + (e.message || e);
errBox.classList.remove("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
return;
}
const uri = session.qrUri ?? session.uri;
$("wc-uri").textContent = 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;
}
let addresses;
try { addresses = await session.getAddresses(); }
catch (e) {
errBox.textContent = "Wallet returned no addresses: " + (e.message || e);
errBox.classList.remove("hidden");
uriBox.classList.add("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
try { await session.disconnect(); } catch {}
return;
}
if (!addresses?.length) {
errBox.textContent = "The connected wallet exposed no addresses.";
errBox.classList.remove("hidden");
uriBox.classList.add("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
try { await session.disconnect(); } catch {}
return;
}
// Wallet-shaped wrapper — enough for holdings lookup and dispatching the
// WC path in the record editor. External wallets don't expose keyFor(),
// so any codepath that needs it (TLD mint via built-in path) is guarded
// by `wallet.source !== "wc"`.
wallet = {
source: "wc",
address: addresses[0],
tokenAddress: BNS.toTokenAddress(addresses[0]),
watchedAddresses: addresses,
session,
};
writeProfile(wallet);
uriBox.classList.add("hidden");
btn.disabled = false; btn.textContent = "Connect wallet";
await enterPortal();
});
$("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);
});
2026-09-08 02:35:56 +02:00
HANDLERS_REMOVED_MARKER */
// Signed-in section's own sign-out button. Delegates the profile+wallet
// teardown to the shared modal's signOutUi() (already declared above).
$("signout-btn")?.addEventListener("click", async () => {
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
if (wallet?.source === "wc" & & wallet.session) {
try { await wallet.session.disconnect(); } catch {}
}
2026-09-07 00:15:43 +02:00
clearProfile();
2026-09-08 02:35:56 +02:00
signOutUi();
2026-08-29 21:57:33 +02:00
});
2026-09-07 00:15:43 +02:00
// ---------- ?mode= deep-link ----------
2026-09-08 18:30:52 +02:00
// portal.html?mode=new|import|wc|unlock opens the sign-in section on that
// tab. Sign-in is INLINE — the tabbed UI is the page, no modal.
(async function bootstrap() {
2026-09-08 02:35:56 +02:00
const profile = readProfile();
2026-09-08 18:30:52 +02:00
const mode = new URLSearchParams(location.search).get("mode");
if (profile) {
// Signed-in section takes over when we have a live wallet; otherwise
// paint the sign-in section at the Unlock tab so a returning user just
// enters their password.
const adopted = await adoptWalletFromModal();
if (adopted) return;
await paintInlineSignIn(mode || "unlock");
return;
2026-09-08 02:35:56 +02:00
}
2026-09-08 18:30:52 +02:00
await paintInlineSignIn(mode);
2026-09-07 00:15:43 +02:00
})();
2026-08-29 21:57:33 +02:00
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(); }
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
wireProfileTabs();
2026-08-29 21:57:33 +02:00
await loadNames();
}
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
// ---------- profile section tabs ----------
// Horizontal tab bar drives which .pane is visible. Persists the active
// tab in the URL hash so a refresh/link keeps context (`portal.html#tab=tlds`).
function wireProfileTabs() {
const buttons = document.querySelectorAll(".profile-tabs [role=tab]");
const panes = document.querySelectorAll(".pane[data-pane]");
function activate(name) {
let matched = false;
buttons.forEach((b) => {
const on = b.dataset.pane === name;
b.classList.toggle("active", on);
if (on) matched = true;
});
panes.forEach((p) => { p.hidden = p.dataset.pane !== name; });
// Fall back to first tab if the hash points at something unknown.
if (!matched & & buttons.length) {
buttons[0].classList.add("active");
const first = buttons[0].dataset.pane;
panes.forEach((p) => { p.hidden = p.dataset.pane !== first; });
}
}
buttons.forEach((b) => b.addEventListener("click", () => {
const name = b.dataset.pane;
activate(name);
// Update hash without adding a history entry.
history.replaceState(null, "", `#tab=${name}`);
}));
// Boot from hash if present.
const m = /(?:^|#|& )tab=([a-z]+)/.exec(location.hash || "");
activate(m ? m[1] : "overview");
}
2026-08-29 21:57:33 +02:00
// ---------- 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)}`);
}
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
const payload = await r.json();
const { count, names: mine, tlds = [], tld_count = 0, wallet: walletInfo, chain } = payload;
// Overview stats — always safe to update (elements exist when signed in).
const setText = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
setText("stat-names", count);
setText("tab-names-count", count);
setText("stat-tlds", tld_count);
setText("tab-tlds-count", tld_count);
if (walletInfo?.sats != null) {
const sats = BigInt(walletInfo.sats);
const fmt = sats.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
setText("stat-balance", fmt);
setText("stat-balance-sub", `sat · ${walletInfo.utxo_count} UTXO${walletInfo.utxo_count === 1 ? "" : "s"}`);
}
if (chain?.height != null) setText("stat-height", chain.height.toLocaleString("en-US"));
// Render TLDs list in its own pane.
renderTlds(tlds);
2026-08-31 05:40:47 +02:00
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 > ` +
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
`< button class = "btn small ghost" data-dns = "${esc(e.name)}" title = "Off-chain DNS records — free per edit" > DNS< / button > ` +
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") || "{}"));
});
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
list.querySelectorAll("[data-dns]").forEach((btn) => {
btn.onclick = () => openDnsEditor(btn.getAttribute("data-dns"));
});
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
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
// ---------- TLDs pane ----------
// Renders the "TLDs you own" list. Each row shows the TLD label, its
// on-chain policy fields (records), and an Edit-policy button — that
// button's wire-up lands with the TUPD editor in a follow-up. For now
// the button is disabled with a "coming soon" hint so the shape of
// the UI is honest about what works today.
function renderTlds(tlds) {
const status = document.getElementById("tlds-status");
const list = document.getElementById("tlds-list");
if (!list || !status) return;
list.innerHTML = "";
if (!tlds.length) {
status.className = "status";
status.innerHTML = "This wallet does not hold any TLD certificates yet. Mint one on < a href = \"./tld.html\" > the TLD page< / a > — every name registered under it earns you 90% of the service fee.";
return;
}
status.className = "status ok";
status.textContent = `You own ${tlds.length} TLD${tlds.length === 1 ? "" : "s"}.`;
for (const t of tlds) {
const div = document.createElement("div");
div.className = "name-row";
const records = t.records & & typeof t.records === "object" ? t.records : {};
const off = isTldHidden(records);
const price = tldPriceOf(records);
const fmt = window.siriusPricing?.formatUsd || ((n) => "$" + n);
const priceBadge = price != null
? `< span class = "badge b-x" title = "flat price you set — every name under .${esc(t.tld)} sells for this" > ${esc(fmt(price))} / name< / span > `
: '< span class = "badge b-x" title = "no owner price set — buyers pay the length tier" > tier pricing< / span > ';
const stateBadge = off
? '< span class = "badge" style = "background:rgba(255,120,120,.16);color:#ff9a9a" title = "hidden from the public registry; no new names until you switch it on" > off< / span > '
: '< span class = "badge" style = "background:rgba(120,255,160,.14);color:#8df0b0" title = "listed in the public registry" > on< / span > ';
const rec = Object.keys(records).length
? Object.entries(records).map(([k, v]) => `< span class = "k" > ${esc(k)}=< / span > ${esc(String(v).slice(0, 80))}`).join(" · ")
: '< span class = "k" > (default policy — open, tier pricing)< / span > ';
const recJson = esc(JSON.stringify(records));
div.innerHTML =
`< div class = "n" > .< b style = "color:var(--acid)" > ${esc(t.tld)}< / b > ${priceBadge} ${stateBadge}< / div > ` +
`< button class = "btn small ghost" data-toggle-tld = "${esc(t.tld)}" data-category = "${esc(t.category)}" data-records = '${recJson}' title = "${off ? " Switch on: list the TLD and sell names again " : " Switch off: hide the TLD , stop new registrations " } " > ${off ? "Switch on" : "Switch off"}< / button > ` +
`< button class = "btn small acid" data-edit-tld = "${esc(t.tld)}" data-category = "${esc(t.category)}" data-records = '${recJson}' > Price & policy< / button > ` +
`< div class = "rec" > ${rec}< div class = "tld-toggle-msg" style = "margin-top:4px" > < / div > ` +
// Owner registration — works whether the TLD is on or off. Off makes
// it a private TLD: the public cannot buy, the owner still can, at the
// platform's 10% share only (the register flow detects ownership).
`< form class = "owner-reg" data-owner-reg = "${esc(t.tld)}" style = "display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px" > ` +
`< span style = "color:var(--dim)" > Register a name under < b style = "color:var(--acid)" > .${esc(t.tld)}< / b > ${off ? " (private — only you can, while it is off)" : ""}:< / span > ` +
`< input type = "text" placeholder = "label" maxlength = "32" autocomplete = "off" spellcheck = "false" style = "width:160px;padding:6px 10px;border-radius:8px;background:var(--panel,#141a24);color:var(--ink,#e7eaf1);border:1px solid var(--line,rgba(255,255,255,.12));font:inherit" > ` +
`< span class = "mono" style = "color:var(--dim)" > .${esc(t.tld)}< / span > ` +
`< button type = "submit" class = "btn small" > Register →< / button > ` +
`< / form > < / div > `;
list.appendChild(div);
}
// Wire owner registration forms → shared mint flow (js/register-flow.js).
list.querySelectorAll("[data-owner-reg]").forEach((form) => {
form.onsubmit = (e) => {
e.preventDefault();
const tld = form.getAttribute("data-owner-reg");
const input = form.querySelector("input");
const label = String(input.value || "").toLowerCase().replace(/[^a-z0-9-]/g, "");
input.value = label;
if (!label) { input.focus(); return; }
if (typeof window.siriusRegisterName !== "function") { input.placeholder = "loading…"; return; }
window.siriusRegisterName(`${label}.${tld}`);
};
});
// Wire Price & policy buttons.
list.querySelectorAll("[data-edit-tld]").forEach((btn) => {
btn.onclick = () => openTldEditor(
btn.getAttribute("data-edit-tld"),
btn.getAttribute("data-category"),
JSON.parse(btn.getAttribute("data-records") || "{}"),
);
});
// Wire on/off toggles — one TUPD that re-publishes the current policy
// with `hidden` flipped. Everything else the owner set stays as is.
list.querySelectorAll("[data-toggle-tld]").forEach((btn) => {
btn.onclick = () => toggleTldListing(
btn,
btn.getAttribute("data-toggle-tld"),
btn.getAttribute("data-category"),
JSON.parse(btn.getAttribute("data-records") || "{}"),
);
});
}
// Policy helpers shared by the TLD list, the editor and the on/off toggle.
// `hidden: 1` on chain means "off"; absence means "on". `price` is USD per
// name; absence means tier pricing. Kept in lockstep with the gateway's
// /api/tlds reading (public-gateway.mjs) and js/pricing.js.
function isTldHidden(records) {
const h = records?.hidden;
return h === 1 || h === true || h === "1";
}
function tldPriceOf(records) {
const p = records?.price;
return typeof p === "number" & & Number.isFinite(p) & & p >= 0 ? p : null;
}
async function toggleTldListing(btn, tld, category, records) {
if (!wallet) return;
const row = btn.closest(".name-row");
const out = row?.querySelector(".tld-toggle-msg");
const say = (t, cls) => { if (out) { out.textContent = t; out.style.color = cls === "err" ? "#ff9a9a" : cls === "ok" ? "#8df0b0" : "var(--dim)"; } };
if (wallet.source === "wc") {
say("External-wallet (WizardConnect) TUPD lands in the next drop — please sign in with the built-in wallet.", "err");
return;
}
const next = { ...records };
const turningOff = !isTldHidden(records);
if (turningOff) next.hidden = 1; else delete next.hidden;
const orig = btn.textContent;
btn.disabled = true;
btn.textContent = "Signing…";
let el = null;
try {
el = await BNS.connect();
say(turningOff ? `Switching .${tld} off…` : `Switching .${tld} on…`);
const res = await BNS.setTldRecordsWithBuiltInWallet(el, {
wallet, tld, records: next, category,
onProgress: (step) => say(String(step)),
});
say(`${turningOff ? "Off" : "On"} — broadcast ${res.txid ? res.txid.slice(0, 16) + "…" : ""}. The public registry picks it up within a minute.`, "ok");
btn.textContent = turningOff ? "Switch on" : "Switch off";
setTimeout(() => loadNames(), 4000);
} catch (e) {
say("Failed: " + (e.message || e), "err");
btn.textContent = orig;
} finally {
btn.disabled = false;
try { el?.close?.(); } catch {}
}
}
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");
feat(sirius-x): Sirius.X brand rebrand, banner star-divider, WC on portal
Three things from one review pass:
1. Rebrand text 'sirius.x' -> 'Sirius.X' as the brand style across all
sirius.x pages (titles, meta, nav wordmark, headings, prose, footer).
Also flipped the pantheon headings to Title.X form: Silentmode.X,
Sirius.X, Theseus.X, Hephaestus.X, Prometheus.X, Helios.X, Hermes.X.
Domain literals, URLs, and code samples stay lowercase (sirius.x is
the actual on-chain name; only prose changes). Logo/avatar SVG
wordmarks and aria-labels updated to match.
2. Banner (brand/banner.svg) redesigned around a centered layout:
'Sirius.X' wordmark above, the star's horizontal arm extended
edge-to-edge as a divider line, tagline lines below. Left tip at
(0,315), right tip at (1200,315) so the arm crosses the full 1200px
banner width; vertical body compact around center. Old layout had
the star crammed into the right half and the wordmark on the left.
3. WizardConnect wired up on portal.html:
- The 'Connect wallet' button now opens a real WC session via
wizardconnect.js, shows the WIZ:// URI (copyable) and waits for
the wallet to approve.
- Successful connection builds a wallet-shaped wrapper with
source: 'wc' and a WC session for signing; siriusProfile now
carries the source so the nav dropdown can differentiate.
- Record editor dispatches to setRecordsWithExternalWallet when
source === 'wc' (bundle already ships the adapter), else stays
on setRecordsWithBuiltInWallet.
- TLD Register submit is gated off for WC users with a tooltip —
no external-wallet variant of registerTld is exported yet, so
TLD mint still needs a built-in wallet.
- Sign-out disconnects the WC session before dropping the wallet.
- toTokenAddress used to derive the tokenAddress for WC users so
the 'Signed in' card shows both forms.
2026-09-07 01:35:35 +02:00
const res = wallet.source === "wc"
? await BNS.setRecordsWithExternalWallet(el, {
session: wallet.session, name: editorName, records,
onProgress: (step) => push(step),
})
: await BNS.setRecordsWithBuiltInWallet(el, {
wallet, name: editorName, records,
onProgress: (step) => push(step),
});
2026-08-31 21:44:20 +02:00
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
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
// ---------- DNS records editor (signed off-chain manifest) ----------
// The chain says who owns the name. The manifest we sign here says what
// the DNS records are. Resolvers verify both — the gateway ensures the
// signature matches the on-chain NFT owner before accepting the write to
// Sia. Zero chain tx per change, no size cap. See docs/#signed-records.
let dnsEditor = { name: null, currentSeq: -1 };
function linesToList(text) {
return String(text || "")
.split(/\r?\n/)
.map((s) => s.trim())
.filter(Boolean);
}
function listToLines(list) {
return (Array.isArray(list) ? list : []).join("\n");
}
function parseMx(text) {
// "10 mail.example.com" per line → [{pref: 10, host: "mail.example.com"}]
const out = [];
for (const line of linesToList(text)) {
const m = /^(\d+)\s+(\S.*)$/.exec(line);
if (!m) continue; // silently skip malformed rows — form is best-effort
out.push({ pref: Number(m[1]), host: m[2].trim() });
}
return out;
}
function mxToLines(list) {
return (Array.isArray(list) ? list : [])
.map((m) => `${m.pref} ${m.host}`)
.join("\n");
}
async function openDnsEditor(name) {
dnsEditor = { name, currentSeq: -1 };
$("dns-ed-name").textContent = name;
// Clear all fields, then GET current manifest (if any) to prefill and
// learn the current seq. On a first write, seq starts at 1.
for (const k of ["A", "AAAA", "MX", "TXT", "CNAME", "NS"]) $("dns-ed-" + k).value = "";
$("dns-ed-msg").textContent = ""; $("dns-ed-msg").className = "msg";
$("dns-ed-log").className = "steps-log"; $("dns-ed-log").innerHTML = "";
$("dns-ed-save").disabled = false;
$("dns-ed-save").textContent = "Sign & publish →";
$("dns-editor").classList.add("open");
try {
const r = await fetch(`https://silentmode.st/api/records/${encodeURIComponent(name)}`);
if (r.ok) {
const cur = await r.json();
dnsEditor.currentSeq = typeof cur.seq === "number" ? cur.seq : 0;
const dns = cur.dns || {};
$("dns-ed-A").value = listToLines(dns.A);
$("dns-ed-AAAA").value = listToLines(dns.AAAA);
$("dns-ed-MX").value = mxToLines(dns.MX);
$("dns-ed-TXT").value = listToLines(dns.TXT);
$("dns-ed-CNAME").value= dns.CNAME || "";
$("dns-ed-NS").value = listToLines(dns.NS);
} else if (r.status === 404) {
dnsEditor.currentSeq = 0; // fresh — first write will be seq 1
} else if (r.status === 409) {
// Name has no s3 pointer on chain — records-manifest has nowhere to live.
const j = await r.json().catch(() => ({}));
$("dns-ed-msg").className = "msg err";
$("dns-ed-msg").textContent = j.error || "This name has no s3 pointer on chain — set records.s3 first (Edit) so the manifest has a home.";
$("dns-ed-save").disabled = true;
} else {
throw new Error(`API ${r.status}`);
}
} catch (e) {
$("dns-ed-msg").className = "msg err";
$("dns-ed-msg").textContent = "Could not read current manifest: " + (e.message || e) + " (you can still publish a fresh one)";
dnsEditor.currentSeq = 0;
}
}
function closeDnsEditor() {
$("dns-editor").classList.remove("open");
dnsEditor = { name: null, currentSeq: -1 };
}
$("dns-ed-close").addEventListener("click", closeDnsEditor);
$("dns-ed-cancel").addEventListener("click", closeDnsEditor);
$("dns-editor").addEventListener("click", (e) => { if (e.target === $("dns-editor")) closeDnsEditor(); });
$("dns-ed-save").addEventListener("click", async () => {
if (!dnsEditor.name || !wallet) return;
if (wallet.source === "wc") {
$("dns-ed-msg").className = "msg err";
$("dns-ed-msg").textContent = "External-wallet signing (WizardConnect) for records manifests lands in the next drop — please sign in with the built-in wallet.";
return;
}
const msg = $("dns-ed-msg");
const log = $("dns-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";
$("dns-ed-save").disabled = true;
$("dns-ed-save").textContent = "Signing…";
try {
const cnameRaw = $("dns-ed-CNAME").value.trim();
const manifest = {
v: 1,
name: dnsEditor.name,
seq: (dnsEditor.currentSeq >= 0 ? dnsEditor.currentSeq : 0) + 1,
updated_at: new Date().toISOString(),
dns: {
A: linesToList($("dns-ed-A").value),
AAAA: linesToList($("dns-ed-AAAA").value),
MX: parseMx($("dns-ed-MX").value),
TXT: linesToList($("dns-ed-TXT").value),
CNAME: cnameRaw || null,
NS: linesToList($("dns-ed-NS").value),
},
};
push(`composed manifest · seq ${manifest.seq}`);
const signed = await BNS.signRecordsManifest(wallet, manifest);
push("signed with wallet key");
const r = await fetch(`https://silentmode.st/api/records/${encodeURIComponent(dnsEditor.name)}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(signed),
});
const j = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(`API ${r.status}: ${j.error || "unknown error"}`);
push(`published · ${j.bytes || "?"} bytes at ${j.sia_key}`);
dnsEditor.currentSeq = manifest.seq;
msg.className = "msg ok";
msg.textContent = `Saved manifest seq ${manifest.seq}. Resolvers will pick it up on their next fetch.`;
$("dns-ed-save").textContent = "Saved ✓";
setTimeout(() => { $("dns-ed-save").textContent = "Sign & publish →"; $("dns-ed-save").disabled = false; }, 1500);
} catch (e) {
msg.className = "msg err";
msg.textContent = "Failed: " + (e.message || e);
push("error: " + (e.message || e));
$("dns-ed-save").disabled = false;
$("dns-ed-save").textContent = "Sign & publish →";
}
});
// ---------- TLD policy editor (TUPD) ----------
// Same shape as the name editor above but drives BNS.setTldRecordsWithBuiltInWallet,
// which signs a TUPD against the TREG NFT. Fields are numbers/strings that get
// packed into the TLD's records object — resolvers and future registrar UIs
// read that to apply per-TLD pricing, reservation, and renewal policy.
const TLD_POLICY_FIELDS = [
{ key: "price", id: "tld-ed-price", parse: parseUsdOrNull },
{ key: "hidden", id: "tld-ed-hidden", parse: (v) => (String(v) === "1" ? 1 : null) },
{ key: "min_len", id: "tld-ed-minlen", parse: parseIntOrNull },
{ key: "fee_bps", id: "tld-ed-feebps", parse: parseIntOrNull },
{ key: "policy", id: "tld-ed-policy", parse: trimOrNull },
{ key: "reserved", id: "tld-ed-reserved", parse: trimOrNull },
{ key: "renewal_period", id: "tld-ed-renewal", parse: parseIntOrNull },
{ key: "renewal_fee", id: "tld-ed-renewalfee", parse: parseIntOrNull },
{ key: "site", id: "tld-ed-site", parse: trimOrNull },
];
function parseIntOrNull(v) { v = String(v).trim(); if (!v) return null; const n = Number(v); return Number.isFinite(n) & & n >= 0 ? Math.floor(n) : null; }
// USD with at most two decimals ("7", "7.5", "$0.25"); anything else = unset.
function parseUsdOrNull(v) { v = String(v).trim().replace(/^\$/, ""); if (!v) return null; const n = Number(v); return Number.isFinite(n) & & n >= 0 ? Math.round(n * 100) / 100 : null; }
function trimOrNull(v) { v = String(v).trim(); return v || null; }
let tldEditor = { tld: null, category: null };
function openTldEditor(tld, category, records) {
tldEditor = { tld, category };
$("tld-ed-name").textContent = "." + tld;
for (const f of TLD_POLICY_FIELDS) {
const cur = records?.[f.key];
$(f.id).value = cur == null ? "" : String(cur);
}
$("tld-ed-msg").textContent = "";
$("tld-ed-msg").className = "msg";
$("tld-ed-log").className = "steps-log";
$("tld-ed-log").innerHTML = "";
$("tld-ed-save").disabled = false;
$("tld-ed-save").textContent = "Save policy →";
updateTldBudget();
$("tld-editor").classList.add("open");
}
function closeTldEditor() {
$("tld-editor").classList.remove("open");
tldEditor = { tld: null, category: null };
}
$("tld-ed-close").addEventListener("click", closeTldEditor);
$("tld-ed-cancel").addEventListener("click", closeTldEditor);
$("tld-editor").addEventListener("click", (e) => { if (e.target === $("tld-editor")) closeTldEditor(); });
function collectTldRecords() {
const r = {};
for (const f of TLD_POLICY_FIELDS) {
const v = f.parse($(f.id).value);
if (v !== null & & v !== undefined & & v !== "") r[f.key] = v;
}
return r;
}
function updateTldBudget() {
if (!tldEditor.tld) return;
try {
// TUPD envelope is the same 200-byte OP_RETURN cap. payloadBudget knows
// the op letter — pass "TUPD" so the envelope size (op + tld) is right.
const max = BNS.payloadBudget(tldEditor.tld, "TUPD");
const used = new TextEncoder().encode(JSON.stringify(collectTldRecords())).length;
$("tld-ed-budget-max").textContent = max;
$("tld-ed-budget-used").textContent = used;
const over = used > max;
$("tld-ed-budget").classList.toggle("over", over);
$("tld-ed-save").disabled = over;
} catch { /* budget helper doesn't yet know TUPD — leave it un-updated */ }
}
for (const f of TLD_POLICY_FIELDS) $(f.id).addEventListener("input", updateTldBudget);
$("tld-ed-save").addEventListener("click", async () => {
if (!tldEditor.tld || !tldEditor.category || !wallet) return;
if (wallet.source === "wc") {
$("tld-ed-msg").className = "msg err";
$("tld-ed-msg").textContent = "External-wallet (WizardConnect) TUPD lands in the next drop — please sign in with the built-in wallet.";
return;
}
const records = collectTldRecords();
const msg = $("tld-ed-msg");
const log = $("tld-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";
$("tld-ed-save").disabled = true;
$("tld-ed-save").textContent = "Signing…";
let el = null;
try {
el = await BNS.connect();
push("connected to chipnet");
const res = await BNS.setTldRecordsWithBuiltInWallet(el, {
wallet, tld: tldEditor.tld, records, category: tldEditor.category,
onProgress: (step) => push(String(step)),
});
push("broadcast: " + (res.txid || "(no txid)"));
msg.className = "msg ok";
msg.textContent = "Policy saved. It'll reflect in registrations under this TLD within a block.";
$("tld-ed-save").textContent = "Saved ✓";
setTimeout(() => loadNames(), 2500);
} catch (e) {
msg.className = "msg err";
msg.textContent = "Failed: " + (e.message || e);
push("error: " + (e.message || e));
$("tld-ed-save").disabled = false;
$("tld-ed-save").textContent = "Save policy →";
} 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;
2026-09-07 01:39:16 +02:00
$("tld-submit").disabled = !valid || !wallet;
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-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");
2026-09-07 01:39:16 +02:00
const serviceFee = {
address: BNS.REGISTRAR?.serviceFee?.address ?? null,
sats,
};
const res = wallet.source === "wc"
? await BNS.registerTldWithExternalWallet(el, {
session: wallet.session,
tld: label,
records: {},
serviceFee,
tldListUrl: "https://navigate.st/api/tlds",
onProgress: (step) => push(step),
})
: await BNS.registerTldWithBuiltInWallet(el, {
wallet,
tld: label,
records: {},
serviceFee,
tldListUrl: "https://navigate.st/api/tlds",
onProgress: (step) => push(step),
});
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
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)";
fix(sirius-x): profile-menu URLs, landing search, empty-wallet hint, drop Pantheon
Five UX fixes from a review pass:
1. profile-menu.js: dropdown links (New/Add/WC wallet, Admin) were
absolute /sirius-x/portal.html paths. That's fine on silentmode.st
but under the BCNR route (sirius.x/) the origin is different, so
the gateway forwarded to Sia which returned 'NoSuchKey' XML. Now
derive the base URL from the nav's own .portal anchor href — which
is already set per page with the right relative path — so it works
from the root, from subdirs, and under any BCNR gateway. Admin URL
is derived from the same base.
2. Landing hero got a search input. Submitting it normalises the
label ([a-z0-9-], 63 chars) and redirects to register.html?q=<label>.
register.html now honours ?q= on load: prefills the input, triggers
the parallel multi-TLD lookup, and scrolls into view. Single source
of truth stays in register.html; the landing just hands it a query.
3. Portal TLD-mint error path now special-cases 'wallet is empty' and
shows a friendlier message with the wallet's address and links to
two chipnet faucets, so the fix is one click away instead of a
guess.
4. Removed 'Pantheon' from every page's top nav — it's a section on
the landing that anyone scrolling will discover, and keeping it in
the nav crowded the bar.
5. Cache-buster ?v=20260907rel on the profile-menu.js script include
across all 7 pages so browsers that cached the pre-fix version
pick up the new one on next load.
2026-09-07 20:32:14 +02:00
const raw = String(e.message || e);
// The most common failure by far: wallet has no chipnet coins. Turn the
// opaque error into a nudge toward a faucet.
if (/wallet is empty/i.test(raw)) {
msg.innerHTML = `Failed: wallet is empty. Fund < code style = "font-size:11.5px" > ${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);
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-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 >
feat(sirius-x/signin): PIN escrow — encrypted-at-rest quick unlock, 3-strike password fallback
Removes the sessionStorage plaintext-mnemonic cache (fixed under the
same commit) and replaces it with a PIN-encrypted blob in localStorage.
No plaintext secret ever touches disk or memory outside the live
BuiltInWallet object.
New js/pin-escrow.js — WebCrypto PBKDF2(50k) + AES-GCM(256). Public
API on window.siriusPin: savePinBlob(mnemonic, pin), tryUnlock(pin),
hasPin(), attemptsUsed(), attemptsRemaining(), clear(), MAX_ATTEMPTS.
Iteration count is lighter than BuiltInWallet's 250k because a 4-6
digit PIN's key space is small anyway; the point is 'not plaintext at
rest,' not brute-force resistance — the durable secret is the full
password.
register-flow.js:
- After a fresh password unlock (Import / Create / Unlock), stepSetPin
offers a 4-6 digit PIN with confirm — skippable with 'Not now'.
Never overwrites an existing PIN blob.
- stepUnlock now shows a numeric PIN pad when a PIN blob is present;
the password field only appears when the user opts to 'Use password
instead' or after the blob was wiped.
- Wrong PIN → increment counter, surface 'N attempts left'. Third
wrong PIN → wipe blob and route to a 'PIN reset' screen that hands
off to the password form.
- Correct PIN → decrypt the mnemonic in-browser, rebuild the
BuiltInWallet, finishSignIn(). Attempt counter resets to 0.
profile-menu.js:
- Sign-out clears the PIN blob (via siriusPin.clear()) alongside the
siriusProfile so the device isn't quick-unlockable with a stale PIN.
All pages that host the sign-in flow now include pin-escrow.js. Same
tabbed layout in stepUnlock — the PIN pad and the password field both
live under 🔓 Unlock, transparent tab-switch works exactly as before.
Verified live:
- Fresh Import → 'Set a PIN' step → 4242 confirmed → blob written
- Reload → PIN pad, 3 attempts remaining
- Correct PIN 4242 → signed in, counter resets to 0
- 3 wrong PIN attempts → 'Wrong PIN — N attempts left' per attempt,
then 'PIN locked — enter your full password to continue', blob
wiped, next reload shows the password form
- localStorage contains only ciphertext + salt + iv + counter; no
plaintext mnemonic anywhere on disk or in sessionStorage.
2026-09-09 01:31:18 +02:00
< script src = "./js/pin-escrow.js?v=20260909pin" > < / script >
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
< script src = "./js/pricing.js?v=20260916private" > < / script >
feat(sirius-x): Deviant sky palette + Fraunces/Ubuntu reader toggle
Design translated from the /design canvas review. Sirius.X now shares
Deviant's "signal" theme background (layered sky-rim/mid gradients over
#050810) and the Silent Mode family typography stack.
Typography:
Fraunces (opsz variable serif) headings h1/h2/h3
DM Sans body / UI
JetBrains Mono code, hashes, mono cells
Ubuntu opt-in Bitcoin Cash brand font,
swaps everything at :root when
<html data-font-cmp="ubuntu">
All via Google Fonts, one <link> per page.
Font picker (js/theme.js):
Small pill in the topnav (Fraunces default / Ubuntu alt) — persisted
to localStorage.sirius-font. Sets the data-font-cmp attribute; the
swap CSS lives in each page's <style> block so !important overrides
page-local font-family declarations at the necessary specificity.
Mono elements stay JetBrains Mono under both fonts.
Palette (cool sky vs old flat dark navy):
--bg #0b0e14 → #050810
--panel2 #18202c → #0a0f1c (matches Deviant --sky-mid)
--line rgba(255,255,255,.09) → .06
--ink #e7eaf1 → #f1f4fa
--mut #8b98a9 → #b8c2d4
--dim #5e6678 → #6a7488
new: --sky-rim #131a2a, --chain-native #0AC18E (BCH brand green,
reserved for chain-native badges/proofs like the Bitcoin.Cash
body copy)
Copy — "no renewals" softened everywhere it appeared. Renewals will
be a per-TLD policy set by each TLD owner; the flat claim was wrong
across the board. Landing card, tld.html hero and body, plus the
og:description all updated.
Applied to: index.html, portal.html, tld.html, theseus/index.html,
brand/index.html, docs/index.html, admin/index.html.
2026-09-10 00:25:18 +02:00
< script defer src = "./js/theme.js?v=20260910font" > < / script >
feat(sirius-x): footer everywhere, redesigned wallet button, dropdown auto-acts
Three UX fixes:
1. Beautiful footer on every page. Extracted the 4-column landing footer
into js/site-footer.js — reads the nav brand link to derive per-page
base ('./' at root, '../' in subdirs) so links resolve from any depth
without duplicating the HTML across 8 pages. Every page now ships an
empty <footer id="site-footer"></footer> and includes the injector;
inline footer + its CSS block removed from landing too so the source
stays in one place.
2. Wallet button redesign. Pill now shows a state dot (dim when not
signed in, green with subtle glow when signed in), a monospace short
address label when signed in ('qra6rs…7yl2') OR 'Wallet' when not,
a WC/seed badge on signed-in state, and a subtle caret. Full border
and hover states, proper aria-expanded/aria-label wiring. Dropdown
menu itself upgraded: grid layout per item (icon column + title +
hint), heavier backdrop blur, larger min-width, clearer typography.
3. Dropdown options actually do something. Removed the redundant 'Open
sign-in page →' item — the other three all land on portal too, so
listing 'open the page' as a fourth option was noise. On portal
arrival, the ?mode= handler now AUTO-TRIGGERS the primary action for
each mode:
new -> click Generate a phrase (12 words appear immediately)
import -> focus the seed textarea (cursor ready to paste)
wc -> click Connect wallet (WC session starts, URI shown)
No extra clicks between dropdown choice and the flow it names.
Cache-buster on the profile-menu.js include bumped to
?v=20260907wallet so cached copies pick up the new design; footer
injector at ?v=20260907rel.
2026-09-07 22:00:21 +02:00
< script defer src = "./js/site-footer.js?v=20260907rel" > < / script >
feat(sirius-x): TLD owner panel — owner-set price, on/off switch, private TLDs
A TLD owner needed to run their namespace without the operator: set what
every name under the TLD sells for, take the TLD off the public registry
for a while, and still register names under it themselves.
Both settings live on chain in the TLD's TUPD records (`price`, `hidden`)
because changes are rare and every client already walks the TLD beacon.
The gateway's /api/tlds now carries records, owner, price_usd and
hidden_by per TLD; pricing.js quotes the owner price ahead of the length
tiers and only trusts a TLD_BEACON-sourced list; the register flow refuses
hidden and frozen TLDs for the public but lets the owner through at the
platform share only (the 90% owner cut would be paid to themselves). The
portal's TLDs tab loads real holdings, shows price and on/off state, and
gives each TLD a one-click switch, a price/policy editor and an inline
"register a name under .tld" form. Docs and the design table describe the
two new records.
2026-09-16 21:28:18 +02:00
< script type = "module" src = "./js/register-flow.js?v=20260916private" > < / script >
2026-09-08 02:19:41 +02:00
< script defer src = "./js/profile-menu.js?v=20260908tabs" > < / script >
2026-08-29 21:57:33 +02:00
< / body >
< / html >