# How to do things — Silent Mode runbook
Operational companion to [`SESSION-CONTEXT.md`](SESSION-CONTEXT.md). That file is
*what the product is*; this one is *how to do a thing*. Every command below was
run on 2026-07-30 and works.
All commands run from `D:\Dev\SilentMode\Argus` unless stated. Prefix with `rtk`
per project convention. **Everything is chipnet.**
> **Before you touch a `.js` in `Argus/src/lib/`, grep for it first.** Two
> sessions have written here concurrently and `registry-lifecycle.js`,
> `portfolio.js` and `ElectrumPool` were each independently rebuilt once. If a
> test file exists for a module you are about to create, implement to the test —
> the test is the contract.
---
## 1. Read the registry
```bash
node src/resolve.js # list every name, with records and category
node src/resolve.js demo.p2p # resolve one
```
Real output today (abridged):
```
silentmode.bch -> {"s3":"bns/silentmode/"}
theseus.bch -> {"h":"
Theseus
…"}
coinspectrum.bch-> {"ip":"195.184.247.106","p":"https://…","tls":"84e4cd…"}
demo.p2p -> {"h":"demo.p2p
…"}
syskypo.de -> {"s3":"bns/syskypo.de/"}
```
Zero-dependency alternative (the resolver clients actually ship):
```bash
node --input-type=module -e "
import WebSocket from 'ws';
import { resolveName } from './src/lib/resolver-web.js';
console.log(await resolveName('silentmode.bch', { WebSocket }));
process.exit(0);"
```
---
## 2. Register a name
```bash
node src/register.js
```
```bash
node src/register.js mysite https://example.org # shorthand → {"u": …}
node src/register.js mysite '{"h":"Hello
"}' # explicit records
node src/register.js mysite.nav '{"s3":"bns/mysite/"}' # any TLD, same command
```
Registers from the project wallet (`wallets.json`). It mints the certificate,
publishes the records and pays the beacon in **one transaction**.
Verify:
```bash
node src/resolve.js mysite.nav
```
Records can be omitted at registration and set later — see §4.
**Buyer-facing registration** (built-in wallet, pay-by-QR) goes through
`site/register.html`, not the CLI. The CLI is for operator-held names and testing.
---
## 3. Add a new TLD
**Superseded 2026-08-29.** The old "TLDs are just a convention, no registry
change needed" model is retired. The per-TLD registry
([`Decentralized.DNS/DESIGN-tld-registry.md`](Decentralized.DNS/DESIGN-tld-registry.md))
is now the source of truth: **a name REG under a TLD that has no on-chain TLD
certificate will be rejected by conforming clients once enforcement is on** (the
code is in `buildIndex({enforceTld:true})` in both `bns.js` and
`resolver-web.js`; default is still off during the mainnet migration, but the
registrar treats the on-chain list as authoritative today).
So adding a TLD is now **three steps: TREG on the beacon, UI, deploy.**
**1. Register the TLD on the TLD beacon (chipnet).**
The idempotent way — the operator wallet holds the seed for the TLD-beacon
address and the seed script skips anything already present:
```bash
cd Argus
# Edit src/seed-tld-beacon.mjs SEED_TLDS[] — append your new TLD label
node src/seed-tld-beacon.mjs
```
Or a one-off from a Node REPL if you don't want to touch the script:
```bash
node --input-type=module -e "
import { loadWallet } from './src/lib/wallet.js';
import { registerTld } from './src/lib/bns.js';
const w = await loadWallet('main');
console.log(await registerTld(w, 'newtld', {}));
process.exit(0);"
```
Either way you get `{tld, txid, category}`. The TLD certificate is minted to
the operator wallet — that address becomes the TLD's owner and can UPD the
records with `updateTld(wallet, tld, records, category)` later.
Cost: ~1,300 chipnet sats per TREG. Empty records `{}` mean "defaults apply"
per the design doc §3.
**2. Add the TLD to the registrar UI.**
The dropdown is gone. `site/sirius/register.html` now uses a `TLDS` array
(around line 275). Append your new TLD with `onchain: true`:
```js
const TLDS = [
...,
{ tld: "newtld", onchain: true },
];
```
If you registered the cert but want to *hold* the TLD without offering it in
the registrar (the `.com` pattern — cert on-chain so nobody else claims it, but
no second-level names offered), just leave it out of the `TLDS` array. The
cert stays valid on the beacon.
**3. Deploy `site/sirius/register.html` to the VPS.**
silentmode.st is nginx-served from `/opt/silent-mode/site/`, NOT Sia
(see [`DEPLOY-split-st-from-bch.md`](DEPLOY-split-st-from-bch.md)):
```bash
scp D:/Dev/SilentMode/site/sirius/register.html silentmode:/opt/silent-mode/site/sirius/register.html
```
(silentmode.bch — the Sia mirror — takes a full `sia-upload.js` of `site/`.
Only touch that if you also want the on-chain-hosted copy refreshed.)
**4. Republish the TLD snapshot (optional, updates mirrors).**
If a downstream is reading from the Sia mirror or the Nostr replaceable event
rather than doing its own chain scan, refresh the snapshot:
```bash
cd Argus
node src/publish-tld-mirror.mjs
```
Uploads `bns/tld-list.json` (floating) and `bns/tld-list-.json`
(immutable content-addressed) to Sia, and publishes NIP-33 kind 30078 (d-tag
`bns-tld-list`) to the configured relays. Dry-run: `BNS_MIRROR_DRY_RUN=1`.
### The thing to think about before adding one
Under the per-TLD registry the operator's key is the countersignature gate —
so **you're the registrar** for any TLD you mint. On mainnet the covenant will
enforce a price floor and an operator-countersig for public mints
([`Decentralized.DNS/DESIGN-tld-registry.md`](Decentralized.DNS/DESIGN-tld-registry.md) §5).
On chipnet nothing enforces this; a rogue script could TREG anything under a
different key. Only the operator's key produces TLD certs that clients trust
once the enforcement rules ship.
`.bch` is special in one way only: its on-chain commitment is the **bare label**
(`silentmode`, not `silentmode.bch`) for backward compatibility. Every other TLD
stores the full `label.tld`. `normalizeName` handles this; don't hand-roll it.
### Current chipnet TLD set (2026-08-29)
Bench-checkable via `node src/seed-tld-beacon.mjs` (idempotent verify) or by
fetching the mirror at `https://s3.silentmode.st:8600/bns/tld-list.json` (403
to anon today; the Nostr replaceable event `kind:30078` `d:bns-tld-list` from
pubkey `f2c925194c531c7c398017e35b2396df64a61a613aa5fadebdf1f4990f2267f4` on
`wss://nos.lol` / `wss://relay.damus.io` is publicly readable).
Live: `bch p2p bit nav test x asm neo gt sc sia com dex cex nt` (15 total).
`.com` is **held** by the operator — cert on-chain, excluded from the public
registrar UI so nobody else can claim it.
---
## 4. Set or change records
```bash
node src/update.js
```
```bash
node src/update.js mysite '{"ip":"203.0.113.9"}'
node src/update.js mysite.nav '{"s3":"bns/mysite/","tls":"a1b2c3…"}'
```
Proves ownership by moving the certificate — only the holder can do it.
### Record types
| Key | Meaning | Example |
|---|---|---|
| `h` | inline HTML, stored on-chain | `{"h":"Hi
"}` |
| `u` | redirect to a URL | `{"u":"https://example.org"}` |
| `ip` | IPv4 of your server | `{"ip":"203.0.113.9"}` |
| `p` | reverse-proxy upstream (address bar stays on the name) | `{"p":"https://backend.example"}` |
| `s3` | Sia object or prefix | `{"s3":"bns/mysite/"}` |
| `tls` | SHA-256 fingerprint of the name's TLS cert (hex, over DER) | `{"tls":"84e4cd…"}` |
### The 200-byte ceiling
The whole payload — `"BNS1"` + the JSON — must be **≤ 200 bytes**. For
`sirius.nav` that leaves about 161 bytes for records. An oversized payload throws
at build time rather than producing a broken registration.
Check before you commit to a long `h`:
```bash
node --input-type=module -e "
import { payloadBudget } from './src/lib/register-tx.js';
console.log('bytes free for records:', payloadBudget('sirius.nav'));
process.exit(0);"
```
`h` is for a page, a profile, a link hub. Anything real goes on Sia or a server.
---
## 5. Put a site on Sia storage
Two steps: upload the bytes, then point the name at them.
```bash
# from D:\Dev\SilentMode
node Argus/src/lib/sia-upload.js
```
```bash
node Argus/src/lib/sia-upload.js D:/Dev/SilentMode/site/mysite bns/mysite
node Argus/src/lib/sia-upload.js ./page.html bns/mysite
```
Then the record — a **trailing slash means "site root"**, no slash means a single
object:
```bash
cd Argus
node src/update.js mysite '{"s3":"bns/mysite/"}' # directory → site
node src/update.js mysite '{"s3":"bns/page.html"}' # one file
```
Verify through the public gateway:
```bash
curl -s -o /dev/null -w "%{http_code}\n" https://navigate.st/bns/mysite.bch/
```
Upload the **whole directory** if the page has assets — `sia-upload.js` walks it
and preserves relative paths, which is what makes `/js/…` and `/vendor/…` resolve.
**`sia-s3.json` is a secret** (SECURITY.md rule 0). Never print it, commit it, or
copy its values into a bundle. The build refuses to emit a bundle containing them.
---
## 6. Build and deploy the site
```bash
cd Argus && node build-web.mjs # bundles
cd .. && node Argus/src/lib/sia-upload.js D:/Dev/SilentMode/site bns/silentmode
```
Two rules that will bite otherwise:
- **Run the upload from the repo root**, not from `site/`. The path is relative
to cwd and `site/Argus/...` does not exist.
- **Upload the whole `site/` directory.** `register.html` depends on `site/js/`
and `site/vendor/`; a page-only upload leaves a broken live page.
`build-web.mjs` refuses to write a bundle that contains a live secret **value**
(it reads `wallets.json`/`sia-s3.json` and greps for their contents) or a
build-machine path. Do not weaken those checks.
libauth is **one shared module behind an import map** (`site/js/libauth.js`).
Do not bundle it per-file: its WASM init uses top-level await, and a second copy
hands out `undefined` for `secp256k1` on cold load. The build verifies every
import the other bundles emit is satisfied.
Verify what is actually live:
```bash
curl -s https://navigate.st/bns/silentmode.bch/register.html | grep -c 'bns-register.js'
```
---
## 7. Tests
```bash
cd Argus
node --test test/*.test.mjs # offline; no network, no coins
```
Live scripts spend real chipnet coins and are run deliberately:
```bash
node test/chipnet-acceptance.mjs # built-in wallet, full registration
node test/chipnet-paybyqr.mjs # registrar mints to a buyer
node test/chipnet-escrow-refund.mjs # unsigned order → refund, name never minted
```
They **exit 2** when the chain is unreachable, so an environment failure is
distinguishable from a product failure. Add `--keep` to the acceptance test to
print the throwaway wallet's phrase.
---
## 8. Gotchas that have actually cost time
**Node cannot resolve some hostnames on the dev machine.** `getaddrinfo` returns
`EAI_FAIL` for `coinspectrum.duckdns.org` and the chipnet electrum hosts while
`nslookup` and c-ares resolve them fine. Symptom: uploads and live tests fail
instantly with a DNS error. Workaround is a `--require` shim that falls back to
`dns.resolve4`; the real fix is on the machine. **If a network command fails
instantly, suspect this before suspecting the code.**
**Electrum uses ports 50004/62002**, which corporate networks and some VPNs block
outright — and every configured server shares those ports, so one firewall rule
takes the whole resolver down. `REGISTRAR.selfHosted` puts your own Fulcrum on
443 first; see `Argus/OPERATIONS-electrum.md`.
**Two resolvers must stay in lockstep.** `resolver-web.js` (zero-dep, shipped in
clients) and `bns.js` (mainnet-js). Change one, change both. A disagreement is a
consensus split between clients.
**Terminology is enforced.** It is a **certificate / registration record /
title** — never an "NFT" in code, comments or UI. `NFTCapability` and `.nft` are
mainnet-js API names and stay.
**The certificate lives in a 1,000-sat UTXO.** A wallet that does not understand
CashTokens sees dust; a "sweep all funds" would destroy the name. This is why
destinations are normalised to the token-aware `z…` form.
**`h` records are HTML you are publishing permanently.** There is no edit — only
an update transaction, and the old payload stays on-chain forever.
**Never register a name you cannot spell twice.** Registration is irreversible and
there is no expiry on chipnet today, so a typo is a permanently burned name.
---
## 9. What not to do
- Do not touch `D:\Dev\NameCoin` — separate live system.
- Do not flip anything to mainnet. It is a deliberate switch (new electrum
servers, `bitcoincash:` prefix, a new beacon, real money), not a config change.
- Do not commit or print `wallets.json`, `sia-s3.json`, `ca/root-ca.key`, or any
seed phrase.
- Do not put the operator's name or email in any file (SECURITY.md rule 3).
- Do not add a TLD to the dropdown without registering a test name on it first.
- Do not weaken the build's secret scan or the LGPL licence checks.