Commit graph

107 commits

Author SHA1 Message Date
Local Dev
63aba73d98 feat(gateway): rewrite cross-name absolute links through /bns/
theseus.x had a hardcoded href="https://sirius.x/" that worked in Theseus
(which resolves BCH names natively) but 404'd for ordinary browsers
served through the relay. Fixing it in every source file each time a
site adds a cross-name link is thankless; fix it once at the gateway.

New transformHtml() = injectBase() + rewriteBnsLinks(). The rewrite
matches href="https?://<label>.<tld>/..." and, when <tld> is a known
BCDN-only TLD, replaces the URL with /bns/<host>/... so the click stays
inside the relay origin.

Allowlisted TLDs: bch, x, p2p, deviant, bit, nav, test, coin, cash.
Intentionally EXCLUDED: st, com, gt, sc, de — those exist in ICANN and
rewriting them would hijack real web references (dl.silentmode.st is
the working case). The rewrite only fires on gateway-served HTML, so
the source sites remain clean when consumed at their native origin
by Theseus or Ariadne.

Verified: https://sirius.x/ on theseus.x home + plug-ins pages now
becomes /bns/sirius.x/; ordinary dl.silentmode.st links unchanged.
2026-09-20 18:35:09 +02:00
Local Dev
0c6753d6a3 feat(sirius-x): prices in BCH, amounts in bits, live BCH/USD rate from several exchanges
Every dollar figure on the site came from a hard-coded 250,000 sats per
dollar, which implies $400 per BCH; the market is near $250, so name
prices, TLD fees and sale listings were shown about 60% too cheap in
dollars. The gateway now serves /api/price: the median of Coinbase,
Kraken, Bitstamp, Binance and CoinGecko public tickers, no keys, cached
60 s, last good answer kept if every source fails. The site reads it
first, queries the same tickers itself if the gateway is unreachable,
remembers the last rate per browser, and only falls back to a constant
for the very first paint. Pages repaint when the rate arrives, and the
rate line says where it came from and how old it is.

Units: satoshi no longer appear anywhere. Sale prices, the seller's
input and the buy dialog are in BCH with the dollar figure beside
them; balances, fees and dust are in bits (1 bit = 100 satoshi).
2026-09-20 18:18:43 +02:00
Local Dev
0396988124 fix(gateway): baseHref per current directory, not per site root
The path-form handler injected `<base href="/bns/<name>/">` on every
page regardless of the sub-path being viewed. That meant a subdirectory
page's relative-parent links resolved wrong: from /bns/theseus.x/extensions/
the page's `<a href="../plug-ins/">` was resolved against the base as
/bns/plug-ins/ — dropping the name, giving a 404.

Compute baseHref from the directory of the current sub-path instead
(everything before the last "/" in rest, defaulting to "/"). Now:

  /bns/theseus.x/                → <base href="/bns/theseus.x/">
  /bns/theseus.x/plug-ins/       → <base href="/bns/theseus.x/plug-ins/">
  /bns/theseus.x/plug-ins/x.html → <base href="/bns/theseus.x/plug-ins/">
  /bns/theseus.x/extensions/     → <base href="/bns/theseus.x/extensions/">

`../plug-ins/` from the extensions page now resolves correctly to
/bns/theseus.x/plug-ins/, and `./` on a subdirectory page resolves to
the subdirectory itself instead of the site root.

Verified end-to-end: clicking Plug-ins from Extensions preserves the
name in the URL. Assets loaded via `../assets/...` from any subdirectory
still resolve correctly (they now go up exactly one level, to the site
root).
2026-09-20 17:31:49 +02:00
Local Dev
f49c7c244a fix(studio): imported dark pages no longer render on white; language picker in the toolbar
Pages such as potidaea.asm put their background on <html> and leave
<body> transparent; the editor's base stylesheet paints <body> white,
so the imported page showed light text on white. The canvas body now
stays transparent, which also matches the exported page.

The gateway's public name route sends Access-Control-Allow-Origin so
Studio on sirius.x can fetch a page's stylesheets and inline them
instead of falling back to links; the route only serves public content.

The language picker had no toolbar to attach to in Studio and floated
over the blocks panel; pages can now mark a slot for it and Studio does.
2026-09-20 15:56:29 +02:00
Local Dev
527cae9d32 feat: community extensions — publish with a BCDN name, install from Settings, theseus.x catalog
Anyone who owns a BCDN name can now publish a Theseus extension, and every
Theseus can install it with the publisher's signature verified locally.

