From f2c3480d43b499e089d53875253459d6e0b86a1c Mon Sep 17 00:00:00 2001 From: Local Dev Date: Sun, 20 Sep 2026 19:20:30 +0200 Subject: [PATCH] sites: scroll-spy tracks scroll position, fires the last section on bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IntersectionObserver-based spy only fired when a section entered a narrow band 40–45% down the viewport. Short sections at the tail of a page — Operator key and Roadmap on theseus.x/extensions, whatever came last on the other pages — never made it into that band because the page runs out of scroll before their top crosses 40%, so their sidebar links stayed dim no matter how far the reader scrolled. Rewrote the spy across every page with a sidebar (theseus.x home / plug-ins / extensions and sirius.x home / tld / theseus / docs / admin / brand): it listens to window scroll, picks the last section whose top has crossed 35% of the viewport, and — when the window is scrolled to within 4px of the bottom — snaps to the final section regardless of where its top is. Every link in the rail can now activate. theseus.x/extensions also had the spy IIFE bundled with the manifest loader in one - - - - - - - - - - + + + + + +Admin — Sirius.X + + + + + + + + + + + + + + + +
+
🛠
+

Admin panel

+

Operator-side tools: mint new TLDs, browse the registry, verify chain health. + Sign in with the wallet the ecosystem knows as the operator; unfamiliar wallets get + read-only access to the registry.

+
+ +
+ +
+

Sign in

+

Chipnet-only. Keys stay in this browser tab; a mint transaction is signed + locally and broadcast direct to the electrum. Operator gate is client-side — the covenant + that enforces it on-chain is a mainnet requirement (Decentralized.DNS/DESIGN-tld-registry.md §5.2).

+
+ + +
+ + seed stays in memory only +
+ +
+
+ + + + + +
+

TLD registry

+

Every TLD registered on the beacon. Operators + (signed-in) can toggle a TLD's visibility on the public /api/tlds listing — hidden TLDs stop + appearing in the name-search UIs, but on-chain registrations under them keep resolving.

+
+ + +
+ + +
+
+
Loading current registry…
+
+
+ +
+ + + + + + + + + + + + + + diff --git a/brand/index.html b/brand/index.html index f004266..126619d 100644 --- a/brand/index.html +++ b/brand/index.html @@ -1,299 +1,310 @@ - - - - - -Brand — Sirius.X - - - - - - - - - - - - - - - - - - - - - - -
-
🎨
-

Sirius.X brand kit

-

Logo, wordmark, avatar, banner, favicon, and the colour palette. Every asset ships as - SVG so it stays crisp at any size and reads correctly in light and dark contexts. Free to reuse - with attribution.

-
- -
- - - -
-

Avatar & favicon

-

Square icons for social profiles, Nostr, GitHub, and browser tabs.

-
-
-
sirius.x avatar
-

Avatar SVG · 512×512

-

Square profile picture. Social platforms apply - their own round mask, so corners stay hard.

- -
-
-
sirius.x favicon
-

Favicon SVG · 64×64, 12px radius

-

Rounded browser-tab icon. Scales down to 16×16 - cleanly; used on every Sirius.X page.

- -
-
-
- - - -
-

Colour palette

-

Silent Mode family colours. Dark chromes are the default; acid green is the - single accent that consistently signals "this is Sirius.X".

-
-
Acid#d6ff3d — accent, links, active nav
-
Background#0b0e14 — page canvas
-
Panel#141a24 — cards, elevated surfaces
-
Panel 2#18202c — nested / step items
-
Ink#e7eaf1 — body text on dark
-
Muted#8b98a9 — secondary text
-
Dim#5e6678 — tertiary text, dividers
-
OK#4fd1a5 — success / available
-
Taken#f6768a — errors / unavailable
-
Warn#ffc75f — caution, holds
-
-
- -
-

Typography

-

System font stack, so nothing needs to load and every OS renders the wordmark in - the same family the reader is used to.

-
-

The quick brown fox

-

jumps over the lazy dog · 0123456789 · Sirius.X

