feat: Nostr relay publish + availability subscription via nostrclient (#2) #11

Merged
padreug merged 3 commits from feat/nostr-relay-publish into main 2026-07-19 17:33:34 +00:00
Owner

Makes chatelet's Nostr layer real, over the nostrclient extension's relay manager in-process (the spirekeeper pattern). Closes the publish/availability portion of #2.

What's wired

  • _sign_and_publish — sign as operator (resolve_signer), optional NIP-44 encrypt, nostr_client.relay_manager.publish_message(...). Soft-fails (logs, returns None) on: no operator onboarded, nostrclient absent, or signer can't encrypt. Never crashes the booking flow.
  • Public events (work today): publish_listing (30402), publish_block_calendar (31923), and the 22001 availability reply — all need only sign_event.
  • Encrypted event: publish_reservation (30078) NIP-44-encrypted to the guest — soft-fails on a LocalSigner until a bunker/server-signing signer lands (lnbits #18).
  • Inbound: subscribe_inbound permanent task answers kind:22000 availability queries with kind:22001 — the client-agnostic availability path parallel to the RPC. 22000/22001 are plaintext (availability is public info), so this path works today.

Public vs encrypted — deliberate

A LocalSigner signs but can't nip44_encrypt (bunker-forward by design). So public discovery + availability publish now; encrypted delivery (reservation, and the #5 DM) sign-encrypt via the signer and soft-fail cleanly pre-bunker. Honest and strict-from-start — no vendored plaintext-nsec fallback.

Cleanups

  • Dropped the _plaintext builder scaffolding (service.py encrypts content in place) and a broken {operator_pubkey} calendar tag.

Tests

  • 20 pass (make test): +4 event-builder tests (tag/kind/content shape for 30402/30078/31923/22001).
  • Import smoke-tested under the dev LNbits venv (service, events, transport_rpcs load clean).
  • Live relay publish needs an onboarded operator + nostrclient enabled — a manual dev step, not automatable here.

Soft dependency

Adds a soft runtime dependency on the nostrclient extension (like nostrmarket) — noted in the README + event-flow.md. Absent → publish/subscribe skip, booking flow unaffected.

Follow-ups

  • #5 (NIP-17 check-in DM) — next; same encryption constraint.
  • Booking-over-Nostr-events (vs the RPC) remains out of scope; the RPC covers booking functionally.

Why PR

Money-adjacent extension → PR category. Handing off merge to you via the Forgejo UI.

🤖 Generated with Claude Code

Makes chatelet's Nostr layer real, over the **nostrclient** extension's relay manager in-process (the spirekeeper pattern). Closes the publish/availability portion of #2. ## What's wired - **`_sign_and_publish`** — sign as operator (`resolve_signer`), optional NIP-44 encrypt, `nostr_client.relay_manager.publish_message(...)`. Soft-fails (logs, returns `None`) on: no operator onboarded, nostrclient absent, or signer can't encrypt. Never crashes the booking flow. - **Public events (work today):** `publish_listing` (30402), `publish_block_calendar` (31923), and the `22001` availability reply — all need only `sign_event`. - **Encrypted event:** `publish_reservation` (30078) NIP-44-encrypted to the guest — soft-fails on a `LocalSigner` until a bunker/server-signing signer lands (lnbits #18). - **Inbound:** `subscribe_inbound` permanent task answers `kind:22000` availability queries with `kind:22001` — the client-agnostic availability path parallel to the RPC. `22000/22001` are **plaintext** (availability is public info), so this path works today. ## Public vs encrypted — deliberate A `LocalSigner` signs but can't `nip44_encrypt` (bunker-forward by design). So public discovery + availability publish now; encrypted delivery (reservation, and the #5 DM) sign-encrypt via the signer and soft-fail cleanly pre-bunker. Honest and strict-from-start — no vendored plaintext-nsec fallback. ## Cleanups - Dropped the `_plaintext` builder scaffolding (service.py encrypts `content` in place) and a broken `{operator_pubkey}` calendar tag. ## Tests - **20 pass** (`make test`): +4 event-builder tests (tag/kind/content shape for 30402/30078/31923/22001). - Import smoke-tested under the dev LNbits venv (`service`, `events`, `transport_rpcs` load clean). - Live relay publish needs an onboarded operator + nostrclient enabled — a manual dev step, not automatable here. ## Soft dependency Adds a soft runtime dependency on the **nostrclient** extension (like nostrmarket) — noted in the README + `event-flow.md`. Absent → publish/subscribe skip, booking flow unaffected. ## Follow-ups - **#5** (NIP-17 check-in DM) — next; same encryption constraint. - Booking-over-Nostr-events (vs the RPC) remains out of scope; the RPC covers booking functionally. ## Why PR Money-adjacent extension → PR category. Handing off merge to you via the Forgejo UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Wire the Nostr layer through the nostrclient extension's relay manager
in-process (spirekeeper pattern), replacing the sketch stubs:

- _sign_and_publish: sign as operator (resolve_signer), optional NIP-44
  encrypt, publish via nostr_client.relay_manager.publish_message. Soft-
  fails (logs, returns None) if no operator onboarded, nostrclient absent,
  or signer can't encrypt — never crashes the booking flow.
- publish_listing (30402) + publish_block_calendar (31923): public, work
  today (sign_event only).
- publish_reservation (30078): NIP-44 encrypted to guest; soft-fails on a
  LocalSigner until a bunker/server-signing signer lands (lnbits#18).
- subscribe_inbound: permanent task answering kind:22000 availability
  queries with kind:22001 (plaintext — availability is public info), the
  client-agnostic availability path parallel to the RPC.

events.py: drop the _plaintext scaffolding (service.py encrypts content in
place) and a broken {operator_pubkey} calendar tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
Pure tests (no signing/relays): listing tags (d/title/price/status/g/t),
reservation carries canonical amount_sat as plaintext JSON, calendar
start/end + no broken 'a' tag, availability response plaintext. 20 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
Document the in-process nostrclient publish/subscribe path, the public-vs-
encrypted split (encrypted events await bunker/server-signing), and the
soft runtime dependency on the nostrclient extension.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019VUQCfdqiLSsFS2jcGnaFD
padreug deleted branch feat/nostr-relay-publish 2026-07-19 17:33:34 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/chatelet!11
No description provided.