Gateway (Argus/src/gateway/public-gateway.mjs):
  PUT /api/ext/<name>/<id>/<version> takes the gzipped tar, checks two BCH
  message signatures against the name's current NFT owner (one authorises
  the upload, one is stored in the channel), inspects the package
  (addon.json at the root, id/version/main match, 8 MB cap), enforces
  first-publisher ownership of an id and monotonic versions, and writes the
  tarball, the extension's updates.json and community/catalog.json to Sia.
  GET /api/ext/catalog reads the catalog back with CORS.

Theseus:
  lib/publisher-sig.mjs recovers the signer of a channel entry; main.js
  compares it with the publisher name's owner from Theseus's own chain
  index before installing or updating, so neither the relay nor a tampered
  catalog can pass off code under a trusted name. addon-updater.js gains
  installCommunity() and accepts publisher-signed entries in the regular
  update check (operator Ed25519 entries unchanged). Settings › Extensions
  shows the community catalog with Install / Update; Settings › Plug-ins
  links to theseus.x/plug-ins.

theseus.x:
  /plug-ins/ is a separate page for the first-party plug-ins (Aegis,
  Ariadne's Thread) with live versions and hashes; /extensions/ lists the
  bundled extensions, the community catalog, and how to build and publish;
  /extensions/publish/ signs and uploads a package in the browser with the
  wallet that holds the publisher's name (session helper + wallet bundle
  copied alongside).
2026-09-20 15:26:30 +02:00
Local Dev
bdb4c6235d fix(studio): open the page the name actually serves, not an empty folder
Sirius Studio only looked in its own folder, bns/<name>/. Every site
published the conventional way lives at bns/<label>/ (the s3 record),
or inline in the h record, so the editor opened those names as "new
site" with a template picker instead of the current build.

The gateway's site read gains a read-only ?src=live view that follows
the on-chain record: list and fetch from whatever bucket s3 points at,
or serve the h HTML as index.html. Writes are unchanged and still
confined to bns/<name>/.

The editor now starts from the live page when there is no draft:
relative image and link URLs are resolved against the public site URL,
stylesheets are inlined (cross-origin ones such as font CDNs are kept
as links and re-emitted on export), scripts dropped. When a draft
exists but the name serves something else, or index.html on Sia is
newer than the draft, a notice says so with a one-click import. An
"Import live site" button is always available. Verified with
aloevera.test, which is hosted at bns/aloevera/.
2026-09-20 01:22:21 +02:00
Local Dev
c8d465e2e4 feat(bns): name marketplace and TLD-owner co-sign rule
Two gaps the owner panel left open. First, a hidden TLD was only a UI
gate: anyone could still broadcast a REG under it and every indexer
admitted it. Second, there was no way to sell a name without trusting
the other side.

Co-sign rule (consensus, applied in lockstep by bns.js and
resolver-web.js): a REG under a TLD whose records at that height say
policy "cosign" or hidden 1 is indexed only if the transaction carries
the TLD's own certificate. The certificate can only be spent by the
owner's key and is re-issued to them in the same transaction, so it is
a co-signature nobody can forge and nothing is consumed. The TLD map
now keeps the TUPD timeline so policy is evaluated at the REG height.
Owners register under their private TLDs with the certificate added
from their own wallet; third parties under a "cosign" TLD build the
full transaction, sign their inputs and queue it at /api/cosign, where
the owner approves it from the dashboard (signCosignRequest refuses to
sign unless the certificate returns to the same locking script).

Marketplace: a listing is the seller's certificate input plus a price
output signed SIGHASH_SINGLE|ANYONECANPAY, stored by the gateway as a
bulletin board (/api/market, verified against the on-chain owner and
pruned when the certificate moves). The buyer completes it in one
transaction, so the seller is paid exactly when the name moves.
Cancelling also spends the certificate once so the offer is void.

Site: market.html, Sell sub-tab and Pending approvals in the portal,
Market link in nav and footer, six dictionaries extended, cache tags
bumped. Verified on chipnet: cosigned.sc registered by a throwaway
wallet through the queue with the .sc certificate back at the owner;
aloevera.test listed and delisted through the API.

