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
-
-
-
-
-
-
-
Signed in operator
-
-
-
—
-
-
—
-
-
-
-
-
-
-
-
Mint a new TLD
-
Adds a per-TLD certificate to the TLD beacon. The cert
- lands in your token address. Cost is chain fees + beacon dust (~1,300 chipnet sats).
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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.
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
+
+
+
+
+
+
+
Signed in operator
+
+
+
—
+
+
—
+
+
+
+
+
+
+
+
Mint a new TLD
+
Adds a per-TLD certificate to the TLD beacon. The cert
+ lands in your token address. Cost is chain fees + beacon dust (~1,300 chipnet sats).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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.
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.
-
-
-
-
-
-
Logo & wordmark
-
A 4-point sparkle star plus Sirius.X, with the .x TLD picked out
- in acid green. The star alone works when you have no room for the wordmark; the full lockup is
- the default choice everywhere else.
-
-
-
-
Wordmark SVG · 320×64
-
Full colour, dark backgrounds. Use this by default.
1200×630 (OpenGraph / Twitter Card canonical). Composition survives aggressive
- centre-cropping — the star and tagline both stay visible even after Facebook and Discord chop
- the sides.
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.
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.
+
+
+
+
+
+
Logo & wordmark
+
A 4-point sparkle star plus Sirius.X, with the .x TLD picked out
+ in acid green. The star alone works when you have no room for the wordmark; the full lockup is
+ the default choice everywhere else.
+
+
+
+
Wordmark SVG · 320×64
+
Full colour, dark backgrounds. Use this by default.
1200×630 (OpenGraph / Twitter Card canonical). Composition survives aggressive
+ centre-cropping — the star and tagline both stay visible even after Facebook and Discord chop
+ the sides.
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.
-
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.
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.
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.
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.
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.
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 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.
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.
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 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.
+
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.
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.
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.
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.
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.
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 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.
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.
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) => `
`).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}}
-
+