-
system-ui, -apple-system, Segoe UI, Roboto, sans-serif
-
-
The wordmark's .x is a <tspan fill="#d6ff3d"> — - don't render the whole word in acid, and don't render the star without the star's exact 4-point - path (it's a mark, not a generic asterisk).
-
- -
-

Usage & licence

-

All assets are MIT. Reuse them for third-party tools that integrate with Sirius.X - (wallets, indexers, explorers, blog posts about BCNR). Keep the star's proportions intact and - don't recolour the acid to a similar-but-different green — palette drift is what makes brands - look untrustworthy over time.

-
    -
  • Do use the mark to indicate "supports Sirius.X" or "hosted on Sirius.X".
  • -
  • Do shrink to the favicon at 16×16 — the SVG scales cleanly.
  • -
  • Don't imply endorsement of a specific product without asking.
  • -
  • Don't stretch, skew, or re-tint the star.
  • -
-
- -
- - - - - - - - - - - + + + + + +Brand — Sirius.X + + + + + + + + + + + + + + + + + + + + + + +
+
🎨
+

Sirius.X brand kit

+

Logo, wordmark, avatar, banner, favicon, and the colour palette. Every asset ships as + SVG so it stays crisp at any size and reads correctly in light and dark contexts. Free to reuse + with attribution.

+
+ +
+ + + +
+

Avatar & favicon

+

Square icons for social profiles, Nostr, GitHub, and browser tabs.

+
+
+
sirius.x avatar
+

Avatar SVG · 512×512

+

Square profile picture. Social platforms apply + their own round mask, so corners stay hard.

+ +
+
+
sirius.x favicon
+

Favicon SVG · 64×64, 12px radius

+

Rounded browser-tab icon. Scales down to 16×16 + cleanly; used on every Sirius.X page.

+ +
+
+
+ + + +
+

Colour palette

+

Silent Mode family colours. Dark chromes are the default; acid green is the + single accent that consistently signals "this is Sirius.X".

+
+
Acid#d6ff3d — accent, links, active nav
+
Background#0b0e14 — page canvas
+
Panel#141a24 — cards, elevated surfaces
+
Panel 2#18202c — nested / step items
+
Ink#e7eaf1 — body text on dark
+
Muted#8b98a9 — secondary text
+
Dim#5e6678 — tertiary text, dividers
+
OK#4fd1a5 — success / available
+
Taken#f6768a — errors / unavailable
+
Warn#ffc75f — caution, holds
+
+
+ +
+

Typography

+

System font stack, so nothing needs to load and every OS renders the wordmark in + the same family the reader is used to.

+
+

The quick brown fox

+

jumps over the lazy dog · 0123456789 · Sirius.X

+
system-ui, -apple-system, Segoe UI, Roboto, sans-serif
+
+
The wordmark's .x is a <tspan fill="#d6ff3d"> — + don't render the whole word in acid, and don't render the star without the star's exact 4-point + path (it's a mark, not a generic asterisk).
+
+ +
+

Usage & licence

+

All assets are MIT. Reuse them for third-party tools that integrate with Sirius.X + (wallets, indexers, explorers, blog posts about BCNR). Keep the star's proportions intact and + don't recolour the acid to a similar-but-different green — palette drift is what makes brands + look untrustworthy over time.

+
    +
  • Do use the mark to indicate "supports Sirius.X" or "hosted on Sirius.X".
  • +
  • Do shrink to the favicon at 16×16 — the SVG scales cleanly.
  • +
  • Don't imply endorsement of a specific product without asking.
  • +
  • Don't stretch, skew, or re-tint the star.
  • +
+
+ +
+ + + + + + + + + + + diff --git a/docs/index.html b/docs/index.html index eca45ca..6058fd8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -125,9 +125,9 @@ .font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600} - + @@ -651,20 +651,31 @@ process.exit(0);" diff --git a/i18n/de.json b/i18n/de.json index ec68eb6..2e71c7a 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -609,5 +609,9 @@ "where the page files live (the s3 record)": "wo die Seitendateien liegen (der s3-Eintrag)", "Not hosted on Sia": "Nicht auf Sia gehostet", "Sirius.X hosting — free, on Sia storage": "Sirius.X-Hosting — kostenlos, auf Sia-Speicher", - "Another Sia folder (advanced)": "Anderer Sia-Ordner (erweitert)" + "Another Sia folder (advanced)": "Anderer Sia-Ordner (erweitert)", + "Price is fixed in": "Preis festgelegt in", + "US dollars — the BCH amount follows the market": "US-Dollar — der BCH-Betrag folgt dem Markt", + "BCH — the dollar value follows the market": "BCH — der Dollarwert folgt dem Markt", + "Accept between": "Akzeptiere zwischen" } diff --git a/i18n/el.json b/i18n/el.json index 5c11e5b..176bbb7 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -609,5 +609,9 @@ "where the page files live (the s3 record)": "πού βρίσκονται τα αρχεία της σελίδας (η εγγραφή s3)", "Not hosted on Sia": "Δεν φιλοξενείται στο Sia", "Sirius.X hosting — free, on Sia storage": "Φιλοξενία Sirius.X — δωρεάν, στον αποθηκευτικό χώρο Sia", - "Another Sia folder (advanced)": "Άλλος φάκελος Sia (για προχωρημένους)" + "Another Sia folder (advanced)": "Άλλος φάκελος Sia (για προχωρημένους)", + "Price is fixed in": "Η τιμή είναι σταθερή σε", + "US dollars — the BCH amount follows the market": "Δολάρια ΗΠΑ — το ποσό σε BCH ακολουθεί την αγορά", + "BCH — the dollar value follows the market": "BCH — η αξία σε δολάρια ακολουθεί την αγορά", + "Accept between": "Αποδοχή μεταξύ" } diff --git a/i18n/es.json b/i18n/es.json index c0e4a44..88a1e7f 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -609,5 +609,9 @@ "where the page files live (the s3 record)": "dónde viven los archivos de la página (el registro s3)", "Not hosted on Sia": "No alojado en Sia", "Sirius.X hosting — free, on Sia storage": "Alojamiento Sirius.X — gratis, en almacenamiento Sia", - "Another Sia folder (advanced)": "Otra carpeta de Sia (avanzado)" + "Another Sia folder (advanced)": "Otra carpeta de Sia (avanzado)", + "Price is fixed in": "El precio se fija en", + "US dollars — the BCH amount follows the market": "Dólares — la cantidad en BCH sigue al mercado", + "BCH — the dollar value follows the market": "BCH — el valor en dólares sigue al mercado", + "Accept between": "Aceptar entre" } diff --git a/i18n/fr.json b/i18n/fr.json index bd7797e..30415b8 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -609,5 +609,9 @@ "where the page files live (the s3 record)": "où se trouvent les fichiers de la page (l'enregistrement s3)", "Not hosted on Sia": "Pas hébergé sur Sia", "Sirius.X hosting — free, on Sia storage": "Hébergement Sirius.X — gratuit, sur le stockage Sia", - "Another Sia folder (advanced)": "Un autre dossier Sia (avancé)" + "Another Sia folder (advanced)": "Un autre dossier Sia (avancé)", + "Price is fixed in": "Le prix est fixé en", + "US dollars — the BCH amount follows the market": "Dollars US — le montant en BCH suit le marché", + "BCH — the dollar value follows the market": "BCH — la valeur en dollars suit le marché", + "Accept between": "Accepter entre" } diff --git a/i18n/pt.json b/i18n/pt.json index 90e72f7..620efde 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -609,5 +609,9 @@ "where the page files live (the s3 record)": "onde ficam os ficheiros da página (o registo s3)", "Not hosted on Sia": "Não alojado no Sia", "Sirius.X hosting — free, on Sia storage": "Alojamento Sirius.X — grátis, no armazenamento Sia", - "Another Sia folder (advanced)": "Outra pasta Sia (avançado)" + "Another Sia folder (advanced)": "Outra pasta Sia (avançado)", + "Price is fixed in": "O preço é fixado em", + "US dollars — the BCH amount follows the market": "Dólares — o montante em BCH segue o mercado", + "BCH — the dollar value follows the market": "BCH — o valor em dólares segue o mercado", + "Accept between": "Aceitar entre" } diff --git a/i18n/ru.json b/i18n/ru.json index 35ba2b2..c828fdb 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -609,5 +609,9 @@ "where the page files live (the s3 record)": "где лежат файлы страницы (запись s3)", "Not hosted on Sia": "Не размещено на Sia", "Sirius.X hosting — free, on Sia storage": "Хостинг Sirius.X — бесплатно, в хранилище Sia", - "Another Sia folder (advanced)": "Другая папка Sia (для опытных)" + "Another Sia folder (advanced)": "Другая папка Sia (для опытных)", + "Price is fixed in": "Цена зафиксирована в", + "US dollars — the BCH amount follows the market": "Доллары США — сумма в BCH следует за рынком", + "BCH — the dollar value follows the market": "BCH — долларовая стоимость следует за рынком", + "Accept between": "Принимать в диапазоне" } diff --git a/index.html b/index.html index acc56bb..3799d60 100644 --- a/index.html +++ b/index.html @@ -1,570 +1,580 @@ - - - - - -Sirius.X — the home for names on Bitcoin Cash - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Names on the chain.

-

Sirius.X is the entry point for BCNR — Bitcoin Cash Name Registry. - Register a top-level domain, register a name under one, look up records, - verify anything against the chain.

- -
- -
- One search checks the label across every TLD in the registry. Available names appear first. -
-
-
- - -
- - - - - -
- -
-

Try it

-

Three things you can do right now. The registrar and TLD-registry UIs live at - silentmode.st today; Sirius.X is the canonical home and will consolidate them here.

-
-
-

Register a name

-

Pick a label, search across every TLD in the registry at once, mint the name - certificate straight into your own wallet. Renewals are per-TLD policy set by - the owner — the price the TLD sets is what you pay.

- -
-
-

Register a TLD

-

A TLD is itself a certificate on the chain. Anyone can mint one — you become the registry - operator for every second-level name under it, set the price yourself, and earn 90 % of the - fee on every name registered under it. Renewals are per-TLD policy that you control.

- -
-
-

Look up & verify

-

Resolve any BCNR name, compare records across mirrors, and check the TLD registry snapshot - against the chain. Everything is publicly verifiable.

- -
-
-

🔑 Sign in — your names portal

-

Import your seed phrase or connect a wallet (Cashonize / Paytaca via WizardConnect) to see - every name you own, review records, and update them. Your keys never leave your browser.

- -
-
-

🧭 Theseus Navigator

-

The browser that speaks BCNR natively. Open hello.bch in the address bar — - it just resolves. TLS verified against on-chain fingerprints, no OS trust-store install.

- -
-
-
- -
-

Documentation

-

The full docs — TLD registry model, record types, resolver & gateway paths, - mainnet pricing, verification, and the operator runbook pointer — live on their own page so - they can grow without cluttering the landing.

- -
- -
-

Propose a new TLD

-

The per-TLD registry means minting a TLD is a real, paid action — not a namespace - land-grab. During the alpha, all TLD mints are operator-signed. To propose a TLD, get in touch - via the channels below. The design (mainnet) adds an on-chain covenant with countersignature + - tiered USD price floors: 1-char $100k, 2-char $50k, 3-char $15k, 4-char $5k, 5–8-char $1k, - 9+-char $250; one-time payment (not annual). See the design doc for the full model.

-
ICANN-adjacent TLDs are not open for public mint. A TLD registry is - only useful if it does not immediately collapse into an ICANN-shadow — the mainnet covenant - enforces this; on chipnet the operator's countersignature is the gate.
-
- -
-

The pantheon

-

Sirius.x is one of seven sibling .x names — the Silent Mode pantheon. - Each project is its own on-chain certificate; each site is reachable through any BCNR - resolver, plus the silentmode.st gateway for browsers without one.

-
-
-

⛓️ Silentmode.X

-

The project umbrella. Redirects to silentmode.st where every pillar - has its own landing page + download hub.

- -
-
-

Sirius.X you're here

-

Names on Bitcoin Cash — register, resolve, verify, manage. This site.

- -
-
-

🧭 Theseus.X

-

The browser that speaks BCNR natively — Chromium build with the resolver baked in, - no OS-level daemon or root-store install.

- -
-
-

🔥 Hephaestus.X

-

The forge. Forgejo instance for the ecosystem's git repos, backed by BCH wallet - auth. Sirius's own source lives here.

- -
-
-

🔮 Prometheus.X

-

Fire from the chain. The project's foresight / experimentation layer. - Redirects to silentmode.st/prometheus/ today.

- -
-
-

☀️ Helios.X

-

The public gaze. Analytics, dashboards, transparency reports. - Redirects to silentmode.st/helios/ today.

- -
-
-

✉️ Hermes.X

-

Messenger. Nostr-based messaging bound to a BCNR name via the np - + nr records. Redirects to silentmode.st/hermes/.

- -
-
-
- -
-

What's coming

-

Sirius.X consolidates docs and registration today; three larger pieces are on the roadmap.

-
-
-

Wallet & name portal next

-

Sign in with your Bitcoin Cash wallet, see every name you own, edit records inline, mint or renew - from one place. Same self-custody as today — the portal never handles your keys.

-
-
-

Sirius Studio planned

-

An in-browser site editor for names you already own — the WordPress / Tilda / Wix pattern, - but the whole site persists to Sia and the name's s3 record updates on save. - Sites live where the name says they live.

-
-
-

Decentralized git remote MVP live

-

The Silent Mode repo is served as a bare git tree over dumb-HTTP from - the VPS. Clone it with any git client, no login. Forgejo + Sia sync - for a proper decentralized backend is the next track.

-
git clone https://silentmode.st/sirius-x/repo/silent-mode.git
-

One-shot snapshot as a - downloadable git bundle (works when dumb-HTTP is blocked):

-
curl -O https://silentmode.st/sirius-x/repo/silent-mode.bundle
-git clone silent-mode.bundle silent-mode
-
-
-
- -
-

Verify this page against the chain

-

You do not have to trust that sirius.x is served by anyone specific. - The name's certificate is on the Bitcoin Cash chain; the records point to Sia; every path is - independently checkable.

- -

1. Confirm the certificate exists on chain

-
node --input-type=module -e "
-import { loadWallet } from './Argus/src/lib/wallet.js';
-import { resolveName } from './Argus/src/lib/bns.js';
-const w = await loadWallet('main');
-console.log(await resolveName(w.provider, 'sirius.x'));
-process.exit(0);"
- -

2. Fetch the TLD-registry snapshot from Nostr

-

The operator-signed snapshot of the TLD list is published as a Nostr replaceable - event. Any relay carries it; the event's content field carries the current snapshot root.

-
-
Kind / d-tag
-
30078 / bns-tld-list
-
Pubkey (x-only, hex)
-
f2c925194c531c7c398017e35b2396df64a61a613aa5fadebdf1f4990f2267f4
-
Relays
-
wss://nos.lol · wss://relay.damus.io
-
- -

3. Reach this site via more than one route

-

The same content should be served from at least two independent paths. If one - mirror is unavailable, the others still work. If two mirrors disagree, the on-chain record is - the tiebreaker.

-
    -
  • Via any BCNR resolver: https://sirius.x/ (requires the local root CA)
  • -
  • Via the public gateway: https://navigate.st/bns/sirius.x
  • -
  • Via silentmode.st mirror: https://silentmode.st/bns/sirius.x
  • -
  • Direct from Sia (content-addressed): the s3 record on sirius.x - names the exact bucket key
  • -
-
- -
- - - - - - - - - - - - - - - - + + + + + +Sirius.X — the home for names on Bitcoin Cash + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Names on the chain.

+

Sirius.X is the entry point for BCNR — Bitcoin Cash Name Registry. + Register a top-level domain, register a name under one, look up records, + verify anything against the chain.

+ +
+ +
+ One search checks the label across every TLD in the registry. Available names appear first. +
+
+
+ + +
+ + + + + +
+ +
+

Try it

+

Three things you can do right now. The registrar and TLD-registry UIs live at + silentmode.st today; Sirius.X is the canonical home and will consolidate them here.

+
+
+

Register a name

+

Pick a label, search across every TLD in the registry at once, mint the name + certificate straight into your own wallet. Renewals are per-TLD policy set by + the owner — the price the TLD sets is what you pay.

+ +
+
+

Register a TLD

+

A TLD is itself a certificate on the chain. Anyone can mint one — you become the registry + operator for every second-level name under it, set the price yourself, and earn 90 % of the + fee on every name registered under it. Renewals are per-TLD policy that you control.

+ +
+
+

Look up & verify

+

Resolve any BCNR name, compare records across mirrors, and check the TLD registry snapshot + against the chain. Everything is publicly verifiable.

+ +
+
+

🔑 Sign in — your names portal

+

Import your seed phrase or connect a wallet (Cashonize / Paytaca via WizardConnect) to see + every name you own, review records, and update them. Your keys never leave your browser.

+ +
+
+

🧭 Theseus Navigator

+

The browser that speaks BCNR natively. Open hello.bch in the address bar — + it just resolves. TLS verified against on-chain fingerprints, no OS trust-store install.

+ +
+
+
+ +
+

Documentation

+

The full docs — TLD registry model, record types, resolver & gateway paths, + mainnet pricing, verification, and the operator runbook pointer — live on their own page so + they can grow without cluttering the landing.

+ +
+ +
+

Propose a new TLD

+

The per-TLD registry means minting a TLD is a real, paid action — not a namespace + land-grab. During the alpha, all TLD mints are operator-signed. To propose a TLD, get in touch + via the channels below. The design (mainnet) adds an on-chain covenant with countersignature + + tiered USD price floors: 1-char $100k, 2-char $50k, 3-char $15k, 4-char $5k, 5–8-char $1k, + 9+-char $250; one-time payment (not annual). See the design doc for the full model.

+
ICANN-adjacent TLDs are not open for public mint. A TLD registry is + only useful if it does not immediately collapse into an ICANN-shadow — the mainnet covenant + enforces this; on chipnet the operator's countersignature is the gate.
+
+ +
+

The pantheon

+

Sirius.x is one of seven sibling .x names — the Silent Mode pantheon. + Each project is its own on-chain certificate; each site is reachable through any BCNR + resolver, plus the silentmode.st gateway for browsers without one.

+
+
+

⛓️ Silentmode.X

+

The project umbrella. Redirects to silentmode.st where every pillar + has its own landing page + download hub.

+ +
+
+

Sirius.X you're here

+

Names on Bitcoin Cash — register, resolve, verify, manage. This site.

+ +
+
+

🧭 Theseus.X

+

The browser that speaks BCNR natively — Chromium build with the resolver baked in, + no OS-level daemon or root-store install.

+ +
+
+

🔥 Hephaestus.X

+

The forge. Forgejo instance for the ecosystem's git repos, backed by BCH wallet + auth. Sirius's own source lives here.

+ +
+
+

🔮 Prometheus.X

+

Fire from the chain. The project's foresight / experimentation layer. + Redirects to silentmode.st/prometheus/ today.

+ +
+
+

☀️ Helios.X

+

The public gaze. Analytics, dashboards, transparency reports. + Redirects to silentmode.st/helios/ today.

+ +
+
+

✉️ Hermes.X

+

Messenger. Nostr-based messaging bound to a BCNR name via the np + + nr records. Redirects to silentmode.st/hermes/.

+ +
+
+
+ +
+

What's coming

+

Sirius.X consolidates docs and registration today; three larger pieces are on the roadmap.

+
+
+

Wallet & name portal next

+

Sign in with your Bitcoin Cash wallet, see every name you own, edit records inline, mint or renew + from one place. Same self-custody as today — the portal never handles your keys.

+
+
+

Sirius Studio planned

+

An in-browser site editor for names you already own — the WordPress / Tilda / Wix pattern, + but the whole site persists to Sia and the name's s3 record updates on save. + Sites live where the name says they live.

+
+
+

Decentralized git remote MVP live

+

The Silent Mode repo is served as a bare git tree over dumb-HTTP from + the VPS. Clone it with any git client, no login. Forgejo + Sia sync + for a proper decentralized backend is the next track.

+
git clone https://silentmode.st/sirius-x/repo/silent-mode.git
+

One-shot snapshot as a + downloadable git bundle (works when dumb-HTTP is blocked):

+
curl -O https://silentmode.st/sirius-x/repo/silent-mode.bundle
+git clone silent-mode.bundle silent-mode
+
+
+
+ +
+

Verify this page against the chain

+

You do not have to trust that sirius.x is served by anyone specific. + The name's certificate is on the Bitcoin Cash chain; the records point to Sia; every path is + independently checkable.

+ +

1. Confirm the certificate exists on chain

+
node --input-type=module -e "
+import { loadWallet } from './Argus/src/lib/wallet.js';
+import { resolveName } from './Argus/src/lib/bns.js';
+const w = await loadWallet('main');
+console.log(await resolveName(w.provider, 'sirius.x'));
+process.exit(0);"
+ +

2. Fetch the TLD-registry snapshot from Nostr

+

The operator-signed snapshot of the TLD list is published as a Nostr replaceable + event. Any relay carries it; the event's content field carries the current snapshot root.

+
+
Kind / d-tag
+
30078 / bns-tld-list
+
Pubkey (x-only, hex)
+
f2c925194c531c7c398017e35b2396df64a61a613aa5fadebdf1f4990f2267f4
+
Relays
+
wss://nos.lol · wss://relay.damus.io
+
+ +

3. Reach this site via more than one route

+

The same content should be served from at least two independent paths. If one + mirror is unavailable, the others still work. If two mirrors disagree, the on-chain record is + the tiebreaker.

+
    +
  • Via any BCNR resolver: https://sirius.x/ (requires the local root CA)
  • +
  • Via the public gateway: https://navigate.st/bns/sirius.x
  • +
  • Via silentmode.st mirror: https://silentmode.st/bns/sirius.x
  • +
  • Direct from Sia (content-addressed): the s3 record on sirius.x + names the exact bucket key
  • +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/js/dashboard.js b/js/dashboard.js index e5618e1..d4a309b 100644 --- a/js/dashboard.js +++ b/js/dashboard.js @@ -10,7 +10,7 @@ // is the persistent device session (js/session.js): sign in once, come back // without a prompt until you sign out. -import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market"; +import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd"; const API = "https://silentmode.st"; const TLD_API = "https://navigate.st/api/tlds?include_hidden=1"; @@ -685,14 +685,34 @@ async function signedHeaders(msg) { // ---------- sell (marketplace listing) ---------- const RATE = () => window.siriusPricing?.CHIPNET_SATS_PER_USD || 250000; let activeListing = null; +function sellMode() { return $("sell-mode").value; } +function paintSellMode() { + const usd = sellMode() === "usd"; + $("sell-usd-terms").classList.toggle("hidden", !usd); + paintBand(); +} +function paintBand() { + const usd = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")) || 0; + const band = Math.min(90, Math.max(5, Number(String($("sell-band").value).replace(/\D/g, "")) || 25)); + const sats = Math.round(usd * RATE()); + $("sell-band-hint").textContent = usd > 0 + ? `% of today's rate: you will receive between ${fmtBch(Math.round(sats * (1 - band / 100)))} and ${fmtBch(Math.round(sats * (1 + band / 100)))}` + : "% of today's BCH rate"; +} +$("sell-mode").addEventListener("change", paintSellMode); +$("sell-band").addEventListener("input", paintBand); function paintSell() { const st = $("sell-status"); + paintSellMode(); $("sell-rate").textContent = `1 BCH ≈ ${fmtUsd(Math.round(1e8 / RATE() * 100) / 100)}${window.siriusPricing?.priceInfo ? " · " + window.siriusPricing.priceInfo() : ""}`; if (activeListing) { st.className = "status ok"; st.textContent = "This name is listed on the market."; $("sell-form").classList.add("hidden"); $("sell-active").classList.remove("hidden"); - const p = BigInt(activeListing.price_sats); - $("sell-kv").innerHTML = [["Price", `${esc(fmtBch(p))} (≈ ${esc(fmtUsd(Math.round(Number(p) / RATE() * 100) / 100))})`], ["Listed", esc(new Date(activeListing.created_at).toLocaleString())], ["Offer", `${esc(activeListing.partial_tx.slice(0, 40))}…`], ["Market page", `open →`]] + const p = BigInt(activeListing.price_sats || 0); + const priceRow = activeListing.kind === "usd" + ? ["Price", `${esc(fmtUsd(activeListing.target_cents / 100))} fixed · today ≈ ${esc(fmtBch(Math.round(activeListing.target_cents / 100 * RATE())))} · band ${esc(fmtBch(activeListing.floor_sats))} – ${esc(fmtBch(activeListing.ceil_sats))}`] + : ["Price", `${esc(fmtBch(p))} (≈ ${esc(fmtUsd(Math.round(Number(p) / RATE() * 100) / 100))})`]; + $("sell-kv").innerHTML = [priceRow, ["Listed", esc(new Date(activeListing.created_at).toLocaleString())], ["Offer", `${esc(activeListing.partial_tx.slice(0, 40))}…`], ["Market page", `open →`]] .map(([k, v]) => `
${k}
${v}
`).join(""); } else { st.className = "status"; st.textContent = "Not for sale. Set a price to list it."; @@ -705,7 +725,7 @@ async function loadListing(name) { if (current.name === name) paintSell(); } const bchStr = (sats) => (Number(sats) / 1e8).toFixed(8).replace(/0+$/, "").replace(/\.$/, ""); -$("sell-usd").addEventListener("input", () => { const v = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")); $("sell-bch").value = v > 0 ? bchStr(Math.round(v * RATE())) : ""; }); +$("sell-usd").addEventListener("input", () => { const v = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")); $("sell-bch").value = v > 0 ? bchStr(Math.round(v * RATE())) : ""; paintBand(); }); $("sell-bch").addEventListener("input", () => { const b = Number(String($("sell-bch").value).replace(/[^\d.]/g, "")); $("sell-usd").value = b > 0 ? String(Math.round(b * 1e8 / RATE() * 100) / 100) : ""; }); $("sell-list").addEventListener("click", async () => { if (!current.entry || !wallet) return; @@ -721,6 +741,33 @@ $("sell-list").addEventListener("click", async () => { const commitment = [...new TextEncoder().encode(raw)].map((b) => b.toString(16).padStart(2, "0")).join(""); const cert = utxos.find((u) => u.token?.category === current.entry.category && u.token?.nft?.commitment === commitment); if (!cert) throw new Error("certificate UTXO not found in this wallet"); + if (sellMode() === "usd") { + // Dollar price: certificate goes into the UsdListing covenant. + const usd = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")) || 0; + if (usd < 0.5) throw new Error("set a dollar price of at least $0.50"); + const band = Math.min(90, Math.max(5, Number(String($("sell-band").value).replace(/\D/g, "")) || 25)); + const targetCents = Math.round(usd * 100); + const nowSats = Math.round(usd * RATE()); + const floorSats = Math.max(10000, Math.round(nowSats * (1 - band / 100))), ceilSats = Math.round(nowSats * (1 + band / 100)); + const orc = await (await fetch(`${API}/api/price/oracle`, { cache: "no-store" })).json(); + if (!orc.pubkey) throw new Error("the price oracle is not available right now"); + push(`moving the certificate into the contract · $${usd} · band ${fmtBch(floorSats)} – ${fmtBch(ceilSats)}`); + const built = BNS.buildUsdListingTx({ name: current.name, certificateUtxo: cert, utxos, sellerAddress: cert.address, oraclePk: orc.pubkey, targetCents, floorSats, ceilSats, records: current.entry.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode }); + const signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h)); + const txid = await BNS.broadcast(el, signed.hex); + push("broadcast " + txid.slice(0, 16) + "…"); + const listing = { v: 2, kind: "usd", name: raw, seller: cert.address, seller_pkh: built.sellerPkh, category: current.entry.category, target_cents: targetCents, floor_sats: String(floorSats), ceil_sats: String(ceilSats), max_age_secs: BNS.DEFAULT_MAX_AGE_SECS, oracle_pk: orc.pubkey, redeem_hex: built.redeemHex, covenant_address: built.address, outpoint: { txid, vout: 0, satoshis: "1000" }, token: { category: cert.token.category, amount: String(cert.token.amount ?? 0), nft: { capability: cert.token.nft.capability, commitment: cert.token.nft.commitment } } }; + let posted = null; + for (let i = 0; i < 6 && !posted; i++) { + const r = await fetch(`${API}/api/market`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(listing) }); + const j = await r.json().catch(() => ({})); + if (r.ok) posted = j.listing; else if (r.status === 409) { push("waiting for the contract output to relay…"); await new Promise((res) => setTimeout(res, 4000)); } else throw new Error(j.error || `API ${r.status}`); + } + if (!posted) throw new Error("the market did not see the contract yet — press Refresh in a minute; the certificate is safe in the contract"); + activeListing = posted; paintSell(); refreshBalance(); + setMsg("sell-msg", `Listed at ${fmtUsd(usd)}. Buyers pay the dollar amount at the oracle rate; you receive between ${fmtBch(floorSats)} and ${fmtBch(ceilSats)}.`, "ok"); + return; + } push("found certificate · signing the offer (SINGLE|ANYONECANPAY)"); const listing = BNS.buildListing({ name: current.name, certificateUtxo: cert, priceSats: sats, sellerAddress: cert.address, addressToLockingBytecode: BNS.addressToLockingBytecode, keyFor: (h) => wallet.keyFor(h) }); const r = await fetch(`${API}/api/market`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(listing) }); @@ -736,6 +783,22 @@ $("sell-cancel").addEventListener("click", async () => { if (!current.entry || !wallet || !activeListing) return; if (!confirm(`Cancel the listing for ${current.name}? This also moves the certificate once so the signed offer becomes void.`)) return; const push = logger("sell-log"); const btn = $("sell-cancel"); btn.disabled = true; btn.textContent = "Cancelling…"; setMsg("sell-msg", ""); + if (activeListing.kind === "usd") { + let el = null; + try { + el = await BNS.connect(); push("connected to chipnet"); + const utxos = await BNS.getUtxosForAddresses(el, wallet.watchedAddresses); + const built = BNS.buildUsdCancelTx({ listing: activeListing, sellerAddress: wallet.address, sellerTokenAddress: wallet.tokenAddress, utxos, records: current.entry.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode, keyFor: (h) => wallet.keyFor(h) }); + const txid = await BNS.broadcast(el, built.hex); + push("certificate reclaimed · broadcast " + txid.slice(0, 16) + "…"); + const h = await signedHeaders(`BNS-MARKET1\n${current.name}\n{ts}`); + await fetch(`${API}/api/market/${encodeURIComponent(current.name)}`, { method: "DELETE", headers: h }).catch(() => {}); + activeListing = null; paintSell(); setTimeout(() => { loadHoldings(); refreshBalance(); }, 3000); + setMsg("sell-msg", "Listing cancelled. The certificate is back in your wallet; every quote issued for that listing is void.", "ok"); + } catch (e) { setMsg("sell-msg", "Failed: " + (e.message || e), "err"); push("error: " + (e.message || e)); } + finally { btn.disabled = false; btn.textContent = "Cancel listing →"; try { el?.close?.(); } catch {} } + return; + } try { const h = await signedHeaders(`BNS-MARKET1\n${current.name}\n{ts}`); const r = await fetch(`${API}/api/market/${encodeURIComponent(current.name)}`, { method: "DELETE", headers: h }); diff --git a/js/i18n.js b/js/i18n.js index 89dbbc3..8f48a4e 100644 --- a/js/i18n.js +++ b/js/i18n.js @@ -22,7 +22,7 @@ (function () { var STORE = "sirius-lang"; var CACHE = "sirius-i18n:"; - var VERSION = "20260920f"; // bump when dictionaries change + var VERSION = "20260920g"; // bump when dictionaries change var LANGS = { en: "English", de: "Deutsch", diff --git a/js/market.js b/js/market.js index 2b741f5..151a6fa 100644 --- a/js/market.js +++ b/js/market.js @@ -7,7 +7,7 @@ // stays valid while their certificate UTXO is unspent, so the gateway prunes // stale offers and the buyer's broadcast is the final arbiter. -import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market"; +import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd"; const API = "https://silentmode.st"; const $ = (id) => document.getElementById(id); @@ -16,6 +16,9 @@ const fmtInt = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ","); const bch = (sats) => (window.siriusPricing?.formatBch || ((s) => (Number(s) / 1e8).toFixed(8).replace(/0+$/, "").replace(/\.$/, "") + " BCH"))(sats); const bits = (sats) => (window.siriusPricing?.formatBits || ((s) => (Number(s) / 100).toLocaleString("en-US") + " bits"))(sats); const usd = (sats) => { const r = window.siriusPricing?.CHIPNET_SATS_PER_USD || 250000; const v = Number(sats) / r; return (window.siriusPricing?.formatUsd || ((n) => "$" + n.toFixed(2)))(Math.round(v * 100) / 100); }; +const fmtUsdC = (cents) => (window.siriusPricing?.formatUsd || ((n) => "$" + n.toFixed(2)))(Math.round(Number(cents)) / 100); +const satsForCents = (cents) => Math.round(Number(cents) / 100 * (window.siriusPricing?.CHIPNET_SATS_PER_USD || 400000)); +const clampSats = (sats, l) => Math.min(Number(l.ceil_sats), Math.max(Number(l.floor_sats), sats)); const shortAddr = (a) => (a ? a.replace(/^[^:]+:/, "").slice(0, 8) + "…" + a.slice(-5) : "—"); let listings = []; @@ -53,7 +56,8 @@ function render() { const keep = tldSel.value; tldSel.innerHTML = `` + tlds.map((t) => ``).join(""); let rows = listings.filter((l) => (!q || l.name.includes(q)) && (!tldSel.value || l.tld === tldSel.value)); - rows.sort((a, b) => sort === "name" ? a.name.localeCompare(b.name) : sort === "new" ? Date.parse(b.created_at) - Date.parse(a.created_at) : Number(a.price_sats) - Number(b.price_sats)); + const priceOf = (l) => (l.kind === "usd" ? clampSats(satsForCents(l.target_cents), l) : Number(l.price_sats)); + rows.sort((a, b) => sort === "name" ? a.name.localeCompare(b.name) : sort === "new" ? Date.parse(b.created_at) - Date.parse(a.created_at) : priceOf(a) - priceOf(b)); $("count").textContent = `${rows.length} of ${listings.length}`; const status = $("status"); document.querySelector(".toolbar").hidden = !listings.length; @@ -72,7 +76,9 @@ function render() { $("list").innerHTML = rows.length ? rows.map((l) => `
${esc(l.label)}.${esc(l.tld)} ${wallet && l.seller === wallet.address ? 'yours' : ""}
-
${esc(bch(l.price_sats))}≈ ${esc(usd(l.price_sats))}
+
${l.kind === "usd" + ? `${esc(fmtUsdC(l.target_cents))}fixed in USD · today ≈ ${esc(bch(satsForCents(l.target_cents)))}` + : `${esc(bch(l.price_sats))}≈ ${esc(usd(l.price_sats))}`}
seller ${esc(shortAddr(l.seller))}·listed ${esc(new Date(l.created_at).toLocaleDateString())}·view site →
`).join("") : `
Nothing matches that filter.
`; @@ -86,13 +92,15 @@ function push(t) { const log = $("buy-steps"); log.className = "steps on"; const function openBuy(name) { selected = listings.find((l) => l.name === name); if (!selected) return; $("buy-name").textContent = name; - const price = BigInt(selected.price_sats); - const check = BNS.verifyListing(selected, { category: selected.category }); + const isUsd = selected.kind === "usd"; + const price = BigInt(isUsd ? clampSats(satsForCents(selected.target_cents), selected) : selected.price_sats); + const check = isUsd ? BNS.verifyUsdListing(selected) : BNS.verifyListing(selected, { category: selected.category }); $("buy-kv").innerHTML = [ - ["Price", `${esc(bch(price))} (≈ ${esc(usd(price))})`], + isUsd ? ["Price", `${esc(fmtUsdC(selected.target_cents))} fixed · about ${esc(bch(price))} at today's rate (the exact amount is set by the oracle quote when you buy, between ${esc(bch(selected.floor_sats))} and ${esc(bch(selected.ceil_sats))})`] + : ["Price", `${esc(bch(price))} (≈ ${esc(usd(price))})`], ["Seller", esc(selected.seller)], ["You receive", "the name's certificate, to your token address"], - ["Offer check", check.ok ? "seller signature valid" : "INVALID: " + esc(check.error)], + ["Offer check", check.ok ? (isUsd ? "contract terms verified" : "seller signature valid") : "INVALID: " + esc(check.error)], ["Extra", "≈ 16 bits dust + fee"], ].map(([k, v]) => `
${k}
${v}
`).join(""); $("buy-steps").className = "steps"; $("buy-steps").innerHTML = ""; setMsg(""); @@ -113,9 +121,19 @@ $("buy-go").addEventListener("click", async () => { el = await BNS.connect(); push("connected to chipnet"); const utxos = await BNS.getUtxosForAddresses(el, wallet.watchedAddresses); push(`wallet has ${utxos.filter((u) => !u.token).length} spendable coin(s)`); - const built = BNS.completeSale({ listing: selected, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode }); - push(`built sale · price ${bch(built.costs.priceSats)} · fee ${bits(built.costs.chainFeeSats)}`); - const signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.sellerInputIndex] }); + let built, signed; + if (selected.kind === "usd") { + const q = await (await fetch(`${API}/api/price/oracle?txid=${encodeURIComponent(selected.outpoint.txid)}`, { cache: "no-store" })).json(); + if (!q.message) throw new Error(q.error || "no oracle quote"); + push(`oracle quote: 1 BCH = $${(q.price_cents / 100).toFixed(2)} (median of ${q.median_of} exchanges)`); + built = BNS.buildUsdBuyTx({ listing: selected, quote: q, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode }); + push(`you pay ${bch(built.priceSats)} · fee ${bits(built.fee)}`); + signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.covenantInputIndex] }); + } else { + built = BNS.completeSale({ listing: selected, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode }); + push(`built sale · price ${bch(built.costs.priceSats)} · fee ${bits(built.costs.chainFeeSats)}`); + signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.sellerInputIndex] }); + } push("signed your inputs (the seller's signature is already in place)"); const txid = await BNS.broadcast(el, signed.hex); push("broadcast: " + txid); diff --git a/js/register-flow.js b/js/register-flow.js index a10a0e5..b147931 100644 --- a/js/register-flow.js +++ b/js/register-flow.js @@ -7,7 +7,7 @@ // Injects its own modal HTML into and its own CSS into on load // so the host page needs nothing but a single script include. -import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market"; +import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd"; const $ = (id) => document.getElementById(id); const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" }[c])); diff --git a/js/studio.js b/js/studio.js index fa65404..13d12a1 100644 --- a/js/studio.js +++ b/js/studio.js @@ -11,7 +11,7 @@ // The gateway never holds a key: it verifies each upload's signature // against the current NFT owner and refuses writes outside bns//. -import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market"; +import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260920usd"; import { initAssistant } from "./studio-ai.js?v=20260920ai"; const API = "https://silentmode.st"; @@ -443,9 +443,17 @@ async function publish() { await putFile("_studio.json", data, "application/json"); pushStep("saved editor project (_studio.json)"); dirty = false; $("btn-draft").disabled = true; - // Point the name at the folder if it does not already. + // Point the name at the folder if it does not already. Re-read the + // registry first: `entry` from boot can be stale or missing (a name + // indexed after the page opened), and a stale view here means a + // needless on-chain transaction on every publish. + try { const r = await fetch(`${API}/api/name/${encodeURIComponent(name)}`, { cache: "no-store" }); if (r.ok) entry = await r.json(); } catch {} const rec = entry?.records || {}; - if (rec.s3 !== folder) { + const norm = (v) => String(v || "").trim().replace(/\/+$/, "") + "/"; + const pointsHere = norm(rec.s3) === folder; + if (!pointsHere && !entry) { + pushStep("the registry has not indexed this name yet — files are up; pointing the name is skipped until it appears (or set Hosting in the dashboard)"); + } else if (!pointsHere) { pushStep(`name points at ${rec.s3 ? `"${rec.s3}"` : "nothing"} — setting s3 = ${folder} on chain`); const next = { ...rec, s3: folder }; delete next.h; // inline HTML would shadow the Sia site diff --git a/market/index.html b/market/index.html index 5494038..7e7eb23 100644 --- a/market/index.html +++ b/market/index.html @@ -97,7 +97,7 @@ 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:560px){.row{grid-template-columns:1fr auto}.row .price{grid-column:1/-1;text-align:left}} - +