Ariadne's resolver-web.js copy and the mobile Bns.java port still need
the co-sign rule; until then they admit REGs this index rejects.
2026-09-17 04:05:45 +02:00
Local Dev
1cedac22c6 fix(wallet): message signatures verified only half the time
BuiltInWallet.signMessage read `recovery` from libauth's recoverable
signature, but libauth v3 names it `recoveryId`. The flag byte came out as
0, so verifiers recovered the right key only when the true recovery id
happened to be 1. Signed DNS manifests and Studio uploads failed with
"signature does not match current on-chain NFT owner" and a different
derived address each attempt. Read the right field, fail loudly if it is
missing, rebuild the browser bundle and move every importer to the new
bundle URL. Also lands the registrar's signRecordsManifest that the bundle
already shipped.
2026-09-17 01:40:10 +02:00
Local Dev
4a5c726022 feat(sirius-x): Sirius Studio web-builder + signed site uploads; shorter dashboard menu
Owners had no way to put a real page behind a name without running Sia
tooling themselves. Sirius Studio (studio.html, js/studio.js) embeds
GrapesJS (BSD-3, vendored under vendor/grapesjs so nothing loads from a
third party) with three starter templates; Publish exports one
self-contained index.html plus the editor project, uploads both to the
name's own folder bns/<name>/ on Sia, and sets the s3 record on chain if
the name does not point there yet. Drafts save to the same folder.

Gateway: /api/site/<name>[/<path>] — list, read-through, PUT and DELETE.
Every write carries a wallet signature over (name, path, body hash,
timestamp) that must recover to the current NFT owner, and writes are
confined to bns/<name>/ so no name can touch another name's bucket.

Dashboard menu is now Web-Builder, Domain names, My TLDs, Settings, Sign
out; Overview and Wallet folded into Domain names and Settings, Docs is a
link in Settings.
2026-09-17 01:35:21 +02:00
Local Dev
63e9b31c92 fix(gateway): restore signed-records + TLD-beacon endpoints lost in the SPA deploy
The 17:23 UTC deploy today pushed the repo copy of the gateway over the
VPS, but that copy predated the signed-records work (/api/records,
/api/dns, TLD_BEACON-sourced /api/tlds, holdings with tlds/wallet/chain),
which had only ever lived on the VPS. Three-way merge of that version with
the SPA-fallback change, plus the resolver's NFT-owner tracking that the
records routes verify against. The repo is now the only deploy source.
2026-09-16 21:28:01 +02:00
Local Dev
c68eaf53c2 fix(argus): try dir-index before SPA fallback for extensionless paths
Two-tier fallback for extensionless paths that miss in Sia. The
previous change went straight to root index.html, which meant a real
sub-site living at "<name>/about/index.html" got shadowed by the SPA
root — visitors requesting "/about" got served the app instead of
the landing page.

New chain (matches nginx try_files order):
  1) request path              → /about
     hit: bns/potidaea/about   → 404
  2) directory index attempt   → /about/
     hit: bns/potidaea/about/index.html → 200 · SERVE
  3) if still 404, SPA root    → bns/potidaea/index.html → 200

Trailing-slash requests still short-circuit to their own index.html
via the existing `rel` derivation (unchanged), so /about/ has been
working the whole time; only the bare /about was falling through
to the SPA rescue.

Deployed to silentmode 2026-09-16:
  scp Argus/src/gateway/public-gateway.mjs
      silentmode:/tmp/public-gateway.mjs
  sudo cp … /opt/bns-gateway/public-gateway.mjs
  sudo systemctl restart bns-gateway
2026-09-16 19:24:29 +02:00
Local Dev
3e6eb1d392 feat(argus): SPA fallback for missing paths under BNS domains
When a request to /bns/<name>/<path> misses in Sia AND the path has
no file extension (looks like a client-side route, not an asset),
serve the domain's own index.html instead. Same shape as nginx's
try_files … /index.html — lets any hosted site use the History API
for pretty URLs.

Extension-having requests (missing .png / .js / .json / …) still
404 as they should — those should never be routed by a client-side
router and swallowing them would hide real bugs.

Deployed to silentmode 2026-09-16:
  scp Argus/src/gateway/public-gateway.mjs
      silentmode:/tmp/public-gateway.mjs
  sudo cp … /opt/bns-gateway/public-gateway.mjs
  sudo systemctl restart bns-gateway

Companion Deviant commit ("widescreen swap + URL routing") uses
this to serve /bns/potidaea.asm/katalogos etc. from the same
index.html.
2026-09-16 18:39:11 +02:00
Local Dev
8cb82d0d31 Ariadne 0.1.2: install.ps1 no longer dies on PS 5.1 stderr quirk; layered TLD-source cascade
install.ps1 root-cause fix
- Every `& $node ... 2>&1` had been tripping PS 5.1's "$ErrorActionPreference=Stop +
  native-command stderr = terminating error" quirk. Trap-and-log added in 0.1.1 EXPOSED
  the bug (0.1.0 died at the exact same line -- silently). 0.1.2 routes both node calls
  through a new Invoke-NodeCapture helper that uses Start-Process
  -RedirectStandardOutput/Error to file, so PS 5.1 never touches the streams.

