sirius/docs/INSTRUCTIONS.md
2026-09-12 00:07:25 +02:00

13 KiB

How to do things — Silent Mode runbook

Operational companion to 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

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):

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

node src/register.js <name> <url-or-json>
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:

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) 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:

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:

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:

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):

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:

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 §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

node src/update.js <name> <url-or-json>
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:

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.

# from D:\Dev\SilentMode
node Argus/src/lib/sia-upload.js <file-or-dir> <bucket[/prefix]>
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:

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:

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

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:

curl -s https://navigate.st/bns/silentmode.bch/register.html | grep -c 'bns-register.js'

7. Tests

cd Argus
node --test test/*.test.mjs        # offline; no network, no coins

Live scripts spend real chipnet coins and are run deliberately:

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.