How the pieces fit together: the TLD registry, name registration, records that tell the resolver where a site lives, the resolver itself, and how anyone can verify anything against the chain.
Every BCNR name lives under a top-level domain whose certificate is itself a first-class token on the Bitcoin Cash chain — an NFT with the TLD label as its commitment, paying dust to a dedicated TLD-registry beacon. That certificate is the on-chain proof the TLD exists, and once enforcement ships, it is what makes any second-level name under it valid to conforming resolvers.
Fourteen public TLDs on chipnet today (2026-08-29):
bch p2p bit nav test x asm neo gt sc sia dex cex nt. The current list and
per-TLD categories are at
silentmode.st/tlds/.
The buyer's certificate mints straight to a wallet they control, in one transaction that also publishes the initial records and pays the beacon. Nobody including the operator can take the name back — the covenant work in progress adds an expiry / reclaim clock but does not change who controls the key.
A registered name carries a small JSON records object. Every record is
optional and multiple can coexist. Priority order the gateway uses:
h (inline HTML) → s3 (Sia bucket key) → ip
(host header served by an IP) → u (redirect).
| Record | Meaning | Typical use |
|---|---|---|
h | Inline HTML in the OP_RETURN payload itself | Tiny sites, a profile, a link hub |
s3 | A Sia bucket key (with auto-index for directory-style) | Multi-file sites, permanent hosting |
ip | An IPv4 address + optional tls fingerprint | Your own server, apps behind an IP |
u | Redirect URL | Short-form redirects to any web host |
tls | SHA-256 fingerprint of the leaf cert served at ip | Chain-pinned TLS trust, no OS root store needed |
np, nr | Nostr pubkey + relay list | Hermes / NIP-17 messaging bound to the name |
el | Space-separated electrum server list | On-chain-updatable resolver bootstrap |
Subdomains inherit from their parent with a slight
priority tweak: for a subdomain query, ip beats s3
(Host-header semantics). Full rule is in Argus/src/lib/record-picker.js.
The on-chain records above (h, s3, ip, u,
tls, np, el) cost a chain transaction to change and are capped
at 200 bytes of OP_RETURN. For DNS-style records (A / AAAA / MX / TXT / CNAME / NS) and any records
you edit often, Sirius.X uses a companion mechanism: a signed off-chain manifest that costs
nothing per change and has no size cap.
The chain says who owns the name. A signed blob says what the records are. Resolvers verify both. No third party is trusted for records: the manifest is signed by the same key that holds the on-chain NFT certificate, so an operator (or Sia farmer, or anyone else in the middle) can't rewrite records without breaking the signature.
| Concern | Where it lives | Cost per change |
|---|---|---|
| Who owns the name | Bitcoin Cash chain (NFT certificate) | 1 tx to transfer |
| Where the site lives | Chain record: s3, ip, u, h | 1 tx per change |
| DNS records, meta, TXT churn, etc. | Sia at <s3-bucket>/_records.json | free |
records.s3 to the Sia bucket,
fetches _records.json.sig field,
derives the CashAddress, compares to the current NFT-holder address from the chain. Mismatch = reject.seq number. Resolvers cache the
highest seen and reject anything less-or-equal. An old signed blob can't be re-served.{
"v": 1,
"name": "bitcoin.cash",
"seq": 42,
"updated_at": "2026-09-15T00:00:00Z",
"dns": {
"A": ["1.2.3.4"],
"AAAA": ["2001:db8::1"],
"MX": [{"pref": 10, "host": "mail.example.com"}],
"TXT": ["v=spf1 include:_spf.silentmode.st -all"],
"CNAME": null,
"NS": []
},
"sig": "<BCH message signature over sha256(canonical bytes)>"
}
Canonicalisation: sorted keys, no whitespace, null
fields omitted. The signable bytes are the manifest with the sig field removed. Full
spec: DESIGN-signed-records-manifest.md
— signable byte order, seq-monotonicity rules, threat model, v2 roadmap.
| Method | Path | Effect |
|---|---|---|
| GET | /api/records/<name> | Fetches the raw signed manifest from the name's Sia bucket. Public, cacheable. |
| POST | /api/records/<name> | Accepts a signed manifest, verifies against the on-chain owner + seq monotonicity, uploads to Sia. |
| GET | /api/dns/<name> | Resolver read. Fetches the manifest, verifies the signature against the on-chain owner and rejects rollback of the seq, then returns only the verified dns block. Callers can trust the response without doing their own signature or chain lookups. 30-second cache hint. |
The distinction between /api/records/<name>
and /api/dns/<name> is deliberate: the former is transparent (raw manifest as
uploaded, including the signature so clients can re-verify), the latter is opinionated (only the
verified DNS records, with the gateway having done the sig + seq work). Bridges and shims should
use /api/dns/; wallets and tools that want to see the full signed blob should use
/api/records/.
A name needs records.s3 set on chain first —
without a storage pointer, the manifest has nowhere to live. Portal and CLI both refuse to
publish a manifest until s3 is present.
The end-to-end recipe for putting a static site under a BCNR name. Chipnet today, mainnet later — the shape is the same. Three ways to host, most people combine two of them (Sia + VPS mirror) so the site keeps serving if either mirror goes down.
Simplest. Site lives on the Sia storage network, name's s3 record
points at the bucket key, gateways (navigate.st, silentmode.st, any BCNR-aware browser)
fetch it on demand. Same pattern silentmode.bch uses.
# From a checkout with sia-s3.json credentials:
node Argus/src/lib/sia-upload.js ./my-site bns/myname/
# Set the chain record (once). Note the trailing slash — enables auto-index
# so /myname/ serves myname/index.html and /myname/about/ serves .../about/index.html.
node Argus/src/update.js myname.x '{"s3":"bns/myname/"}'
Bucket-path convention: bns/<label>/ (no TLD; the label is
unique enough within our Sia namespace and keeps game.x + game.bch from colliding). Files
update by re-running sia-upload; the chain record only changes when the bucket path does.
ip)Point the name at an IP; every request goes directly to your box. Add a
tls record with the SHA-256 of the leaf certificate so browsers verify the
connection against the chain instead of the OS trust store.
# On your VPS: nginx serves your site over TLS. Then, on chain:
node Argus/src/update.js myname.x '{"ip":"1.2.3.4","tls":"<sha256-of-leaf-cert>"}'
What Sirius.X itself uses. Sia is the chain-authoritative source (survives
if the VPS is down); the VPS serves the same content on silentmode.st/<path>
for users without a BCNR resolver installed. On the VPS side, extend the nginx vhost
serving silentmode.st with a new location block that aliases
straight from disk:
location /myname-x/ {
alias /opt/silent-mode/site-myname-x/;
index index.html;
try_files $uri $uri/ =404;
}
Then rsync / scp your site tree to silentmode:/opt/silent-mode/site-myname-x/
and reload nginx. This gives you two URLs for the same content:
https://silentmode.st/myname-x/ (VPS-direct) and the chain-authoritative
https://myname.x/ via any BCNR resolver.
This is the exact sequence Sirius.X itself uses on every publish. The chain record stays constant; only file contents change, so no per-publish UPD is needed.
site-myname-x/ in your working repo. Any
static generator works (or plain HTML) — the deploy is content-agnostic.silentmode.st/myname-x/ within seconds. Users without a
BCNR resolver hit this route.
scp -r site-myname-x/* silentmode:/opt/silent-mode/site-myname-x/
sirius.x/ via any BCNR gateway. Survives VPS outage.
node Argus/src/lib/sia-upload.js site-myname-x/ bns/myname/
curl -so /dev/null -w "%{http_code}\n" https://silentmode.st/myname-x/
and curl -so /dev/null -w "%{http_code}\n" https://silentmode.st/bns/myname.x/
— both should return 200.Wrap the two commands into a script so every publish reaches both mirrors without extra thought. This is exactly what a two-liner deploy hook looks like:
# scripts/deploy-myname-x.sh
#!/usr/bin/env bash
set -eu
SRC="$(dirname "$0")/../site-myname-x"
VPS_DEST="silentmode:/opt/silent-mode/site-myname-x/"
SIA_BUCKET="bns/myname/"
echo "→ VPS mirror"
scp -qr "$SRC"/. "$VPS_DEST"
echo "→ Sia mirror"
node "$(dirname "$0")/../Argus/src/lib/sia-upload.js" "$SRC" "$SIA_BUCKET"
echo "✓ deployed; verifying"
for url in "https://silentmode.st/myname-x/" "https://silentmode.st/bns/myname.x/"; do
code=$(curl -so /dev/null -w '%{http_code}' "$url")
printf ' %s → %s\n' "$url" "$code"
done
Wire it into git: chmod +x scripts/deploy-myname-x.sh, add a
.git/hooks/post-commit that runs it if the site subtree changed, or invoke
manually. Sia uploads that fail (network hiccup, quota) leave the VPS mirror ahead until
the next run — same failure mode as any staged deploy.
If your primary publish path is straight-to-VPS (nginx-direct, no Sia step per publish), a cron job can pull the VPS state and push to Sia on a schedule:
# /etc/cron.d/sirius-x-sia-backup (on the operator's VPS)
17 * * * * root cd /opt/silent-mode && node Argus/src/lib/sia-upload.js \
/opt/silent-mode/site-myname-x/ bns/myname/ >/var/log/sia-backup.log 2>&1
Runs hourly at :17 (offset from the top of the hour so it doesn't collide with everyone else's cron traffic). The tradeoff: up to an hour of drift between VPS and Sia after a publish — usually fine, since the chain record still resolves correctly either way and BCNR gateways cache Sia content.
s3 is set, gateways fetch from Sia; the VPS mirror is just a
convenience URL. If only ip is set, gateways fetch from the VPS; Sia is
just backup. If BOTH are set, gateways pick per the priority in the "Records" section
(subdomain-aware; see record-picker.js). Set both for durability, but be
deliberate about which is primary.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, 'myname.x'));
process.exit(0);"
Argus/sia-s3.json. VPS
deploy needs SSH to the operator's box. Neither is in the repo — a session that isn't
run by the operator will need those handed over out-of-band. Full command reference is in
INSTRUCTIONS.md, especially §5 (Sia storage) and §6
(deploy).Three paths, same chain data. Anyone can pick.
.bch URLs directly.
Installs a local root CA for TLS.
Download →https://navigate.st/bns/<name>/ proxies through a hosted resolver.
Same content, fewer guarantees — trust the gateway to fetch honestly, or run one of
the first two.On mainnet the covenant enforces USD-denominated floors, tiered by TLD label length. This is what keeps someone from land-grabbing every ICANN TLD in one afternoon. TLD registration is one-time — a TLD is closer to a domain purchase than a lease.
| Label length | Floor (USD) | Notes |
|---|---|---|
| 1 char | 100,000 | Effectively unique; ENS-like scarcity |
| 2 char | 50,000 | The .ai / .io tier |
| 3 char | 15,000 | Order of magnitude below ICANN's $185k application floor |
| 4 char | 5,000 | Floor for short but reasonable TLDs |
| 5–8 char | 1,000 | Bulk-registerable but not spam |
| 9+ char | 250 | Descriptive TLDs, low economic gravity |
On name registration under a TLD, the TLD's
owner collects a share (fee_bps, default 5%, cap 50%). Chain-enforced when
the TLD's policy is covenant; honour-system otherwise. Name registration is
yearly and priced separately.
The TLD owner also sets what names under it sell for and whether the TLD is on or off, straight from the portal: a price record (flat USD per name, replaces the length tiers for every buyer) and a hidden record (1 makes it a private TLD: off the public list, nobody but the owner can register names under it — the owner still can from the portal, paying only the platform share — until it is switched back on). Both ride the same signed TUPD, so every client reads them from the chain; the owner keeps 90% of each sale.
A tracker publishes signed snapshots of the TLD registry to Sia and Nostr so downstream clients don't have to walk the chain themselves. The chain is authoritative; the tracker is speed. Every snapshot carries a root hash anyone can recompute locally — a lying mirror is caught by any recipient who bothers to check.
A conforming client falls through: (1) fetch snapshot from a mirror,
(2) verify root, (3) if suspicious, walk the beacon and rebuild. All three
yield the same list for any given block height.
You do not have to trust that sirius.x, silentmode.st, or navigate.st are serving honest content. Every name's certificate is on chain; every record is a signed on-chain payload; every host is checkable independently.
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);"
Same content should be served from at least two independent paths:
https://<name>/ (requires local resolver + CA)https://navigate.st/bns/<name>/https://silentmode.st/bns/<name>/s3 recordThese docs describe what. The how — exact bash commands to
register TLDs, mint names, update records, and deploy — is
INSTRUCTIONS.md. Three ways to read it, in order of "how much do I already
have installed":
git clone https://silentmode.st/sirius-x/repo/silent-mode.git — dumb-HTTP,
no auth. Get the whole tree, browse offline, run the scripts.git clone https://code.silentmode.st/silentmode/sirius.git — public,
no auth.The runbook covers CLI name registration, TLD-registry seeding, record updates, Sia upload, VPS deploy, tests, and the historically-costly gotchas.