Multi-source TLD cascade with on-disk cache
- -Indexer now accepts a comma-separated list; default "https://navigate.st,https://silentmode.st".
- Cascade (first hit wins): indexer[s] HTTPS  ->  local cache
  (C:\ProgramData\Ariadne\tlds-cache.json)  ->  electrum WSS (fetchTldList / legacy
  fetchBcnrTlds)  ->  bundled ariadne.config.json snapshot  ->  bootstrap [bch].
- Authoritative fetches (indexer or electrum) refresh the cache. Second install on a
  machine that has ever succeeded before is instant + always works offline.
- Per-source deadlines kept tight (5s for indexer HTTPS) so a slow source can't stall
  the install.

Upgrade over live install
- .iss CurStepChanged(ssInstall) stops "BNS Resolver Daemon" + "BNS Sia Bridge" via
  schtasks + 1.5s pause before file replacement. Fixes "DeleteFile failed; code 5.
  Access is denied" on runtime\node.exe when upgrading a machine where the daemon is
  currently running.

CA import
- Switched Import-Certificate -CertStoreLocation Cert:\... to direct X509Store('Root',
  'LocalMachine').Add(). Cert: PSDrive is not always loaded in -NoProfile elevated
  shells, which had broken the standalone unblock scripts.

Verified end-to-end on this box: silent install -> exit 0, clean install.log,
BNS Resolver Daemon running, 127.0.0.1:53 open, 9 NRPT rules (all TLDs including .bch),
coinspectrum.bch resolves system-wide, tlds-cache.json populated.

Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.2.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.2.exe
- silentmode.st + navigate.st/bns/silentmode.bch manifests advertise 0.1.2
- sha256 5a2c9e4f3c5e1a162008641019f5ffc35968751c0440f94d3c5fe67aafb8cc5e

Local Dev <noreply@localhost>
2026-09-09 22:57:06 +02:00
Local Dev
900e1ef586 feat(theseus/aegis): 0.6.1 — in-panel vault setup/unlock, BCH wallet imports, opt-in fiat prices, WizardConnect
Aegis Wallet 0.4.4 → 0.6.1:

- Vault lifecycle from the wallet gate. The locked / not-yet-created states
  now show a master-password form (with optional BIP39 mnemonic on setup)
  instead of redirecting users to Settings › Passwords. New
  api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by
  the existing "vault-derive" capability. api.openSettings(section) also
  added; settings.html honours a #section hash on open.
- Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44
  path or a WIF; the cashaddr is derived in the add-on, the signer material
  goes to a separate wallet-imports.enc via api.vault.imports {list, add,
  remove, signer}. Argus password-vault gains createImports / unlockImports /
  saveImports with its own KDF salt so the imports key is disjoint from the
  passwords key. lib/chain-bch-imported.js is a single-address Electrum
  adapter; spend support is deferred to M.1b.
- Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted
  in add-on storage. Fiat lines under balances, in the wallet picker, and a
  portfolio total when 2+ wallets are open. Settings tab is now reachable
  while the vault is locked so the toggle is always available.
- WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js).
  @wizardconnect/{core,wallet} are loaded dynamically via api.import to stay
  on the right side of LGPL §4d. Sign requests go through approvalModal and
  are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS.
- DGB adapter load is now soft-fail: when Aegis runs from userData/addons the
  bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of
  taking the whole add-on down.
2026-09-09 10:33:21 +02:00
Local Dev
c20a10d480 feat(argus): seed .cash TLD on the chipnet TLD beacon
Chipnet, TREG on TLD_BEACON:
  .cash txid 83822310a7d7d3762fb36690f4d20911177cc3f2c9b7598c593f8a8dc7e06c23

Follow-up name mints (operator wallet, chipnet):
  REG bitcoin.cash txid 4a684964cc067599fdbb58d6b174bfe97a5f92046586d2f2dc9660cd4ce5d12f
  UPD bitcoin.cash txid 326d0490a90d50c4c19369e6ec18db732a58ea4a1ee110d6609aae07fdefe174
                       (records.h = 126-byte on-chain whitepaper-style page)

