nostr_tools.Client

class nostr_tools.Client[source]

Bases: object

Async WebSocket client for connecting to Nostr relays.

This class provides async methods for subscribing to events, publishing events, and managing WebSocket connections with proper error handling and timeout support. It supports both clearnet and Tor relays via SOCKS5 proxy. The client implements async context manager protocol for automatic connection management.

Examples

Basic usage with context manager:

>>> relay = Relay("wss://relay.damus.io")
>>> client = Client(relay, timeout=15)
>>> async with client:
...     # Client is automatically connected
...     filter = Filter(kinds=[1], limit=10)
...     events = await fetch_events(client, filter)

Connect to Tor relay:

>>> tor_relay = Relay("wss://relay.onion")
>>> client = Client(
...     relay=tor_relay,
...     socks5_proxy_url="socks5://127.0.0.1:9050"
... )
>>> async with client:
...     # Use Tor connection
...     pass

Manual connection management:

>>> client = Client(relay)
>>> await client.connect()
>>> try:
...     # Perform operations
...     sub_id = await client.subscribe(filter)
... finally:
...     await client.disconnect()

Subscribe and publish:

>>> async with client:
...     # Subscribe to events
...     filter = Filter(kinds=[1], authors=["abc123..."])
...     sub_id = await client.subscribe(filter)
...
...     # Publish an event
...     success = await client.publish(event)
...
...     # Listen for events
...     async for message in client.listen_events(sub_id):
...         event = Event.from_dict(message[2])
...         print(event.content)
...         break
...
...     await client.unsubscribe(sub_id)
Raises:

Methods

__init__(relay[, timeout, socks5_proxy_url])

authenticate(event)

Authenticate with the relay using a NIP-42 event.

connect()

Establish WebSocket connection to the relay.

connector()

Create appropriate connector based on network type.

disconnect()

Close WebSocket connection and cleanup all resources.

from_dict(data)

Create Client from dictionary.

listen()

Listen for all messages from the relay.

listen_events(subscription_id)

Listen for events from a specific subscription.

publish(event)

Publish an event to the relay.

send_message(message)

Send a message to the relay.

session([connector])

Create HTTP session with specified connector.

subscribe(filter[, subscription_id])

Subscribe to events matching the given filter criteria.

to_dict()

Convert Client to dictionary.

unsubscribe(subscription_id)

Unsubscribe from events.

validate()

Validate the Client instance configuration.

Attributes

active_subscriptions

Get list of currently active subscription IDs.

is_connected

Check if client is currently connected to the relay.

is_valid

Check if the Client configuration is valid.

socks5_proxy_url

"socks5://host:port"

timeout

Connection and operation timeout in seconds.

relay

The Nostr relay to connect to.

relay: Relay

The Nostr relay to connect to. Must be a valid Relay instance with properly formatted WebSocket URL.

timeout: int | None = 10

Connection and operation timeout in seconds. Default is 10 seconds. Set to None for no timeout.

socks5_proxy_url: str | None = None

“socks5://host:port”

Type:

SOCKS5 proxy URL for Tor relays. Required when connecting to .onion relays. Format

__post_init__()[source]

Validate Client configuration after initialization.

This method is automatically called after the dataclass is created. It validates all configuration parameters to ensure the client is properly configured before use.

Raises:

ClientValidationError – If configuration is invalid (negative timeout, missing proxy, etc.).

Return type:

None

validate()[source]

Validate the Client instance configuration.

Performs comprehensive validation including: - Type checking for relay, timeout, and proxy URL - Timeout value validation (must be non-negative if set) - SOCKS5 proxy requirement check for Tor relays

Raises:

ClientValidationError – If any attribute is invalid: - relay must be a Relay instance - timeout must be int or None and non-negative - socks5_proxy_url must be str or None - SOCKS5 proxy URL is required for Tor relays

Return type:

None

Examples

>>> client = Client(relay, timeout=10)
>>> client.validate()  # Passes validation
>>> invalid_client = Client(relay, timeout=-5)
>>> invalid_client.validate()  # Raises ClientValidationError
property is_valid: bool

Check if the Client configuration is valid.

Returns:

True if valid, False otherwise

Return type:

bool

classmethod from_dict(data)[source]

Create Client from dictionary.

Parameters:

data (dict[str, Any]) – Dictionary containing client data

Returns:

An instance of Client

Return type:

Client

Raises:

TypeError – If data is not a dictionary

to_dict()[source]

Convert Client to dictionary.

Returns:

Dictionary representation of Client with keys:
  • relay: Relay configuration dictionary

  • timeout: Connection timeout in seconds

  • socks5_proxy_url: SOCKS5 proxy URL for Tor relays

