nostrclient/CLAUDE.md
Padreug 115e869225
Some checks failed
ci.yml / fix: queue outgoing events when relay connection is down (pull_request) Failing after 0s
fix: queue outgoing events when relay connection is down
When all relay connections are temporarily lost, EVENT messages published
by extensions (nostrmarket, events) are now queued in a bounded deque
(max 100) instead of being silently dropped. On reconnection, queued
events are flushed to all connected relays. Dead relay queues are also
drained before restart to preserve in-flight events.

Closes aiolabs/nostrclient#1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 20:09:26 +02:00

3.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Nostrclient is an LNbits extension that acts as an always-on Nostr relay multiplexer. Multiple Nostr clients connect to a single WebSocket endpoint, which fans out requests to multiple configured Nostr relays and aggregates/deduplicates responses. It rewrites subscription IDs per-client to prevent conflicts.

Build & Development Commands

All commands use uv as the Python package manager:

make format        # Format: prettier + black + ruff --fix
make check         # All checks: mypy, pyright, black --check, ruff check, prettier --check
make test          # Run pytest (DEBUG=true PYTHONUNBUFFERED=1 uv run pytest)
make mypy          # Type check (excludes nostr/ directory)
make pre-commit    # Run pre-commit hooks on all files

Individual checks: make checkblack, make checkruff, make checkprettier.

CI runs lint then pytest with LNBITS_BACKEND_WALLET_CLASS=FakeWallet.

Architecture

Request flow: Nostr Clients → WebSocket → NostrRouter → RelayManager → Nostr Relays

Key components:

  • NostrRouter (router.py) — One per client WebSocket connection. Rewrites subscription IDs (original → hashed → original) to isolate clients. Two async tasks: _client_to_nostr (forward requests) and _nostr_to_client (deliver aggregated responses).

  • NostrClient (nostr/client/client.py) — Singleton orchestrator. Owns the RelayManager. Polls MessagePool and dispatches events via callbacks to routers.

  • RelayManager (nostr/relay_manager.py) — Manages connections to multiple relays. Caches subscriptions so new relays receive existing subscriptions. Runs health checks via check_and_restart_relays().

  • Relay (nostr/relay.py) — Individual relay WebSocket connection with retry/backoff, ping latency tracking, and error counting.

  • MessagePool (nostr/message_pool.py) — Thread-safe event aggregation with deduplication by event ID across all relays.

Hybrid threading model: Relay connections use threads (via RelayManager.open_connections()); client communication uses asyncio. The bridge is in tasks.py where subscribe_events() runs in a thread executor.

Lifecycle (__init__.py): nostrclient_start() spawns three background tasks (init relays, subscribe events, check relays). nostrclient_stop() cancels tasks, stops routers, closes the client.

API Endpoints (views_api.py)

  • REST endpoints under /api/v1/ for relay CRUD and config (admin-authenticated)
  • WebSocket endpoints: /api/v1/{ws_id} (private, encrypted ID) and /api/v1/relay (public, if enabled)

Database

Three migrations in migrations.py: relays table, config table (JSON extra field), config owner scoping. CRUD in crud.py uses LNbits database abstraction.

Code Quality Notes

  • mypy excludes nostr/* — this is a custom Nostr protocol implementation, not a third-party package
  • Ruff rules: F, E, W, I, A, C, N, UP, RUF, B
  • Frontend: Vue.js + Quasar via LNbits base templates (templates/nostrclient/index.html)
  • Pub key helpers in helpers.py normalize between hex and bech32 (npub1) formats