Gateway /opt/bns-gateway/public-gateway.mjs updated in-place on the VPS
to build /api/tlds from a fresh buildTldSnapshot(TLD_BEACON) walk instead
of the legacy `tlds.bch` root record — so newly-minted TLDs (.cash, .coin
and the rest of the seed set) now surface in the sirius.x TLD list. That
gateway edit isn't in this repo; the change is documented here for the
audit trail.
2026-09-09 04:52:01 +02:00
Local Dev
2847e98d83 feat(argus): loadWallet cross-repo fall-through to Deviant encrypted keystore
Extend Argus/src/lib/wallet.js so loadWallet(name) transparently reads from
D:/Dev/Deviant/Keys/chipnet-keystore.json when the alias isn't in the local
SilentMode wallets.json. Same master password (env DEVIANT_KEYSTORE_PASSWORD
or CHIPNET_KEYSTORE_PASSWORD, or interactive prompt) unlocks the Deviant
keystore once per process; decrypted state cached in memory.

- Adds DEVIANT_KEYSTORE_PATH in config.js (defaults to sibling repo, env
  override for non-sibling layouts).
- loadWallet() tries local wallets.json first (fast path), then falls through
  to the Deviant keystore. Existing "main" behaviour unchanged.
- Slug normalisation: sirius.x ↔ sirius-x, main ↔ i, beacon ↔ bns-beacon,
  tld-beacon ↔ bns-tld-beacon — so scripts using either flavour resolve to
  the same key.
- New unlockDeviantKeystore() for eager-unlock at script boot.
- New listWallets() returns a merged inventory of slug/cashaddr/label/
  category/source. Never returns seeds or WIFs.

Verified against a live Deviant keystore with 15 entries: loadWallet('main')
resolves locally; loadWallet('sirius.x') falls through and produces the same
address as reading D:/Dev/SilentMode/Argus/wallets.json directly.
2026-09-09 02:16:42 +02:00
Local Dev
2d80e68ef8 feat(registrar): 90/10 revenue split — TLD owner earns from name mints
Every second-level name registration under a TLD now routes 90% of the
service fee to whoever holds that TLD's certificate on chain, with 10%
going to the platform address. That's the economic incentive for
minting a TLD: you earn from every name registered under it.

The mechanism, end to end:

1. resolver-web.js fetchTldMap now also records mintScriptHex — the
   scriptPubKey of the TREG output that carries each TLD's NFT.
   Exported so registrar can decode it into a cashaddr with libauth.
   MVP: this is the ORIGINAL owner; NFT transfers after mint are not
   traced yet (a follow-up will walk the chain of transfers).

2. registrar.js gains findTldOwnerAddress(client, tld) and
   splitServiceFee(sats). The split constants live at the top of the
   file (TLD_OWNER_SHARE_NUM/DEN = 90/100) so the ratio moves in one
   place. Rounding: BigInt division favours the platform on odd sat
   counts so the two shares always sum EXACTLY to the input.

3. quoteRegistration wraps the existing flow: it derives the TLD from
   the name, looks up the TLD owner, and if the owner ≠ buyer it asks
   buildRegistrationTx to add a second fee output. If the owner
   couldn't be resolved (TLD not registered, decode failure) the full
   fee stays on the platform address — the buyer still pays the same
   amount either way.

4. register-tx.js buildRegistrationTx accepts tldFeeAddress/tldFeeSats
   and, when set, emits an extra P2PKH output for the TLD owner. Sits
   between the beacon dust and the platform-fee output; outputMap
   records .tldOwnerFee so callers can find it. costs also carries
   tldOwnerFeeSats and netCostSats includes it.

5. priceSummary in registrar-config splits the 'Service fee' row into
   'Service fee — TLD owner (90%)' + 'Service fee — platform (10%)'
   whenever tldOwnerFeeSats > 0, with a per-line note explaining
   where the money goes.

Bundle: re-exported findTldOwnerAddress + splitServiceFee from
register-entry.js. Rebuilt bns-register.js (~34 kB) and deployed;
cache-buster bumped to ?v=20260908split on portal / admin /
register-flow.js.

Verified live: findTldOwnerAddress('.bch') returns the operator
cashaddr; quoteRegistration('tester42.bch') builds cleanly with a
9,000/1,000 split output pair on a 10,000-sat fee; priceSummary
renders both lines. No regressions on the TLD-mint flow (buyer IS
the TLD owner there — split short-circuits and it stays a single
fee output as before).
2026-09-08 02:42:16 +02:00
Local Dev
2346320bad feat(sirius-x): admin TLD hide/unhide + tabbed sign-in modal (4 direct actions)
Two features:

