363 lines
13 KiB
Markdown
363 lines
13 KiB
Markdown
# 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.
|