Two pieces from testing "login through hephaestus.x":
1. bns-gateway (public-gateway.mjs, ip: case) — forward the upstream's
response headers, not just content-type. The old code stripped Location,
Set-Cookie, and everything else, so redirects from a proxied hostname
died silently in the browser ("Temporary Redirect" body with no Location
header). Header set now respects RFC 7230 hop-by-hop exclusions and
drops Content-Length (we may rewrite HTML for baseHref).
2. Hephaestus stack:
- forgejo/Dockerfile: tiny extension of the official image that copies
the Silent Mode Argonautica root CA into the OS trust store, so
Forgejo's Go client can validate hephaestus.x's cert on OIDC discovery
calls. Deliberately no `USER git` at the end — parent's s6-overlay
needs to init as root.
- docker-compose.yml: switch to `build: ./forgejo` and add
`extra_hosts: - "hephaestus.x:host-gateway"` so Forgejo can resolve
hephaestus.x back to the host's Caddy.
Both changes are inert at the moment because Forgejo's ROOT_URL is back at
code.silentmode.st (browsers-without-BCNR-resolvers couldn't complete
OAuth through hephaestus.x because session cookies don't cross origins —
the working UX has the "Sign in with wallet" button on hephaestus.x
delegate to code.silentmode.st). Keep the infrastructure in place for when
Theseus/Ariadne adoption makes it viable to flip ROOT_URL properly.
gateway/public-gateway.mjs:
* GET /api/holdings/<scripthash> and POST /api/holdings {scripthashes:[…]}
— server-side wallet-holdings lookup for the portal. Bns-indexer is
beacon-only so it can't answer listunspent for arbitrary addresses; the
gateway does the electrum roundtrip and cross-joins with the cached BCNR
index. Client posts scripthashes (no libauth needed on the server) and
gets back the names owned.
* elConnect now falls through to whole-buffer JSON.parse when a Fulcrum
response lacks a trailing newline — chipnet.bch.ninja does this and
otherwise every elCall to it times out. Same tolerance pattern as
bns-indexer.test.mjs's tiny electrum client.
* verifyElectrum picks a full electrum with a 3s server.version probe so a
hung server fails over to the next in seconds instead of stalling 20.
* Skips silentmode.st/electrum and coinspectrum.duckdns.org:50011 for
/api/holdings — both are bns-indexer routes that don't do listunspent.
web/register-entry.js: export addressToScripthash so the portal can derive
the scripthash for each of its wallet's watched addresses before POSTing.
Browser bundle rebuilt.
site-sirius-x pages:
* favicon + logo hrefs made relative (./assets/… on index/portal,
../assets/… on docs/theseus). Absolute /sirius-x/… broke on the sirius.x
BCNR route where the site is served from root instead of under /sirius-x/.
* Roadmap nav link added back to portal, docs, theseus — the landing has an
anchor to a section on itself, so subpages now link to it explicitly.
* portal.html: loadNames rewritten to POST scripthashes to /api/holdings
instead of doing client-side electrum. Works on browsers whose networks
block chipnet electrum ports; same-origin HTTPS on 443.
* portal.html: bundle import ?v= bumped so Chrome's in-memory ES module
map picks up the new bundle instead of a stale cached copy.
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.
.hub and .os seeded on the chipnet TLD beacon (~2,600 sats) and added to the
public registrar. .dea (already on-chain via a concurrent session) also
surfaced in the TLDS array so its second-level names become registerable —
consistent with the "if it's on-chain, offer it" default.
portal.html: fix "t is not iterable" by passing (electrum, addresses) to
BNS.getUtxosForAddresses instead of (addresses); open one BNS.connect() and
share it across the UTXO scan and the buildIndex. Sign-in itself already
worked (wallet unlocks, address populates) — the crash was in the subsequent
names load. Ships a clearer network-blocked error too when the browser can't
reach chipnet electrum ports (50004/50011/62002).
Mobile parity with Theseus 0.0.7's multi-source model, one source at a time.
Ships:
* Bns.pullSiaIndex() — fetches dl.silentmode.st/bns-name-index.json (the
operator's latest published interpreted BNS snapshot) on a background
thread. Installs the result as the current index + persists to disk cache
when we don't already have something fresher. Best-effort: silent on
failure, on-device electrum still runs.
* MainActivity fires the pull once per applyResolverSettings(), racing the
bundled starter + on-device electrum. The bundled starter wins the very
first navigation (already in memory); the Sia pull wins any subsequent
cold start after an operator publish; electrum wins steady-state via the
existing INDEX_TTL_MS refresh.
* publish-name-mirror.mjs now uploads three shapes to Sia (raw snapshot,
raw immutable, mobile-shape floating, mobile-shape immutable) AND scps
the mobile-shape floating pointer to the CDN so anonymous clients can
actually GET it. The s3.silentmode.st bucket rejects anon reads today;
the CDN copy is the workaround, same bytes.
* Cannot use Sia direct for now: s3d bucket policy blocks anon reads.
Documented in Bns.SIA_INDEX_URL — swap URL when the policy changes.
Version bump: 0.10 → 0.11 (versionCode 11, versionName 0.11).
Also: .gitignore TheseusNavigator/_prev/ — the ~372 MB local rollback dir
that kept appearing as untracked in every git status.
ariadne-v0.11.apk 124 KB
03c95832c16cc4763acaadbf328caa7d4bd05fc7c2994f6997a79cc6890bca44
Mobile Bns.java already had a disk cache + background refresh, but on a
FRESH install the disk was empty — first .bch navigation blocked on a
full electrum walk (~10-60 s over mobile network). Fix: ship a starter
BNS index inside the APK so the first navigation returns from cache.
* Argus/src/generate-mobile-index.mjs — new build-time tool. Reads the
raw name-beacon snapshot from Theseus's bundle, runs the SAME
buildIndexFromSnapshot reduction, serialises in the shape Bns.saveDisk
writes. Regenerate every release: `node src/generate-mobile-index.mjs`.
* AriadneResolver/mobile/app/assets/bns-index.json — the generated file
(44 names, snapshot root c37b8596…). Packaged via aapt2 -A.
* build.ps1 — pass `-A app/assets` to aapt2 link if the dir exists, so
the asset makes it into the APK. Version bumped to 0.10.
* AndroidManifest.xml — versionCode 10 / versionName 0.10.
* Bns.java — new setBundledIndex(byte[]); loadDisk() shares its parser
(parseIndexBytes) with the bundled path so both cache sources produce
identical Entry shapes. On cold start, if the user disk cache is empty
we fall through to the bundled bytes; existing behaviour otherwise.
* MainActivity.applyResolverSettings() — reads bns-index.json from
assets and hands the bytes to Bns.setBundledIndex. Best-effort: a
missing asset just skips the fallback and the network path takes over.
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).
- /api/tlds now calls buildTldSnapshot on the per-TLD-NFT beacon
(Decentralized.DNS/DESIGN-tld-registry.md §3) instead of reading the
legacy tlds.bch record. Every entry carries `certificate: true|false`
so a name shop can distinguish covenanted, sellable TLDs from legacy
ones that predate the certificate model. Ships a `legend` field
explaining both, and hides intentionally-legacy TLDs (currently just
`silentmode`, from an accidental REG) unless `?hidden=1`.
- /api/registry — bulk dump of every registered name for offline
availability computation by registrar sites.
- Inline electrum client (~30 lines) so the endpoint can query an
arbitrary scripthash — resolver-web's Electrum class is not exported.
Prefers the local multi-beacon indexer at ws://127.0.0.1:50010 with
public-pool fallback.
silentmode.bch/tools/ was returning the Sia NoSuchKey XML because the
gateway only auto-appended index.html for the root path (/), not for
sub-directory paths (/tools/, /apps/, etc). Trailing-slash URLs now
resolve to <path>/index.html — matches Ariadne's bnsd.js which already
had the same rewrite on the client side.
Verified: /bns/silentmode.bch/tools/ returns 200 with the tools page
(was 404 NoSuchKey).
Fixes the user-reported "download link on silentmode.bch/tools/#theseus
shows an XML NoSuchKey error" — the browser was requesting /tools/
(trailing slash from the URL path), gateway looked up bucket key
bns/silentmode/tools/ which doesn't exist, forwarded the S3 error.
Deployed on the VPS (/opt/bns-gateway/) with the in-place edit path
that keeps the same-dir import shape there (repo has ../lib/, VPS has
./resolver-web.js — the two file layouts intentionally differ).
Adds a unified /api/search/<q> endpoint to the gateway and rebuilds
navigate.st's search box + featured cards around the new "6 curated names,
everything else via search" IA.
Gateway (Argus/src/gateway/public-gateway.mjs): /api/search/<q> iterates the
cached full-index Map and scores every match — label-exact (100) beats
label-substring (70) beats h-record content substring (50). Content search
strips HTML tags before matching and returns a ±40/60-char snippet around
each hit. Results sorted by score then name, capped at 50, exposes a
`truncated` flag. Same 30-second cache the record picker + /api/labels/
already use — no extra fetch on the hot path. s3/ip content search awaits
Helios v1 (deferred by design).
Client (navigate.st/index.html): submit now routes three ways —
<label>.<tld…> → /bns/<name> straight, no API call
everything else → /api/search/<q>, render ranked panel
‣ 0 hits → "not on chain — Register at Sirius · Search the web"
‣ 1 label-exact hit → auto-redirect (fast path preserved)
‣ otherwise → results panel with match-type badge, snippet, mark-
highlighted query term
The old bare-label /api/labels/ path is retired from navigate.st (the endpoint
stays live — Sirius registration availability still uses it).
Featured cards on the main page are now the six user-selected names:
hello.bch, coinspectrum.x, silentmode.x, theseus.x, game.x, deviant.x.
Three are already on chain (hello.bch / coinspectrum.x / game.x) and render
as normal cards; three are not yet registered (silentmode.x / theseus.x /
deviant.x) and render as dashed "coming soon" pending cards — non-clickable,
faded, prevents 404s until they're actually minted via Sirius. The old 8-name
Quick Links grid is gone; "everything else via search" is now the story.
Deployed. Live search verified with poutakidis (1 hit → auto-redirect),
deviant (3 hits including a content match), xyzzynomatch (empty-state CTA).
Gateway rollback point at /opt/bns-gateway/public-gateway.mjs.bak.1787246702.
Fixes the "type a bare label, find matches across every TLD" gap. Before this,
typing `poutakidis` in the navigate.st search box was silently pinned to
`.bch` (missing `poutakidis.de`); typing `coinspectrum` was likewise pinned
to `.bch` and missed the `.x` registration.
Gateway (Argus/src/gateway/public-gateway.mjs): new /api/labels/<label>
endpoint. Iterates the cached full-index Map (getIndex(), rebuilt every 30s)
and returns { label, matches: [{ name, tld, records, category }] } — bare
`<label>` entries are surfaced as `<label>.bch` in the response for clarity.
Zero cost on the live path — the label endpoint reuses the same index that
the record-picker already touches.
Client (navigate.st/index.html): three routes on submit —
bare label → /api/labels/<l>: 0 matches show "not registered, register at
Sirius"; 1 match redirects to /bns/<name>; 2+ matches show
an inline picker with a per-record-type badge (on-chain /
Sia / server / redirect).
<label>.<tld…> → /bns/<name> straight, no API call needed.
free text → DuckDuckGo, unchanged.
The picker + empty-state markup + CSS all live inside the single 15 KB page —
no external dependencies. On API failure the client falls back to the pre-fix
".bch guess" so the box stays useful if the gateway is unreachable.
Deployed. Live at https://navigate.st/api/labels/poutakidis (1 match, .de) and
https://navigate.st/api/labels/coinspectrum (2 matches, .bch + .x). Backup of
the previous VPS gateway at /opt/bns-gateway/public-gateway.mjs.bak.1787175583.
- 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
Uncovered by the 2026-08-13 subdomain-inheritance fix: once
`checkers.game.x` correctly picked the parent's `ip` record instead of
`s3`, the ip branch itself failed. Two reasons:
1. `fetch("http://<ip>/", { headers: { host: name } })` follows the
site's :80→:443 redirect into `https://<name>.<tld>/`, which isn't
in ICANN DNS → "fetch failed".
2. The site's cert is signed by a per-machine BNS root, not a public
CA; standard TLS validation rejects it.
Both are fixed by connecting to the IP with SNI = name, pinning the
presented cert's SHA-256 against the on-chain `tls` record, and only
then issuing the HTTPS request over the same socket. The on-chain
fingerprint is the trust anchor BNS uses everywhere else (see
Argus/src/lib/ca.js).
Gateway (public-gateway.mjs): new pinnedHttpsGet + httpGet + ipRequest
helpers; case "ip" delegates. No silent HTTP fallback on pin failure
(a mismatch means "not the site the chain says it is").
Theseus (main.js): parallel port of the same helpers, Tor-aware
(routes through SocksProxyAgent when Tor is on). serveBns's inner
serveIp() delegates to ipRequest.
Verified live: `curl -sI https://navigate.st/bns/checkers.game.x/`
returns 200 OK with the checkers game (1,179,215 bytes, apex
`game.x` unchanged, served from Sia).
Symptom: pasting https://navigate.st/search?q=coinspectrum.bch (or the
new default-search-engine redirect from any browser) returned 404 from
nginx. Same for any BCNR name whose on-chain record uses ip (h / s3 / p
were fine).
Cause: the gateway's ip branch used fetch() with { headers: { host: name } }.
WHATWG fetch defines Host as a forbidden header, so undici silently
drops it and sends Host: <ip>. nginx has no server_name for a bare IP,
falls to the default server, 404s. The whole point of the ip record is
to reach a server_name that matches the BCNR name, so Host must be set.
Fix: use http.request from node:http (no forbidden-header list) with an
explicit Host header. Same behavior for every ip record; nothing changes
for h / s3 / p / u branches.
Live-patched on the VPS gateway and restarted the bns-gateway systemd
service. coinspectrum.bch now returns 200 (116 KB) via
navigate.st/bns/coinspectrum.bch/ and via the /search?q= redirect chain.
Argus/src/gateway/public-gateway.mjs mirrors the on-disk change on the
VPS at /opt/bns-gateway/public-gateway.mjs so future redeploys keep it.
Theseus soft-mode UX: 'Open with...' modal on collision, per-name/per-TLD
overrides, live per-tab switcher in the site-info popover, and a Naming section
in Settings for policy + reset. Backed by an on-chain root TLD certificate
(tlds.bch) that resolver-web.js discovers via fetchBcnrTlds()/isBcnrNativeTld().
Companion pieces:
- Argus/src/indexer/ELECTRUM-SOURCE-README.md — the featherweight VPS variant
(no BCHN node, no Fulcrum) now live as bns-indexer.service.
- Argus/DESIGN-root-tld-cert.md — clarified: NOT a governance workflow, just
ordinary key management (single wallet MVP -> 2-of-3 multisig). List gates
registration / surgical NRPT / soft-mode classifier — never resolution.
- ROADMAP-IDEAS.md — recorded SiaGit/GitHub.sia + user-friendly Sia UI ideas.
Full spec: Argus/DESIGN-collision-modes.md (already tracked).
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.
- Address-bar security badge + popover lock redesigned to match Firefox's
padlock (shackle + rounded body + keyhole).
- Added AI/LLM answer engines to the catalog: ChatGPT, Claude, Phind, You.com
(Perplexity already present) — all accept a URL query. Off by default; enable
in Settings.
- Favicons now load from DuckDuckGo's icon service (one privacy-respecting host,
returns an icon for ANY domain) instead of guessing /favicon.ico per site,
which failed for several engines and fell back to emoji. Verified loading.
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/.
Was only .bch. The daemon and CA already handle these four TLDs; the NRPT
rule was the missing piece that made .p2p / .bit / .nav queries never reach
the daemon at all (they went to ICANN's normal DNS and NXDOMAIN'd).
Dual TLDs (.de .ru .dev .ltd) intentionally NOT here. They need the
daemon's DNS-forwarding path to work reliably for unregistered names,
and there's still an open EDNS/AAAA forwarding bug in bnsd.js that
would break most German/Russian sites if we NRPT'd .de.
Adding more native TLDs (.x .sia .dgb .nt from Theseus's expanded set)
needs a CA rotation first — the current CA's nameConstraints only permit
bch/p2p/bit/deviant/nav/test/bcnr. Deferred.
- Light/dark/system theme (Settings > General) via nativeTheme.themeSource
driving prefers-color-scheme across chrome, settings, popover, and home.
- Search box redesigned: a magnifier icon opens the engine dropdown, the rest
is a wider typing field; styled to match the browser (was a raw <select>).
- Trimmed built-in engines to DuckDuckGo/Google/Brave/Bing/Startpage/Yandex;
custom engines still add/remove via "Add / edit engines…".
- WebRTC: the "WebRTC Network Limiter" extension can't run in Electron
(chrome.privacy API is unavailable), so its function is now a native
4-mode WebRTC IP policy in Settings > Privacy.
- Address-bar placeholder: "Ask a search engine or enter web address".
Adds handleSearch() to public-gateway.mjs: users add
https://navigate.st/search?q=%s
as their browser's default search engine and typing bare "silentmode.bch"
in the URL bar (which Chromium and Firefox otherwise send to Google as a
search query, since .bch isn't in ICANN's TLD list) arrives here. Native
BNS TLDs (.bch, .p2p, .bit, .nav) 302 to the relay; anything else falls
through to DuckDuckGo so ordinary search still works.
Live on the VPS gateway at /opt/bns-gateway/public-gateway.mjs.
Nginx now proxies location = /search to 127.0.0.1:8700 as well.
Solves: bare "silentmode.bch" in Chrome/Edge went to Google. It now
resolves to the site via the relay in every browser.
- Security popover redesigned to resemble Firefox's site-info panel:
lock/shield hero, "Connection secure" status, host, plain-language
subtitle, and a details grid; the overlay view auto-sizes to content.
- Search-engine picker returned to the toolbar with an "Add / edit engines…"
entry that opens Settings; added Ecosia, Mojeek, Presearch.
- Custom search engines: add (name + URL template with %s) / remove in
Settings > General; used by address-bar search.
- Address-bar placeholder: "Ask a search engine or enter web address".
- Theseus installer/portable rebuilt with the toolbar overhaul; updated
SHA-256 in releases-manifest.json, releases/index.html, apps/index.html
(Setup 91764aba…, portable 41a54c7e…). Binaries live on dl.silentmode.st;
restructured site (shared.css, apps/nav/store/hermes/choose) deployed to
Sia (silentmode.bch).
- bns-indexer: main-module detection via pathToFileURL.
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.