New public API in register-tx.js:
- TLD_BEACON constant (per Decentralized.DNS/DESIGN-tld-registry.md §3);
keep in step with Argus/src/config.js:TLD_BEACON and the indexer's
BNS_TLD_BEACON_SCRIPTHASH — all three must agree, or clients disagree
about which TLDs exist.
- normalizeTld(input) — validates a bare TLD label (1–16 chars,
[a-z0-9-]), matches bns.js's normalizeTld.
- buildTldRegistrationTx({...}) — mints a per-TLD certificate onto the
TLD beacon. Same shape as buildRegistrationTx (genesis-input rule,
dust accounting, change-at-vout-0) but the payload op is "TREG", the
commitment is the TLD label, and dust flows to TLD_BEACON instead of
the name beacon.
register-entry.js re-exports registerTldWithBuiltInWallet so the
in-browser registrar can drive TLD mints alongside name mints.
New record kind alongside h / s3 / ip / u: `p` reverse-proxies the
request to a full upstream URL while keeping the BNS host in the
address bar. Unlike `ip` (which pins the upstream to a raw IP + on-chain
TLS fingerprint), `p` uses the upstream's own DNS + public CA cert and
sends `Host:` of the upstream so vhost-based origins answer correctly.
Argus (record-picker.js):
- Placed AFTER `ip` in the apex chain: a name carrying both keeps its
existing pinned-IP behavior; names with only `p` get honored instead
of falling through to `u` and 302-ing away.
- Not applied to subdomain inheritance — `p` is single-URL by intent,
and inheriting through a subdomain would silently mangle the target.
Theseus (main.js serveBns):
- Fetches the upstream (path prefix from the `p` URL is preserved) and
returns the response body/status/content-type verbatim. Provenance
label "mirror" appears in the source badge.
Tests: record-picker.test.mjs covers `p`-only, `p`+ip precedence, and
the subdomain-suppression rule.
The sirius.x portal signs the user in with a seed phrase and then calls
POST /api/holdings to look up the BCNR names their wallet holds. That
endpoint was removed by commit 66fb0fe ("forward proxied headers +
trust Silent Mode CA") along with the elConnect/elCall helpers it
depended on. From a user's point of view the sign-in appeared broken:
the wallet unlocked but the names panel showed 'Could not load names:
API 404: bns gateway'.
Restores the endpoint using the connectAny + CHIPNET_ELECTRUM helpers
that resolver-web.js already had (now exported), and keeps the
beacon-only-URL filter so listunspent doesn't land on bns-indexer,
which only serves get_history + transaction.get.
Rebuild-in-place: no client-side change needed; portal.html already
POSTs to /api/holdings and expects the {count, names[]} shape this
endpoint returns.
registrar.js: BNS.connect() now filters silentmode.st/electrum and
coinspectrum.duckdns.org:50011 out of the default electrum list. Those
endpoints are our own bns-indexer.js — beacon-only, serves get_history +
transaction.get but NOT listunspent for arbitrary scripthashes. Every
wallet op (getBalance, getUtxos, edit signing) needs listunspent, so
picking a beacon indexer first (which we did for best reachability) broke
every wallet unlock with "-32601 unsupported method: blockchain.
scripthash.listunspent". New `beaconOk: true` opts back in for pure-
resolution paths.
Rebuilt the browser bundle (site/js/bns-register.js) so the fix reaches
portal + admin + register.html + anything else that imports BNS.connect.
site-sirius-x/js/profile-menu.js: a small shared script that transforms
the "🔑 Sign in" nav pill into a dropdown menu on every sirius.x page.
Signed-out shows New wallet / Add wallet / WizardConnect + a link to the
sign-in page. Signed-in (reads localStorage 'siriusProfile') shows the
short address, My names, Admin, Sign out. Included via one <script defer>
tag on each of the 6 pages (landing / portal / admin / docs / theseus /
brand); dropdown CSS is inlined by the script itself so consumers don't
need a matching stylesheet.
Portal.html writing to localStorage.'siriusProfile' after sign-in is a
follow-up so the dropdown reflects state across pages — until then the
menu always shows the onboarding options.
Bns-indexer on VPS was only exposed on port 50011 via nginx, which corporate
firewalls, mobile carriers, and many VPNs block. Added an nginx location on
443 that proxies to the same 127.0.0.1:50010, giving us a browser-friendly
WSS endpoint on the standard HTTPS port.
* lib/electrum.js CHIPNET_ELECTRUM: wss://silentmode.st/electrum listed
first (used by registrar.connect() + everything that depends on it).
* lib/resolver-web.js CHIPNET_ELECTRUM: same, kept in lockstep per the
"change both together" comment.
* bns-register.js bundle rebuilt to bake the new list.
* portal.html imports the bundle with ?v= so Chrome's in-memory ES-module
map doesn't serve a stale copy across tabs.
Portal now connects successfully; next remaining issue is that
bns-indexer only supports (server.version, get_history, transaction.get) —
`blockchain.scripthash.listunspent` for wallet address queries is not
implemented, so the "list your names" step fails there. Follow-up: add a
GET /api/holdings/<address> to public-gateway.mjs backed by the existing
BCHN electrum access, so the portal can pull holdings over plain HTTP.
The first .bch page now opens with zero user-visible latency and stays fresh
for as long as the browser runs. Four sources conspire in parallel — none of
them can block a navigation:
1. Warm start from disk (sync)
warmFromSnapshot loads the user-cache snapshot first, then falls back to
the copy bundled with the build. sharedIndex is set BEFORE the first
navigation can even fire.
2. Continuous background delta refresh (every 30 s)
startBnsPolling opens one electrum connection, fetches the beacon
history (a single call), and only pulls verbose tx bodies for txids we
don't already have — mergeFreshHistory-style. Merges into memory,
rebuilds the index locally, persists the enlarged snapshot to disk.
Turns a ~60 s full walk into ~1 s per new event.
3. Sia snapshot pull on boot (one-shot, wins the NEXT boot)
refreshSnapshotFromSia downloads the operator's published snapshot from
s3.silentmode.st for the next launch. After a long idle period the
browser resumes from that fresher snapshot instead of walking days of
events.
4. ensureIndex full-walk fallback
Still runs on boot for the case where there's no bundled snapshot AND
the poll hasn't landed yet — very first launch, offline install, etc.
resolveHost now prefers sharedIndex (which the poll keeps live) and, on a
miss with a stale index, triggers pollAndMerge (delta fetch) instead of
ensureIndex (full walk). The old full-walk fallback is only taken if the
delta primitives aren't available (running against an older resolver-web).
Argus/src/lib/resolver-web.js exports connectElectrum so the Theseus poll
can drive its own ad-hoc queries against the pool without duplicating the
Electrum wrapper.
Bundled starter snapshot refreshed: 73 beacon txs, root c37b859682…54e414ba.
Artifacts:
dist-public/TheseusNavigator-Setup-0.0.7.exe (95.4 MB)
sha256 1b0dabcd2b13067aa1fd89c3271708b35ba22dfc7a620e248b35e0487b7a104f
dist-public/TheseusNavigator-0.0.7-portable.exe (92.7 MB)
sha256 706dc01b21a88d3c2fbc3eeced0cab6ace873630bf3edc2d40915ca5be5c8cae
Four independent pieces of one story — make cold-start .bch resolution fast,
survive individual electrum outages, and turn adding a Silent Mode BNS server
into a checklist run.
* Argus/src/lib/snapshot-name.js — pure, interpretation-free snapshot format
for the name beacon (raw history + verbose tx cache + deterministic root).
* Argus/src/publish-name-mirror.mjs — mirror the snapshot to Sia S3
(floating + content-addressed) and Nostr NIP-33 (kind 30078,
d-tag bns-name-list). Same pattern as publish-tld-mirror.mjs.
* Argus/src/snapshot-name-to-file.mjs — dump the snapshot to a file for
bundling. Writes to TheseusNavigator/snapshots/bns-name-snapshot.json.
* Argus/src/lib/resolver-web.js — new export buildIndexFromSnapshot() runs
the same REG/UPD reduction as buildIndex() but from a pre-fetched snapshot,
no electrum required. Warm-start path in Theseus depends on this.
* TheseusNavigator/main.js — warmFromSnapshot() and refreshSnapshotFromSia()
wired into app.whenReady(). First .bch navigation returns from a warm index
instead of waiting on a full electrum walk.
* TheseusNavigator/package.json — bundles the starter snapshot as an
extraResource so packaged builds ship with a floor.
* TheseusNavigator/snapshots/bns-name-snapshot.json — first starter snapshot
(70 beacon txs, root 8cc859aa…).
* VPS/BNS-SERVER-PLAYBOOK.md — turn-key checklist for spinning up a second
or third Silent Mode BNS server, distilled from what actually worked on
the silentmode box (BCHN 29 [chip]-not-[chipnet], multi-beacon indexer,
wss:// nginx, on-chain publish via electrum.bch, common gotchas table).
* Argus/src/register-electrum-list.mjs — publish the current chipnet
server pool as `el` on electrum.bch. Fits inside the 200-byte OP_RETURN
cap by sizing the record (drops fallback IP pins in favour of URL-only
tokens until they fit). NAME was chosen deliberately: two-part `electrum.bch`
normalises to the bare distinct name `electrum`, whereas the earlier
candidate `electrum.silentmode.bch` collapsed to silentmode.bch and would
have inherited/overwritten that name's records.
* Argus/src/revert-silentmode-bch.mjs — one-off recovery from the earlier
register-electrum-list revision that hit exactly that subdomain-collapse
bug (broadcast an UPD to silentmode.bch and clobbered its s3 record).
Records recovered from the local snapshot cache; safe to leave in the
tree as documentation of what happened.
Per-TLD certificates on a distinct TLD beacon supersede the legacy list-in-one-NFT
model. TLD label is the NFT commitment; TREG/TUPD payloads on the TLD beacon
mirror REG/UPD on the name beacon. Chipnet TLD beacon:
bchtest:qzc4c76vtw9nd49g6x8nmvc0jz62uvk4dc7hmpmerh
scripthash c85db10e106606273422b2733b2a6fb8b35a4d0e7dbfb20cf59bdb0833c008b8
* config.js, resolver-web.{js,d.ts} — TLD_BEACON + TLD_BEACON_SCRIPTHASH
* bns.js — KNOWN_OPS { REG, UPD, TREG, TUPD }, registerTld/updateTld/normalizeTld
* bns.js + resolver-web.js — buildIndex({enforceTld:false}) drops name REGs
whose TLD lacks a live TLD cert at their block height (mempool-friendly).
Lockstep across both files.
* lib/tld-index.js — buildTldSnapshot({history,txs}) → deterministic root over
canonicalised tlds[]. Pure, importable, and stable across insertion order.
* indexer/bns-indexer.js — BeaconIndex is multi-beacon; ws handler dispatches
by scripthash; new HTTP endpoint GET /tld-list.json + /health behind
BNS_HTTP_PORT; state file v1 → v2 migrates in fromJSON.
* publish-tld-mirror.mjs — S3 (floating + immutable content-addressed) plus
NIP-33 kind 30078 d:bns-tld-list to Nostr relays, signed via nostr-tools
(libauth's Schnorr differs from BIP-340 and relays reject it).
* seed-tld-beacon.mjs — idempotent chipnet seed. Current set on the beacon:
bch p2p bit nav test x asm neo gt sc sia com dex cex nt (15). `.com` is
held by the operator: cert on-chain, excluded from public registrar UI.
Design + docs:
* Decentralized.DNS/DESIGN-tld-registry.md — schema, enforcement,
operator-countersig gate, USD-tiered pricing floors, fee splits,
tracker/mirror mechanics, migration/grandfathering.
* INSTRUCTIONS.md §3 rewritten for the 4-step flow (TREG → UI → deploy →
republish snapshot).
* site/tlds/ — public docs page listing current TLDs + verification paths.
* site/sirius/register.html — dropdown removed; parallel search across
every TLD in the TLDS array, sorted available → taken → not-on-registry.
Config:
* ariadne.config.json tlds → [bch, p2p, bit, nav, test, x] (CA constraints;
on-chain registry is now the authoritative source, config is fallback).
* setup/install.ps1 — reads TLD list via fetchTldList (per-TLD registry)
with fallback to fetchBcnrTlds (legacy tls.bch list-NFT) then config.
Tests:
* test/tld-index.test.mjs — 30 self-contained checks (multi-beacon dispatch,
state migration, ws routing, snapshot shape + root recomputability).
* test/tld-enforcement.test.mjs — synthetic + live chipnet integration
(with-gate correctly drops all pre-seed names).
- password-vault: createVault takes { messengerRootHex } opt; unlockVault
surfaces messengerRoot (null on legacy vaults, so nothing regresses);
saveVault persists it; bindMessengerRoot mutates an unlocked state for a
later attach-mnemonic-to-existing-vault flow.
- main.js password-setup: when the user provides a mnemonic, ALSO compute
seedToPurposeRoot(seed, "messenger/0") and store it. Random-seed vaults
stay as-is (no mnemonic = no messenger root to store).
- main.js hermes-init: two modes now — { mnemonic } (unchanged) or
{ useVault: true } (uses vaultState.messengerRoot directly, no mnemonic).
Response includes source: "vault" | "mnemonic" for the panel to badge.
- main.js hermes-can-use-vault: cheap availability probe used by the panel
to decide whether to show the vault sign-in shortcut.
- hermes.js: nostrKeyFromRoot(root) accepts 32-byte Uint8Array or 64-char
hex; produces the same {sk, pkHex, npub} as nostrKeyFromMnemonic for the
same seed (unit-verified: derivation paths converge on f2c92519...67f4).
- Messages panel: "Sign in with password vault" button appears above the
mnemonic entry iff the vault is unlocked AND was set up from a mnemonic.
The mnemonic path stays as the always-available fallback — bind is
genuinely optional, not required.
- Hermes/proof/: standalone keystone — BIP-39→Nostr, NIP-17 wrap/unwrap,
name-addressed send/receive verified end-to-end via public relay
- Argus/src/lib/hermes-derive.js: HKDF(silentmode/messenger/0) using libauth
so the registrar can compute np without pulling nostr-tools into Argus
- Argus/src/register-hermes-chipnet.mjs: idempotent REG/UPD script; publishes
np (Nostr pubkey) and nr (relay) records for a chipnet name
- TheseusNavigator/lib/hermes.js: same derivation + wrap/unwrap + record
parsing, canonical for Theseus; guarded by lib/package.json type:module
- TheseusNavigator/messages.html + messages-preload.js: Messages panel UI
(identity from mnemonic, live inbox, compose by .bch name), .bch suffix
stripped from displayed names
- TheseusNavigator/main.js: HERMES_MOD + loadHermesLib next to VAULT_MOD;
ipc handlers (hermes-status/init/close/inbox/send/open), per-relay
subscription with auto-reconnect, status pushed on WS open/close,
reverse-resolve pk -> .bch name via cached BNS index, Ctrl+Shift+M shortcut
via web-contents-created (works from any tab)
- Chipnet hermes.bch registered with np=f2c92519...67f4 nr=wss://nos.lol
(txid 7fc6de4544c13b782f163fdb892ea6749785886d05a336282fa659d722c7e92b);
end-to-end verified in Theseus
Root cause of the "Firefox 'Server Not Found' (github)" and Node
`getaddrinfo EAI_FAIL` reports against the shipped daemon: reply() in
bnsd.js built responses from scratch and never copied the query's EDNS0
OPT record from the additional section. RFC 6891 §6.1.1 requires the
response to echo OPT when the query carried one; strict clients (glibc
getaddrinfo, Firefox with TRR off) treat the missing OPT as FORMERR and
surface EAI_FAIL / "Server Not Found". Lenient clients (nslookup, Chrome
async-dns) tolerate the loss, which is why the bug looked flaky.
The prior commit (ef19db1) extracted src/lib/dns-server.js as a testable
surface but left bnsd.js still calling the old inline helpers. This
finishes the migration:
- src/lib/dns-server.js:
- buildReply() now walks the additional section, finds the OPT RR by
name=root/type=41, and appends it to the response. When the query
had no OPT, none is fabricated (RFC 6891 §7). Preserves RD and
opcode from the query rather than hardcoding 0x8580.
- parseQuestion() rejects QDCOUNT != 1 and truncated labels early,
and skipName() tolerates DNS compression pointers per RFC 1035
§4.1.4 so the OPT walker doesn't trip on a compressed name.
- forwardQuery() accepts "host" (default port 53) or "host:port" so
it can be tested off port 53.
- src/daemon/bnsd.js: replaces the inline helpers with imports; the
icann-first branch keeps its rcode-inspecting callback via a small
forwardInspect() wrapper around the Promise-based forwardQuery.
- test/dns-server.test.mjs (new): 14 tests. Includes parseQuestion
cases (plain / EDNS / bad QDCOUNT / truncated), buildReply invariants
(OPT echoed / OPT never fabricated / RD preserved / NODATA still
echoes OPT), full dns.Resolver round-trips against an in-process
server (A / AAAA-NODATA / forward-to-mock-upstream), and two pinned
regression tests that copy the OLD reply() verbatim and assert its
OPT-dropping behavior so a future regression is caught immediately.
- AriadneResolver/ariadne-resolver/{src/daemon/bnsd.js, src/lib/dns-server.js}:
synced from Argus so the packaged installer picks up the fix.
Pre-existing failures in test/portfolio-index.test.mjs (require is not
defined at src/lib/chain.js:54) are unrelated and predate this work.
Three doc-alignment changes shipping in AriadneResolver-Setup-0.1.0.exe
sha256 c5efae43af6cc163f468f1dc73d08939db32a9a6db52298ba18b2d9a2aee1292:
1. install.ps1 fetches the activated TLD list from the on-chain root
TLD certificate (tlds.bch, per START-HERE §1) via
resolver-web.js fetchBcnrTlds(), then:
- regenerates the CA's nameConstraints extension from that list
(rotates the CA when the on-chain set changes so leaves for the
newly-activated TLDs actually validate)
- adds one NRPT rule per activated TLD in surgical mode
- falls back to bootstrap ["bch"] if the chain is unreachable
Result: adding a TLD on-chain is picked up by re-running the
installer, no code release needed.
2. bnsd.js honors --policy / BNS_POLICY (bcnr-first | icann-first,
alias bns-first) per START-HERE §4b. bcnr-first is default and
preserves prior behavior. icann-first forwards to upstream first
and only substitutes a BCNR answer on NXDOMAIN. install.ps1
accepts -Policy and passes it into the scheduled task action.
3. ca.js loadOrCreateRootCA() now accepts a runtime TLD list; the
old callers still work (fall back to PERMITTED_TLDS from tlds.js).
Doc: RESOLVER-PACKAGING-PROMPT.md rewritten to reflect current
shipping reality; original 2026-07-26 packaging brief preserved
in the "Archival" section.
Deployed to dl.silentmode.st (nginx serves from /opt/silent-mode/dl/)
and mirrored to Sia bns/silentmode/. VPS bns-indexer.service reports
tlds.bch currently activates: bch p2p deviant nav sia dgb nt sm lm.
Parallel session was right — the previously-shipped f94834b5 pre-dated:
1. the ASCII em-dash sweep in setup/install.ps1 (Inno's [Run] uses
powershell.exe / PS 5.1; em-dashes in the file crashed the parser
under UAC, so the installer copied files but never registered the
scheduled task or NRPT rule)
2. the multi-TLD NRPT expansion (added .p2p .bit .nav alongside .bch)
3. the VPS-primary electrum server list — resolver-web.js now dials
wss://coinspectrum.duckdns.org:50011 first (Silent Mode's own
BCHN-backed BNS-only indexer), public servers as fallback
Result: a real user installing 3438d558 on a fresh Windows box now gets
a working system-wide resolver with no manual steps.
Deployed to dl.silentmode.st and mirrored to Sia bns/silentmode/.
Theseus Navigator:
- Firefox-style toolbar: SVG back/fwd/reload/home buttons
- security padlock badge in the address bar opens a floating site-info
overlay VIEW (on top of the page, never pushes content down)
- Edge-style favorites bar (shown only on the new-tab/home page)
- search-engine picker (DuckDuckGo/Google/Bing/Brave/Startpage)
- Theseus logo moved right, opens Settings; native menu removed
- address bar doubles as search; bottom resolver status line
Argus:
- BNS-only indexer: an electrum endpoint over a bare BCHN node
(watches just the beacon, caches txs so no txindex needed) + test
- registry lifecycle / portfolio / name-index modules
Site restructure (nav/apps/store/hermes/choose) + runbook notes.