Return type:

dict[str, Any]

Examples

>>> client = Client(relay, timeout=10)
>>> client_dict = client.to_dict()
>>> print(client_dict['timeout'])
10
async __aenter__()[source]

Async context manager entry.

Automatically connects to the relay when entering the context. This method is called when using the client in an async with statement.

Returns:

Self for use in async with statement

Return type:

Client

Examples

>>> async with Client(relay) as client:
...     # Client is automatically connected
...     await client.publish(event)
async __aexit__(exc_type, exc_val, exc_tb)[source]

Async context manager exit.

Automatically disconnects from the relay when exiting the context. This method is called when leaving the async with statement.

Parameters:
  • exc_type (type) – Exception type if an exception occurred

  • exc_val (Exception) – Exception value if an exception occurred

  • exc_tb (TracebackType) – Exception traceback if an exception occurred

Return type:

None

Examples

>>> async with Client(relay) as client:
...     # Client is automatically connected
...     pass
>>> # Client is automatically disconnected here
connector()[source]

Create appropriate connector based on network type.

Returns:

TCPConnector for clearnet or ProxyConnector for Tor

Return type:

Union[TCPConnector, ProxyConnector]

Raises:

ClientConnectionError – If SOCKS5 proxy URL required for Tor but not provided

session(connector=None)[source]

Create HTTP session with specified connector.

Parameters:

connector (TCPConnector | ProxyConnector | None) – Optional connector to use (default: auto-detect)

Returns:

HTTP session for making requests

Return type:

ClientSession

async connect()[source]

Establish WebSocket connection to the relay.

This method attempts to establish a WebSocket connection to the relay, trying both WSS (secure) and WS (insecure) protocols, preferring WSS. Creates HTTP session with appropriate connector (TCP or SOCKS5 proxy) based on relay network type.

The method is idempotent - calling it when already connected will simply return without error.

Raises:

ClientConnectionError – If connection fails for any reason: - Network unreachable - Invalid relay URL - Timeout exceeded - SOCKS5 proxy connection failed (for Tor)

Return type:

None

Examples

>>> client = Client(relay)
>>> await client.connect()
>>> print(client.is_connected)
True
>>> # With timeout handling
>>> try:
...     await client.connect()
... except ClientConnectionError as e:
...     print(f"Connection failed: {e}")
async disconnect()[source]

Close WebSocket connection and cleanup all resources.

This method properly closes the WebSocket connection, HTTP session, and clears all active subscriptions. It’s safe to call even if not connected.

Resources cleaned up: - WebSocket connection closed gracefully - HTTP session terminated - All subscription state cleared - Internal buffers released

Examples

>>> await client.connect()
>>> # ... perform operations ...
>>> await client.disconnect()
>>> print(client.is_connected)
False
Return type:

None

async send_message(message)[source]

Send a message to the relay.

Parameters:

message (list[Any]) – Message to send as a list (will be JSON encoded)

Raises:
Return type:

None

async subscribe(filter, subscription_id=None)[source]

Subscribe to events matching the given filter criteria.

Sends a REQ message to the relay with the specified filter. The relay will send all stored events matching the filter, followed by real-time events as they arrive. A unique subscription ID is generated if not provided.

Parameters:
  • filter (Filter) – Event filter criteria defining which events to receive. Can filter by kinds, authors, tags, time range, etc.

  • subscription_id (Optional[str]) – Custom subscription ID. If None, a UUID4 will be automatically generated. Useful for tracking multiple subscriptions.

Returns:

Subscription ID used to identify events and manage the subscription.

Use this ID with listen_events() and unsubscribe().

Return type:

str

Raises:

Examples

Subscribe with auto-generated ID:

>>> filter = Filter(kinds=[1], limit=10)
>>> sub_id = await client.subscribe(filter)
>>> print(f"Subscribed with ID: {sub_id}")

Subscribe with custom ID:

>>> filter = Filter(authors=["abc123..."])
>>> sub_id = await client.subscribe(filter, "my-custom-sub")
>>> async for msg in client.listen_events(sub_id):
...     process_event(msg)

Multiple subscriptions:

>>> # Subscribe to different event types
>>> notes_sub = await client.subscribe(Filter(kinds=[1]))
>>> reactions_sub = await client.subscribe(Filter(kinds=[7]))
async unsubscribe(subscription_id)[source]

Unsubscribe from events.

Parameters:

subscription_id (str) – Subscription ID to close

Raises:
Return type:

None

async publish(event)[source]

Publish an event to the relay.

Sends an EVENT message to the relay with the provided event. Waits for an OK response from the relay to determine if the event was accepted. The event must be properly signed and validated before publishing. Returns on success or raises ClientPublicationError on failure.