1. Operator can hide TLDs from the public /api/tlds listing so hidden
   TLDs stop appearing in name-search UIs. On-chain registrations
   under a hidden TLD keep resolving — this is a UX filter, not
   enforcement.

   Gateway (public-gateway.mjs):
   - Persistent HIDDEN_TLDS set backed by hidden-tlds.json next to
     the service script
   - GET /api/tld-visibility  -> {hidden:[...]}                (public)
   - POST /api/tld-visibility -> updates the list (operator-gated by
     Bearer BNS_OPERATOR_TOKEN env var; if unset, all writes refused
     so we default-deny)
   - /api/tlds filters out HIDDEN_TLDS; add ?include_hidden=1 to see
     everything (used by the admin panel to show all rows)
   - Operator token installed via systemd override on the VPS

   Admin panel:
   - New 'Operator token' card at the top of the TLD-registry section;
     token stored in sessionStorage (not localStorage) so a full
     browser close forgets it
   - Each TLD row got a 'Hidden from public' checkbox that POSTs on
     toggle and refreshes the table; failures roll back the checkbox
     and surface the error next to the token field

2. Wallet dropdown restored to 4 direct actions
   (Unlock / Create a wallet / Import a wallet / WizardConnect) and
   the shared mint/sign-in modal grew a tab strip so users can switch
   between the four wallet actions from any step without going back
   to a choice screen.

   register-flow.js:
   - renderTabs(active) prepended to stepCreate/stepImport/stepUnlock/
     stepExternal when signInOnly is set. Unlock tab only appears
     when a saved wallet exists.
   - Delegated click handler on the sheet routes tab clicks to the
     matching step; switching away from a live WC session tears it
     down first so we don't leak WebSockets.

   profile-menu.js:
   - Restored 4-item onboarding menu (Create/Import/WC plus Unlock
     when saved). Each item is a direct entry point; the tabbed modal
     lets the user pivot to any other option without closing.

Cache-buster bumped on all 8 sirius-x pages to ?v=20260908tabs.
2026-09-08 02:19:41 +02:00
Local Dev
502bcf4ad1 Ariadne 0.1.1 + Theseus fetches it from silentmode.st (no more bundling)
Ariadne 0.1.1
- install.ps1 primary TLD source: HTTPS $Indexer/api/tlds -> advertised[]
  (electrum fetchTldList / legacy fetchBcnrTlds / ariadne.config.json /
   bootstrap kept as fallbacks in that order). Silent installs no longer
   stall when WSS is blocked.
- Persistent transcript at C:\ProgramData\Ariadne\install.log; separate
  install-error.log on any uncaught exception. Top-level trap logs the full
  stack, then exit 1 so Inno/NSIS silent-install callers see a real failure.
- .iss: SetupLogging=yes; DeinitializeSetup() copies Inno's log to
  C:\ProgramData\Ariadne\inno-setup.log on every exit path.
- Version bumped to 0.1.1. sha256=be1fa8841919b321841d90f8147abe1b1d7604fe7fff6c2e271dc47c4c24a2b6

Theseus decoupled from Ariadne
- Removed nsis/installer.nsh (custom Ariadne chain-install page).
- Removed extraResources entry for AriadneResolver-Setup-*.exe.
- Removed build.nsis.include from package.json.
- Settings > System-wide resolver panel now fetches the .exe from
  https://silentmode.st/releases-manifest.json, streams the download while
  hashing, refuses to spawn on SHA-256 mismatch. 30-min in-memory manifest
  cache so opening Settings doesn't spam the endpoint. Offline gracefully
  degrades to installedVersion-only / canUpdate:false.
- "bundledVersion" IPC field kept for renderer compatibility; it now
  carries "latest advertised by the site's manifest".

Deployed to silentmode.st:
- dl.silentmode.st/AriadneResolver-Setup-0.1.1.exe (25 MB)
- silentmode.st/releases-manifest.json (schema/2, updated 2026-09-08)
- silentmode.st/releases/ + silentmode.st/tools/ show 0.1.1

Local Dev <noreply@localhost>
2026-09-08 01:27:51 +02:00
Local Dev
ef0e7e430c feat(argus): POST /api/icon-upload route for Deviant Potidaea Katalogos icons
Content-addressed image upload for the DVMB icon sidecar (see the
Deviant repo commit 'in-browser Sia upload' for the client side).

