# 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":"<h1>Theseus</h1>…"}
coinspectrum.bch-> {"ip":"195.184.247.106","p":"https://…","tls":"84e4cd…"}
demo.p2p        -> {"h":"<h1>demo.p2p</h1>…"}
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 <name> <url-or-json>
```

```bash
node src/register.js mysite https://example.org          # shorthand → {"u": …}
node src/register.js mysite '{"h":"<h1>Hello</h1>"}'     # 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-<root>.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 <name> <url-or-json>
```

```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":"<h1>Hi</h1>"}` |
| `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 <file-or-dir> <bucket[/prefix]>
```

```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.