Parameters:

event (Event) – Event to publish. Must be a valid, signed Event instance. The event will be validated by the relay.

Returns:

Returns nothing on success. Raises ClientPublicationError on failure.

Return type:

None

Raises:
  • ClientPublicationError – If the relay rejects the event or no OK response is received. Rejection can occur due to: - Invalid signature - Spam/rate limiting - Relay policy violations - Duplicate event - No response from relay

  • ClientConnectionError – If not connected or communication fails.

  • TypeError – If event is not an Event instance.

Examples

Publish a text note:

>>> from nostr_tools import generate_event
>>> event_dict = generate_event(
...     private_key, public_key,
...     kind=1, tags=[], content="Hello Nostr!"
... )
>>> event = Event.from_dict(event_dict)
>>> try:
...     await client.publish(event)
...     print("Event published successfully!")
... except ClientPublicationError as e:
...     print(f"Event rejected: {e}")

Publish with error handling:

>>> try:
...     await client.publish(event)
...     print("Success!")
... except ClientPublicationError as e:
...     print(f"Publish failed: {e}")
... except ClientConnectionError as e:
...     print(f"Connection error: {e}")
async authenticate(event)[source]

Authenticate with the relay using a NIP-42 event.

Parameters:

event (Event) – Authentication event (must be kind 22242)

Returns:

True if authentication successful, False otherwise

Return type:

bool

Raises:
async listen()[source]

Listen for all messages from the relay.

This async generator continuously listens for messages from the relay and yields them as they arrive. Messages are automatically parsed from JSON format. The generator continues until timeout, connection closure, or error.

Message types from relay: - [“EVENT”, subscription_id, event_dict]: New event matching subscription - [“EOSE”, subscription_id]: End of stored events for subscription - [“OK”, event_id, success, message]: Response to EVENT/AUTH message - [“NOTICE”, message]: Relay notification or error message - [“CLOSED”, subscription_id, message]: Subscription closed by relay

Yields:

list[Any]

Relay messages as parsed JSON lists. Format depends on

message type (see above).

Raises:

ClientConnectionError – If not connected, connection fails, or encounters WebSocket errors.

Return type:

AsyncGenerator[list[Any], None]

Examples

Listen to all relay messages:

>>> async for message in client.listen():
...     msg_type = message[0]
...     if msg_type == "EVENT":
...         sub_id, event_dict = message[1], message[2]
...         event = Event.from_dict(event_dict)
...         print(f"Received event: {event.content}")
...     elif msg_type == "EOSE":
...         print(f"End of stored events for {message[1]}")
...     elif msg_type == "NOTICE":
...         print(f"Relay notice: {message[1]}")

With timeout handling:

>>> try:
...     async for message in client.listen():
...         process_message(message)
... except asyncio.TimeoutError:
...     print("No messages received within timeout")
async listen_events(subscription_id)[source]

Listen for events from a specific subscription.

This method filters messages to only yield events from the specified subscription until the subscription ends.

Parameters:

subscription_id (str) – Subscription to listen to

Yields:

list[Any] – Events received from the subscription

Raises:

TypeError – If subscription_id is not a string

Return type:

AsyncGenerator[list[Any], None]

property is_connected: bool

Check if client is currently connected to the relay.

This property checks if the WebSocket connection is active and not closed.

Returns:

True if connected and WebSocket is open, False otherwise.

Return type:

bool

Examples

>>> if client.is_connected:
...     await client.publish(event)
... else:
...     await client.connect()
>>> async with client:
...     assert client.is_connected  # True inside context
>>> assert not client.is_connected  # False after context exit
property active_subscriptions: list[str]

Get list of currently active subscription IDs.

Returns list of subscription IDs that are active (not closed/unsubscribed). Useful for tracking and managing multiple concurrent subscriptions.

Returns:

List of subscription IDs that are currently active.

Empty list if no active subscriptions.

Return type:

list[str]

Examples

>>> sub1 = await client.subscribe(Filter(kinds=[1]))
>>> sub2 = await client.subscribe(Filter(kinds=[7]))
>>> print(client.active_subscriptions)
['sub_id_1', 'sub_id_2']
>>> await client.unsubscribe(sub1)
>>> print(client.active_subscriptions)
['sub_id_2']
>>> # Close all active subscriptions
>>> for sub_id in client.active_subscriptions:
...     await client.unsubscribe(sub_id)
__init__(relay, timeout=10, socks5_proxy_url=None)
Parameters:
  • relay (Relay)

  • timeout (int | None)

  • socks5_proxy_url (str | None)

Return type:

None