Request
  POST /api/icon-upload
  Content-Type: image/(svg+xml|png|jpeg|gif|webp|x-icon)
  body:       raw image bytes (≤ 128 KB)

Response
  { sha256, ext, path: "s3://bns/potidaea/icons/<sha256>.<ext>",
    url:  "https://navigate.st/bns/potidaea.asm/icons/<sha256>.<ext>",
    bytes }

Why no auth
  Content-addressed: the key IS the SHA-256, so a re-upload of the same
  bytes overwrites itself harmlessly, and a spammer only pollutes their
  own hash-space. The on-chain iconSha256 field (DVMB chunk 5) ties an
  entry NFT to specific bytes — a rogue upload can't hijack another
  asset's icon.

Guards
  • content-type whitelist (only real image formats)
  • 128 KB body cap enforced during read (early destroy on overflow)
  • CORS * — safe, since we never touch per-user state
  • OPTIONS preflight handled for browsers that trigger one

Deployed to silentmode 2026-09-06; live at
  https://navigate.st/api/icon-upload
Smoke: browser JS POST returned 200 with the expected content-
addressed URL; a later GET of that URL returned matching bytes.
2026-09-07 02:29:02 +02:00
Local Dev
db71b9e832 feat(registrar): registerTldWithExternalWallet + wire into portal
Adds the external-wallet variant of registerTldWithBuiltInWallet so
WizardConnect users can mint TLDs from the sirius.x portal without
first importing a seed. Same transaction shape (TREG payload, TLD-
beacon output, service-fee output) — the wallet approves the mint
(and, if needed, a one-time prep signature to satisfy the vout-0
non-token genesis-input rule) on the user's own device.

Bundle: re-exported from src/web/register-entry.js and rebuilt
(bns-register.js: 33.3 kB, cache-buster bumped to ?v=20260907tldext
on portal.html / register.html / admin/index.html).

Portal: TLD-register submit now dispatches on wallet.source, calling
registerTldWithExternalWallet with wallet.session when the user is
signed in via WC. Removed the WC-disable guard and updated the card
copy to say TLD mints also go through the wallet's signing prompt.

Uses the same landOnChain helper as registerWithExternalWallet, so
wallet-broadcasts (walletBroadcasts:true sessions) and duplicate/
mempool responses are handled identically.
2026-09-07 01:39:16 +02:00
Local Dev
d07fece6c9 feat(argus/registrar): TLD-registration TREG builder (per-TLD certificates)
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.
2026-09-07 00:18:19 +02:00
Local Dev
408f965629 feat(bns): 'p' reverse-proxy record type — mirror an upstream URL under a BNS name
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.
2026-09-07 00:18:07 +02:00
Local Dev
f15752fe4b fix(gateway): restore /api/holdings endpoint for sirius.x portal
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.
2026-09-06 22:51:54 +02:00
Local Dev
a9f2974194 navigate.st: fuzzy search fallback + hide syskypo.de from suggestions
/api/search/ now catches typos with a bounded-Levenshtein pass against
each label — max edit distance 1|2|3 depending on query length. Fuzzy
hits are labeled `matchType:"fuzzy"` and always rank below real matches
(scores 40|25|10 vs 100|70|50), so exact matches never get displaced by
a near-miss. Client shows a "closest name we found" header when the
whole result set is fuzzy, and a subtle amber "did you mean" badge per
row. Verified: hlelo → hello.bch, silntmode → silentmode.{bch,x},
pouakidis → poutakidis.de, and silentmode still returns label-exact
first with fuzzy hidden.

