Add .bit nip-05 resolver via ElectrumX (namecoin)#9
Open
mstrofnone wants to merge 2 commits into
Open
Conversation
added 2 commits
May 19, 2026 23:46
resolves <local>@<host>.bit (or bare <host>.bit) to nostr pubkey + relays
via Namecoin ElectrumX servers (browser-WSS). hooks into the two existing
nip-05 entry points:
- aa.p.check_nip05 (.p check command + profile UI button)
- aa.u.setup_sheet flow (login via nip-05 identifier)
both fall through to NostrTools.nip05.queryProfile for non-.bit inputs.
implementation:
aa/fx/namecoin.js — single file, ~165 lines incl. comments
- aa.fx.is_bit_nip05(s) — cheap suffix check
- aa.fx.nip05_namecoin(s) — drop-in for nip05.queryProfile
- aa.fx.namecoin_resolve(key) — name_show with per-page TTL cache
- aa.fx.electrumx_name_show(url, name) — one-shot wss JSON-RPC
server set mirrors amethyst's DEFAULT_ELECTRUMX_SERVERS (browser-WSS subset,
4 of 6 — bare-IP entries skipped because WSS to bare IPs needs IP-SAN certs).
servers are tried in order, first non-null answer wins, results cached for
5 minutes.
wire format covers all three ifa-0001 `nostr` shapes:
- plain hex pubkey
- {pubkey, relays}
- {names: {<local>: <pubkey>}, relays?}
plus shallow one-hop `import` walking and `map` sub-label descent.
matches alphaama's existing style: vanilla JS, snake_case, Allman braces,
hangs off the global `aa.*` namespace, no build step.
The WSS receive loop in aa.fx.electrumx_name_show consumed the FIRST
inbound frame unconditionally. ElectrumX is free to push unsolicited
notifications (server.banner on connect, blockchain.headers.subscribe
deltas) BEFORE the actual response to our request, and JSON-RPC is
fundamentally bidirectional — those pushes carry a 'method' field and
no matching 'id'. When one of them arrived first, the resolver flipped
its done flag, closed the socket, and resolved with whatever .result
was on the notification (usually undefined, surfacing as null).
Net effect: a name that *exists* on Namecoin would intermittently
resolve to null, the resolver would walk to the next server, and on
unlucky days every server would push first and the name appeared
unresolvable. The 5 min positive-TTL cache then locked that null in.
Fix:
- Generate a unique outgoing id per call and capture it in the closure.
- In onmessage, ignore any frame that:
* fails to parse (keep listening until timeout)
* has a 'method' field (server-initiated notification)
* has an id that doesn't match the outgoing request
Only a frame with the matching id flips done and resolves/rejects.
- The existing timeout still rejects if no matching frame ever lands.
Also: split the namecoin_cache TTL so a null result lives only 30s while
positive results keep the existing 5 min window. Prevents one transient
miss from poisoning a name for the full positive TTL.
Tests: aa/fx/namecoin.test.html / .test.js — open in a browser, or run
via a tiny Node vm shim. The harness mocks WebSocket and replays
server.banner + headers.subscribe pushes before the real response,
asserting the resolver returns the parsed value not null. Also covers
stray frames with missing id, mismatched id, rpc-error frames,
push-only timeouts, and the negative-cache TTL split.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolve
.bitNIP-05 identifiers via NamecoinAdds a small
.bit-suffix branch to alphaama's existing NIP-05 verificationpath. Identifiers ending in
.bit(e.g.m@something.bit, or baresomething.bit) get resolved against Namecoin via ElectrumX over WSS;everything else still goes through
NostrTools.nip05.queryProfileexactlyas today.
Why
Namecoin lets people anchor a Nostr identity to a name they actually own on
chain instead of one rented from a webserver. The wire format is small,
self-contained, and already in use by other Nostr clients (Amethyst,
Nostur, Jumble, nostrmo, and others — see references below). Wiring it
into alphaama is ~165 lines of vanilla JS, matches the scrappy style of
the codebase, and doesn't touch any existing call path that isn't a
.bitaddress.How
Single new file:
aa/fx/namecoin.jsaa.fx.is_bit_nip05(s)— cheap suffix check, callers branch on itaa.fx.nip05_namecoin(s)— drop-in fornip05.queryProfile(s),returns
{pubkey, relays}ornullaa.fx.namecoin_resolve(key)— callsblockchain.name.showover wss,per-page TTL cache (5 min)
aa.fx.electrumx_name_show(url, name)— one-shot wss JSON-RPCTwo hook points (one line each, default path unchanged):
p/p.jsaa.p.check_nip05— backs the.p check <name@domain>CLIcommand and the click handler on the profile UI's NIP-05 row
u/u.jssetup flow — backs login by NIP-05 identifierServer set
Browser-WSS subset of the canonical six ElectrumX servers, mirroring
Amethyst's
DEFAULT_ELECTRUMX_SERVERS:Tried in order, first non-null wins, 5-minute cache. Bare-IP entries from
the canonical set are skipped because browser WSS needs SAN-matched certs
— they'd need TLSA TOFU pinning (separate NIP-track work) to be usable
from a browser.
Wire format
Covers the three
nostrvalue shapes specified by ifa-0001 Domain NameObject (already deployed on-chain by multiple identities today):
Plus shallow one-hop
importwalking (keeps the implementation tight —deep walking is unnecessary for the common case) and
mapsub-labeldescent for
subdomain.<name>.bitstyle identifiers. Triesd/<name>first, then
id/<name>.Style notes
This matches alphaama's scrappy style — minimum viable resolver, ~165
lines incl. comments, single file, hangs off the global
aa.*namespace,snake_case, Allman braces, no build step, no module dance. Could have
been a
n/module with amk.jsand a CLI command set, but that wouldbe three times the code for the same behaviour. Happy to expand if you'd
rather have it as a proper module.
Verifying
(any
.bitname with anostrfield will work — current testers includemstrofnone.bit,testls.bit, and several others on the chain today).Diff
+219 / -3across 4 files:aa/fx/namecoin.js(new, +219)aa/aa.js(+1 — adds the new fx file to the load list)p/p.js(+4, -2 —.bitbranch incheck_nip05)u/u.js(+2, -1 —.bitbranch in setup)References (cross-client wire-format precedent)
DEFAULT_ELECTRUMX_SERVERSinquartz/.../nip05DnsIdentifiers/namecoin/ElectrumXServer.kt