Skip to main content
The RelayPool struct manages connections to multiple Nostr relays, handles subscriptions, and provides automatic reconnection with keepalive functionality.

Struct fields

relays
HashMap<String, Relay>
Map of relay URLs to their corresponding Relay connection objects
subscriptions
HashMap<String, Subscription>
Map of subscription IDs to Subscription objects tracking active Nostr filters

Methods

new()

Creates a new RelayPool instance with no connections.
pub fn new() -> Self
return
RelayPool
A new RelayPool instance with empty relay and subscription maps

add_url()

Adds a new relay connection to the pool.
pub fn add_url(
    &mut self,
    url: String,
    wake_up: impl Fn() + Send + Sync + 'static,
) -> Result<()>
url
String
required
WebSocket URL of the relay (e.g., “wss://relay.damus.io”)
wake_up
impl Fn() + Send + Sync + 'static
required
Callback function to trigger UI updates when events are received
return
Result<()>
Ok(()) if successful, or an error if the connection fails

remove_url()

Removes a relay connection from the pool.
pub fn remove_url(&mut self, url: &str) -> Option<Relay>
url
&str
required
URL of the relay to remove
return
Option<Relay>
The removed Relay object if it existed, or None

add_subscription()

Adds a new Nostr subscription to all connected relays.
pub fn add_subscription(&mut self, sub: Subscription) -> Result<()>
sub
Subscription
required
Subscription object containing an ID and Nostr filters to apply
return
Result<()>
Ok(()) if the subscription was sent successfully, or an error

Behavior

  1. Stores the subscription in the pool’s subscription map
  2. Serializes the subscription as a Nostr REQ message
  3. Sends the subscription to all connected relays
  4. Automatically resends subscriptions when relays reconnect

send()

Sends a WebSocket message to all connected relays.
pub fn send(&mut self, message: ewebsock::WsMessage) -> Result<()>
message
ewebsock::WsMessage
required
WebSocket message to broadcast (typically Text containing JSON)
return
Result<()>
Ok(()) if sent to all connected relays successfully
Only sends to relays with status RelayStatus::Connected. Disconnected relays are skipped.

try_recv()

Attempts to receive pending messages from all relays.
pub fn try_recv(&mut self) -> Option<(String, String)>
return
Option<(String, String)>
A tuple of (relay_url, message_text) if a message is available, or None

Behavior

  • Polls all relays for pending WebSocket events
  • Returns the first available message
  • Automatically handles connection events (Opened) by resubscribing
  • Responds to Ping messages with Pong automatically

keepalive()

Performs periodic maintenance: reconnects disconnected relays and sends keepalive pings.
pub fn keepalive(&mut self, wake_up: impl Fn() + Send + Sync + Clone + 'static)
wake_up
impl Fn() + Send + Sync + Clone + 'static
required
Callback function to wake up the UI thread when events occur

Behavior

This method should be called regularly (e.g., in the UI update loop):
  1. Reconnection (every 5 seconds): Attempts to reconnect any disconnected relays
  2. Keepalive pings (every 30 seconds): Sends ping messages to all connected relays to keep connections alive
The reconnection interval is defined by the RELAY_RECONNECT_SECONDS constant (5 seconds).

send_auth()

Sends an authentication event (NIP-42) to a specific relay.
pub fn send_auth(&mut self, relay_url: &str, event: Event) -> Result<()>
relay_url
&str
required
URL of the relay to authenticate with
event
Event
required
Signed authentication event (kind 22242)
return
Result<()>
Ok(()) if the AUTH message was sent successfully

get_challenge()

Retrieves the authentication challenge string from a relay.
pub fn get_challenge(&self, relay_url: &str) -> Option<String>
relay_url
&str
required
URL of the relay to get the challenge from
return
Option<String>
The challenge string if the relay has sent one, or None

is_key_authenticated()

Checks if a public key has been authenticated with a specific relay.
pub fn is_key_authenticated(&self, relay_url: &str, pubkey: &str) -> bool
relay_url
&str
required
URL of the relay to check
pubkey
&str
required
Public key (hex string) to check authentication status for
return
bool
true if the key is authenticated with this relay, false otherwise

send_subscription_to_relay()

Sends a specific subscription to a specific relay.
pub fn send_subscription_to_relay(&mut self, relay_url: &str, sub_id: &str) -> Result<()>
relay_url
&str
required
URL of the relay to send the subscription to
sub_id
&str
required
ID of the subscription to send
return
Result<()>
Ok(()) if successful, or an error if the relay or subscription doesn’t exist

Example usage

use hoot::relay::RelayPool;
use hoot::relay::Subscription;
use nostr::Filter;

// Create a new relay pool
let mut pool = RelayPool::new();

// Add relays with a wake-up callback
let wake_up = || { /* trigger UI repaint */ };
pool.add_url("wss://relay.damus.io".to_string(), wake_up.clone())?;
pool.add_url("wss://nos.lol".to_string(), wake_up.clone())?;

// Create a subscription for mail events
let filter = Filter::new()
    .kind(nostr::Kind::GiftWrap)
    .pubkey(my_pubkey);

let subscription = Subscription {
    id: "mail-sub".to_string(),
    filters: vec![filter],
};

pool.add_subscription(subscription)?;

// In your main loop:
loop {
    // Handle keepalive and reconnection
    pool.keepalive(wake_up.clone());
    
    // Process incoming messages
    while let Some((relay_url, message)) = pool.try_recv() {
        println!("Received from {}: {}", relay_url, message);
        // Process the message...
    }
}

Authentication flow (NIP-42)

For relays requiring authentication:
// 1. Relay sends AUTH challenge (handled automatically)
if let Some(challenge) = pool.get_challenge(relay_url) {
    // 2. Create authentication event
    let auth_event = AccountManager::create_auth_event(
        &keys,
        relay_url,
        &challenge
    )?;
    
    // 3. Send AUTH response
    pool.send_auth(relay_url, auth_event)?;
    
    // 4. Mark key as authenticated
    pool.add_authenticated_key(relay_url, keys.public_key().to_hex());
    
    // 5. Retry any pending subscriptions
    let pending_subs = pool.take_pending_auth_subscriptions(relay_url);
    for sub_id in pending_subs {
        pool.send_subscription_to_relay(relay_url, &sub_id)?;
    }
}

Constants

RELAY_RECONNECT_SECONDS
u64
Interval in seconds between reconnection attempts: 5

Build docs developers (and LLMs) love