syskypo.de is now excluded from /api/search/ results (still reachable
via /api/name/ and /bns/…): it's a mirror of poutakidis-content and
was cluttering suggestions. Swapped it out of the placeholder too.
2026-09-06 19:58:58 +02:00
Local Dev
e47413eeb5 sirius.x: fix electrum listunspent + profile dropdown across all pages
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.
2026-09-06 18:53:17 +02:00
Local Dev
22ff0e0167 sirius/registrar: seed .vpn TLD + add to registrar UI
REG txid 03527afba52d81a2 (mempool → will confirm next block).
19 TLDs total on the chipnet TLD beacon now.
2026-09-06 17:29:37 +02:00
Local Dev
32dd9ff2ff feat(gateway,hephaestus): forward proxied headers + trust Silent Mode CA
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.
2026-08-31 16:23:43 +02:00
Local Dev
ff76e4cf94 sirius.x portal: /api/holdings endpoint + favicon paths + roadmap link
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.
2026-08-31 05:40:47 +02:00
Local Dev
6539f84b24 electrum: wire wss://silentmode.st/electrum as first-choice (port 443 reachable)
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.
2026-08-31 03:35:39 +02:00
Local Dev
fd6f112cab sirius/registrar: add .hub, .os TLDs; portal: fix getUtxosForAddresses call
.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).
2026-08-31 03:25:28 +02:00
Local Dev
083a137ed7 Ship Ariadne 0.11: Sia CDN pull + gitignore _prev/
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
2026-08-31 01:05:20 +02:00
Local Dev
4bfb7ba68f Ariadne mobile 0.10: bundle BNS starter index in APK
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.
2026-08-30 22:15:10 +02:00
Local Dev
e6f2e4b99f Theseus 0.0.7: multi-source BNS with continuous delta refresh
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
2026-08-30 15:57:01 +02:00
Local Dev
f29b82a50f BNS: Sia+Nostr snapshot mirror, Theseus warm-start, VPS playbook, electrum.bch
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.
2026-08-30 09:59:01 +02:00
Local Dev
9068eff9c7 BNS: per-TLD on-chain registry, multi-beacon indexer, enforcement gate
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).
2026-08-29 17:11:02 +02:00
Local Dev
de696a8b9f gateway: /api/tlds sourced from TLD_BEACON snapshot, add /api/registry
- /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.
2026-08-29 16:43:27 +02:00
Local Dev
5b65be2483 gateway: auto-index directory paths for s3 records
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).
2026-08-29 13:27:42 +02:00
Local Dev
f77c6558ee navigate.st: Theseus-home-style landing — search + quick links + install CTA
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.
2026-08-20 20:44:09 +02:00
Local Dev
8228bda939 navigate.st: multi-TLD label search (gateway /api/labels/<label> + client picker)
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.
2026-08-20 00:28:00 +02:00
Local Dev
45a3b595ce Hermes: optional bind to password vault (skip the second mnemonic prompt)
- 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.
2026-08-19 00:38:52 +02:00
Local Dev
44c63eca0f Hermes: wire NIP-17 messaging into Theseus, provision chipnet hermes.bch
- 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
2026-08-18 20:14:43 +02:00
Local Dev
47601fcbdb gateway+Theseus: pin BNS ip-record fetch to on-chain tls fingerprint
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).
2026-08-16 20:28:58 +02:00
Local Dev
c2578d4017 gateway: fix ip-record fetches (Host header) so coinspectrum.bch resolves
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.
2026-08-16 11:37:24 +02:00
Local Dev
b4a550ef41 Snapshot in-progress work: Ariadne mobile, Theseus password manager, Hephaestus
Several concurrent workstreams committed together as a checkpoint:

- Ariadne mobile resolver — BchFetcher/Bns/MainActivity resolution logic,
  AndroidManifest + build.ps1
- Theseus password manager — settings.html/chrome.html/settings-preload.js UI +
  main.js wiring + package.json resource; Argus password-vault.js, record-picker.js
  (+ tests) and resolver-web.d.ts
- Hephaestus — new BCH-wallet OIDC auth-proxy + Forgejo docker-compose and
  restic/S3 scripts (secrets referenced via env only; Hephaestus/.env is gitignored)
- Argus public-gateway.mjs updates
- Docs — root README, Email README/RUNBOOK, VPS access runbooks (Checkers/Deviant),
  site/hermes, WebsiteDev registry + faster-blocks, Failures/ AAAA-mangle writeup,
  Decentralized Storage map, coordination notes
- .gitignore — exclude /.keys/ and Hephaestus/.env
2026-08-14 23:17:18 +02:00
Local Dev
2c307249a1 Collision modes (BCNR/ICANN) + root TLD cert + VPS electrum-source indexer
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).
2026-08-02 11:39:00 +02:00
Local Dev
83a8fff021 resolver: echo EDNS OPT record so strict clients stop failing
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.
2026-08-02 01:19:09 +02:00
Local Dev
f159dbf816 Ariadne c5efae43: tlds.bch-driven install + BNS_POLICY, doc-aligned
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.
2026-08-01 02:06:48 +02:00
Local Dev
e69b1ad6c1 Theseus: Firefox-style lock icon, LLM search engines, reliable favicons
- 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.
2026-08-01 01:26:51 +02:00
Local Dev
0f144d94b9 Resolver 3438d558: ASCII-clean install.ps1 + multi-TLD NRPT + VPS-first electrum
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/.
2026-07-31 23:09:23 +02:00