argus/OPERATIONS-electrum.md
Local Dev a45320db7d Initial commit — Silent Mode baseline (2026-07-29)
Snapshot of the decentralized-web stack at the point of the resolver+Theseus
rebuild deploy. Includes:

- Argus (BNS engine + resolver daemon + Sia gateway)
- AriadneResolver (Windows Inno installer bundle + Android APK sources +
  Firefox extension)
- TheseusNavigator (Electron browser)
- site/ (silentmode.st content, deployed to Sia at bns/silentmode/)
- design docs, roadmap, protocol spec

Secrets excluded via .gitignore: Argus/sia-s3.json, Argus/wallets.json,
Argus/ca/*.key,*.crt. Build outputs, node_modules, and bundled runtimes
also excluded.

Shipped hashes on dl.silentmode.st at this commit:
  AriadneResolver-Setup-0.1.0.exe    5bcb216eef31ea28ed767e4134ab74bd5ac69dfbd365fd249e9e6938e55c986a
  TheseusNavigator-Setup-0.0.1.exe   7c735e88bad2da3347145adba3016c8f626a18b8422289c8c6ba471972e2952b
  TheseusNavigator-0.0.1-portable.exe 008fd84445babeabb401b2bca40ea9466b24b0e6d6c85104da7640c5c5c84521
  ariadne-v0.2.apk                   635c8f04d44ef855a8390b9eeddb8cd2d50622e81cc4e5004e8daffc1bb0425c
2026-07-29 13:54:34 +02:00

4.7 KiB

Running your own Fulcrum — and why the resolver wants you to

Referenced from src/registrar-config.js (REGISTRAR.selfHosted).

The problem this solves

Public electrum servers listen on non-standard ports: 50004 and 62002. On 2026-07-27 all three configured chipnet servers went TCP-closed from one machine at the same instant, while :443 stayed perfectly reachable. That is the signature of a firewall or VPN policy, not an outage — and it is the normal condition on corporate networks, on some mobile carriers, and behind many consumer VPNs.

The consequence is worse than it sounds. Every server in the list shares the same two ports, so one rule takes down the entire resolver: no availability search, no registration, no record updates, for that user, everywhere the software runs — website, extension, Theseus, Ariadne.

A server on 443 is indistinguishable from ordinary HTTPS traffic and is essentially never blocked. One server on 443 is worth more than three more on 50004.

The second reason: not trusting one server

A single electrum server is a trusted third party. It cannot forge a registration — that needs a real CashTokens genesis whose commitment matches the name — but it can omit one. Because the protocol rule is "first valid REG wins", a hidden REG makes a taken name look available: the buyer pays, mints a duplicate certificate, and every other resolver rejects it.

ElectrumPool.unionHistory defends against exactly this by taking the union of beacon history across every server that answers, and reporting which servers were short. Union rather than quorum, because omission is the attack: a server showing you more history is giving you more truth, and two stale servers must not be able to outvote one honest one.

So running your own server is not a centralisation trade. Your server is the fast, unblockable path; the public ones stay in the list as cross-checks. The union means your own server cannot quietly hide a registration from you either.

Configuration

// src/registrar-config.js
selfHosted: "wss://bns.example.st:443",

That is all. The pool connects in parallel, prefers earlier entries, fails over automatically, and unions the history regardless of order.

Running Fulcrum

Fulcrum indexes a full node; you need both.

  1. Bitcoin Cash Node with the indexes Fulcrum needs:

    txindex=1
    server=1
    rpcuser=…
    rpcpassword=…
    chipnet=1          # or omit for mainnet
    
  2. Fulcrum, pointed at it. The part that matters here is the WSS port on 443:

    bitcoind = 127.0.0.1:8332
    rpcuser  = …
    rpcpassword = …
    ssl  = 0.0.0.0:50002
    wss  = 0.0.0.0:443          # <- the one that survives restrictive networks
    cert = /etc/letsencrypt/live/bns.example.st/fullchain.pem
    key  = /etc/letsencrypt/live/bns.example.st/privkey.pem
    
  3. A real certificate. Browsers will not open a wss:// connection with a self-signed cert, and the failure is opaque — it surfaces as a generic WebSocket error with no indication that the certificate is the cause. Use Let's Encrypt.

  4. Binding 443 needs privilege. Either setcap 'cap_net_bind_service=+ep' on the Fulcrum binary, or terminate TLS at nginx and proxy to Fulcrum's plain WS port. The nginx route is usually easier because it shares 443 with the rest of the site:

    location /electrum {
        proxy_pass http://127.0.0.1:50003;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;   # electrum connections are long-lived
    }
    

    Then selfHosted: "wss://bns.example.st:443/electrum".

Verifying it

# reachable at all?
curl -sk -o /dev/null -w "%{http_code}\n" https://bns.example.st:443

# does it speak electrum?
cd Argus && node --input-type=module -e "
import WebSocket from 'ws';
import { Electrum } from './src/lib/electrum.js';
const el = await new Electrum('wss://bns.example.st:443', WebSocket).connect();
console.log(await el.call('server.version', ['probe','1.4.2']));
console.log('height:', (await el.call('blockchain.headers.subscribe')).height);
el.close(); process.exit(0);"

The second command is the one that matters — a server can answer HTTPS and still not be a working electrum endpoint.

Note for the browser

The register page can only reach electrum over WSS. It cannot open raw TCP, so it can never talk to a P2P node directly, and it cannot use a plain-TCP electrum port. Every entry in the list must be wss://. This is also why SPV-grade verification (headers + merkle proofs over electrum) is the right target for the web client rather than direct-to-node SPV — see Decentralized.DNS/DESIGN-trustless-purchase.md.