Skip to main content

contextvm_sdk/relay/
mod.rs

1//! Nostr relay pool management.
2//!
3//! Wraps nostr-sdk's Client for relay connection, event publishing, and subscription.
4
5#[cfg(any(test, feature = "test-utils"))]
6pub mod mock;
7#[cfg(any(test, feature = "test-utils"))]
8pub use mock::MockRelayPool;
9
10use async_trait::async_trait;
11
12use crate::core::error::{Error, Result};
13use nostr_sdk::prelude::*;
14use std::sync::Arc;
15use std::time::Duration;
16
17/// Trait abstracting relay pool operations, enabling dependency injection and testing.
18#[async_trait]
19pub trait RelayPoolTrait: Send + Sync {
20    /// Connect to the given relay URLs.
21    async fn connect(&self, relay_urls: &[String]) -> Result<()>;
22    /// Disconnect from all relays.
23    async fn disconnect(&self) -> Result<()>;
24    /// Publish a pre-built event to relays.
25    async fn publish_event(&self, event: &Event) -> Result<EventId>;
26    /// Build, sign, and publish an event from a builder.
27    async fn publish(&self, builder: EventBuilder) -> Result<EventId>;
28    /// Sign an event builder without publishing.
29    async fn sign(&self, builder: EventBuilder) -> Result<Event>;
30    /// Get the signer associated with this relay pool.
31    async fn signer(&self) -> Result<Arc<dyn NostrSigner>>;
32    /// Get notifications receiver for event streaming.
33    fn notifications(&self) -> tokio::sync::broadcast::Receiver<RelayPoolNotification>;
34    /// Get the public key of the signer.
35    async fn public_key(&self) -> Result<PublicKey>;
36    /// Subscribe to events matching filters.
37    async fn subscribe(&self, filters: Vec<Filter>) -> Result<()>;
38    /// Sign and publish an event to specific relay URLs.
39    async fn publish_to(&self, urls: &[String], builder: EventBuilder) -> Result<EventId>;
40    /// Fetch events matching filters from connected relays.
41    async fn fetch_events(&self, filters: Vec<Filter>, timeout: Duration) -> Result<Vec<Event>>;
42}
43
44/// Relay pool wrapper for managing Nostr relay connections.
45pub struct RelayPool {
46    client: Arc<Client>,
47}
48
49impl RelayPool {
50    /// Create a new relay pool with the given signer.
51    pub async fn new<T>(signer: T) -> Result<Self>
52    where
53        T: IntoNostrSigner,
54    {
55        let client = Client::builder().signer(signer).build();
56
57        Ok(Self {
58            client: Arc::new(client),
59        })
60    }
61
62    /// Connect to the given relay URLs.
63    pub async fn connect(&self, relay_urls: &[String]) -> Result<()> {
64        for url in relay_urls {
65            self.client
66                .add_relay(url)
67                .await
68                .map_err(|e| Error::Transport(e.to_string()))?;
69        }
70
71        self.client.connect().await;
72
73        Ok(())
74    }
75
76    /// Disconnect from all relays.
77    pub async fn disconnect(&self) -> Result<()> {
78        self.client.disconnect().await;
79        Ok(())
80    }
81
82    /// Publish a pre-built event to relays.
83    pub async fn publish_event(&self, event: &Event) -> Result<EventId> {
84        let output = self
85            .client
86            .send_event(event)
87            .await
88            .map_err(|e| Error::Transport(e.to_string()))?;
89        Ok(output.val)
90    }
91
92    /// Build, sign, and publish an event from a builder.
93    pub async fn publish(&self, builder: EventBuilder) -> Result<EventId> {
94        let output = self
95            .client
96            .send_event_builder(builder)
97            .await
98            .map_err(|e| Error::Transport(e.to_string()))?;
99        Ok(output.val)
100    }
101
102    /// Sign an event builder without publishing.
103    pub async fn sign(&self, builder: EventBuilder) -> Result<Event> {
104        self.client
105            .sign_event_builder(builder)
106            .await
107            .map_err(|e| Error::Transport(e.to_string()))
108    }
109
110    /// Get the underlying nostr-sdk Client.
111    pub fn client(&self) -> &Arc<Client> {
112        &self.client
113    }
114
115    /// Get notifications receiver for event streaming.
116    pub fn notifications(&self) -> tokio::sync::broadcast::Receiver<RelayPoolNotification> {
117        self.client.notifications()
118    }
119
120    /// Get the public key of the signer.
121    pub async fn public_key(&self) -> Result<PublicKey> {
122        let signer = self
123            .client
124            .signer()
125            .await
126            .map_err(|e| Error::Other(e.to_string()))?;
127        signer
128            .get_public_key()
129            .await
130            .map_err(|e| Error::Other(e.to_string()))
131    }
132
133    /// Subscribe to events matching filters.
134    pub async fn subscribe(&self, filters: Vec<Filter>) -> Result<()> {
135        for filter in filters {
136            self.client
137                .subscribe(filter, None)
138                .await
139                .map_err(|e| Error::Transport(e.to_string()))?;
140        }
141        Ok(())
142    }
143
144    /// Sign and publish an event to specific relay URLs.
145    pub async fn publish_to(&self, urls: &[String], builder: EventBuilder) -> Result<EventId> {
146        let output = self
147            .client
148            .send_event_builder_to(urls, builder)
149            .await
150            .map_err(|e| Error::Transport(e.to_string()))?;
151        Ok(output.val)
152    }
153
154    /// Fetch events matching filters from connected relays.
155    pub async fn fetch_events(
156        &self,
157        filters: Vec<Filter>,
158        timeout: Duration,
159    ) -> Result<Vec<Event>> {
160        let mut all_events = Vec::new();
161        for filter in filters {
162            let events = self
163                .client
164                .fetch_events(filter, timeout)
165                .await
166                .map_err(|e| Error::Transport(e.to_string()))?;
167            all_events.extend(events);
168        }
169        Ok(all_events)
170    }
171}
172
173#[async_trait]
174impl RelayPoolTrait for RelayPool {
175    async fn connect(&self, relay_urls: &[String]) -> Result<()> {
176        RelayPool::connect(self, relay_urls).await
177    }
178
179    async fn disconnect(&self) -> Result<()> {
180        RelayPool::disconnect(self).await
181    }
182
183    async fn publish_event(&self, event: &Event) -> Result<EventId> {
184        RelayPool::publish_event(self, event).await
185    }
186
187    async fn publish(&self, builder: EventBuilder) -> Result<EventId> {
188        RelayPool::publish(self, builder).await
189    }
190
191    async fn sign(&self, builder: EventBuilder) -> Result<Event> {
192        RelayPool::sign(self, builder).await
193    }
194
195    async fn signer(&self) -> Result<Arc<dyn NostrSigner>> {
196        self.client
197            .signer()
198            .await
199            .map_err(|e| Error::Other(e.to_string()))
200    }
201
202    fn notifications(&self) -> tokio::sync::broadcast::Receiver<RelayPoolNotification> {
203        RelayPool::notifications(self)
204    }
205
206    async fn public_key(&self) -> Result<PublicKey> {
207        RelayPool::public_key(self).await
208    }
209
210    async fn subscribe(&self, filters: Vec<Filter>) -> Result<()> {
211        RelayPool::subscribe(self, filters).await
212    }
213
214    async fn publish_to(&self, urls: &[String], builder: EventBuilder) -> Result<EventId> {
215        RelayPool::publish_to(self, urls, builder).await
216    }
217
218    async fn fetch_events(&self, filters: Vec<Filter>, timeout: Duration) -> Result<Vec<Event>> {
219        RelayPool::fetch_events(self, filters, timeout).await
220    }
221}