Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/42-nostr-tools-transport"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes the long-running deaf-after-flap saga by replacing the daemon's relay transport. Fixes #41, implements #42.
Why
#41: after a relay flap the bunker went silently deaf — connected but no longer subscribed — and only a manual
systemctl restart nsecbunkerdrecovered it (twice on the demo, 2026-06-23 / 06-26). Root cause is structural in NDK: aNDKRelaySubscriptionregistersrelay.once("ready", …)and never re-arms, andonConnect()never replaysopenSubs, so a running subscription is dead after any reconnect. We chased it through #4 → #7 → #20 → #21 without closing it; #20's reconnect even blinded theconnectedRelays()-only watchdog to the deaf state.nostr-tools (like every other nostr signing daemon — lightning.pub, signet, FROSTR) binds resubscribe to reconnect. We don't rely on its version-fragile auto-resubscribe (regressed in 2.23.0, fix unreleased) — we own the reconnect loop so the behavior is ours, not a function of the installed version.
What
The daemon's whole relay-facing path now runs on a nostr-tools
RelayPoolinstead of NDK.lib/relay-pool.ts— owns the per-relay connect loop and re-subscribes the entire registry on every (re)connect (the lightning.pub/signet pattern). Exposeshealthy()= connected AND subscribed on the wire — the session-liveness signal the old watchdog couldn't make.publish()retries across a reconnect window.nip46/transport.ts— the NIP-46 RPC/crypto wire layer (adaptive nip04/nip44 envelope, kind:24133/24134,{id,result,error}), byte-compatible withNDKNostrRpcso lnbits and the spire are unaffected. Full bidirectional RPC (serve requests + route responses +sendRequest).backend/index.ts— the signer backend rebuilt on the transport (wasextends NDKNip46Backend); response strings match NDK's handlers exactly. Token redemption split intobackend/token-store.tsand injected, so the protocol layer is DB-free and testable.admin/index.ts— the admin RPC rebuilt on the transport;AdminRpcRequest/AdminRpcreplaceNDKRpcRequest/NDKNostrRpc(the ~16 command/validation handlers swap the import only); the connection-only watchdog becomes apool.healthy()session-liveness one.relay-reconnect.tsdeleted.run.ts— daemon drives aRelayPool(heartbeat on) for the backend;attachIndefiniteReconnectretired.Tests
The regression that was missing every prior round: a flap mid-session, asserting signing still works.
tests/relay-pool.test.ts— flap a mock relay, inbound event still delivered;healthy()semantics.tests/nip46-backend.test.ts— a real nostr-tools client drives connect/ping/get_public_key/sign_event/nip44_encrypt, survives a flap, deny path returns "Not authorized".tests/admin-transport.test.ts— admin request→reply on the right kinds survives a flap; the approval-flowsendRequest/response routing round-trips.lifecycle 7 / relay 2 / nip46 1 / admin 2green; daemon bundles clean; zero new type errors (the 3 pre-existingauthorize.tsones are untouched). Integration test (test:integration) needs a generated prisma engine, unavailable on the nixos dev box — the ACL/token logic is unchanged (moved verbatim intotoken-store.ts).Scope / follow-up
This PR swaps the transport only. Residual NDK remains in non-transport helpers — key generation (
create_new_key/create_account/profile.ts), the one-shot boot DM, thegetKeysnpub helper, type-only imports, and the standalone CLI. A follow-up PR removes@nostr-dev-kit/ndkfrom the daemon entirely (tracked separately).Refs: #41, #42, #4, #7, #9, #20, #21, #24, #25
🤖 Generated with Claude Code
Second increment of the NDK -> nostr-tools transport swap. The daemon's backend signing path no longer uses NDK at all. - nip46/transport.ts: the NIP-46 RPC wire layer over the RelayPool, replacing NDKNostrRpc. Same crypto + framing so existing clients (lnbits, the spire) are unaffected byte-for-byte: adaptive nip04/nip44 envelope (nip04 iff content has `?iv=`, fallback to the other), verify the kind:24133 signature, JSON `{id,method,params}` in / `{id,result,error}` out, signed as the held key and `#p`-tagged to the client. - backend/index.ts: the Backend is rebuilt on that transport instead of `extends NDKNip46Backend`. The dispatch + response strings match NDK's strategies exactly (connect->ack, ping->pong, get_public_key, sign_event-> signed event JSON, nip04/44 encrypt/decrypt, reject->error/"Not authorized"). The ACL hook (pubkeyAllowed -> permitCallback) is unchanged; the ACL only reads `.kind` off the sign_event payload, so a plain parsed event suffices. - backend/token-store.ts: the prisma-backed connection-token redemption (validateToken/applyToken) split out of the Backend and injected, so the protocol layer has no database dependency and is unit-testable. Logic unchanged (#24/#25 live-lifecycle semantics preserved). - run.ts: the daemon now drives a RelayPool (heartbeat on) for the backend transport instead of an NDK instance + attachIndefiniteReconnect; startKey wires the prisma applyToken. - relay-pool.ts: publish() now retries across a reconnect window — a publish that lands mid-flap rejects ("relay connection errored"), so we wait for the pool to recover and retry (relays dedupe by id; clients match by request id). Test (tests/nip46-backend.test.ts): a real nostr-tools NIP-46 client drives connect/ping/get_public_key/sign_event/nip44_encrypt through a mock relay, asserts each response, FLAPS the relay, and asserts the backend still answers — then checks the deny path returns "Not authorized". Green. lifecycle + relay suites unchanged. The admin interface (NDKRpc) + the getKeys listing still use NDK; that's the next increment. NDK remains a dependency until then. Refs: #42, #41, #25, #24, #21, #9