Skip to main content

alloy_rpc_client/
builder.rs

1use crate::{BuiltInConnectionString, ConnectionConfig, RpcClient};
2use alloy_transport::{BoxTransport, IntoBoxTransport, TransportConnect, TransportResult};
3use tower::{
4    layer::util::{Identity, Stack},
5    Layer, ServiceBuilder,
6};
7
8/// A builder for the transport  [`RpcClient`].
9///
10/// This is a wrapper around [`tower::ServiceBuilder`]. It allows you to
11/// configure middleware layers that will be applied to the transport, and has
12/// some shortcuts for common layers and transports.
13///
14/// A builder accumulates Layers, and then is finished via the
15/// [`ClientBuilder::connect`] method, which produces an RPC client.
16#[derive(Debug)]
17pub struct ClientBuilder<L> {
18    pub(crate) builder: ServiceBuilder<L>,
19}
20
21impl Default for ClientBuilder<Identity> {
22    fn default() -> Self {
23        Self { builder: ServiceBuilder::new() }
24    }
25}
26
27impl<L> ClientBuilder<L> {
28    /// Add a middleware layer to the stack.
29    ///
30    /// This is a wrapper around [`tower::ServiceBuilder::layer`]. Layers that
31    /// are added first will be called with the request first.
32    pub fn layer<M>(self, layer: M) -> ClientBuilder<Stack<M, L>> {
33        ClientBuilder { builder: self.builder.layer(layer) }
34    }
35
36    /// Create a new [`RpcClient`] with the given transport and the configured
37    /// layers.
38    ///
39    /// This collapses the [`tower::ServiceBuilder`] with the given transport via
40    /// [`tower::ServiceBuilder::service`].
41    pub fn transport<T>(self, transport: T, is_local: bool) -> RpcClient
42    where
43        L: Layer<T>,
44        T: IntoBoxTransport,
45        L::Service: IntoBoxTransport,
46    {
47        RpcClient::new_layered(is_local, transport, move |t| self.builder.service(t))
48    }
49
50    /// Convenience function to create a new [`RpcClient`] with a [`reqwest`]
51    /// HTTP transport.
52    #[cfg(feature = "reqwest")]
53    pub fn http(self, url: url::Url) -> RpcClient
54    where
55        L: Layer<alloy_transport_http::Http<reqwest::Client>>,
56        L::Service: IntoBoxTransport,
57    {
58        let transport = alloy_transport_http::Http::new(url);
59        let is_local = transport.guess_local();
60
61        self.transport(transport, is_local)
62    }
63
64    /// Convenience function to create a new [`RpcClient`] with a [`reqwest`]
65    /// HTTP transport using a pre-built `reqwest::Client`.
66    #[cfg(feature = "reqwest")]
67    pub fn http_with_client(self, client: reqwest::Client, url: url::Url) -> RpcClient
68    where
69        L: Layer<alloy_transport_http::Http<reqwest::Client>>,
70        L::Service: IntoBoxTransport,
71    {
72        let transport = alloy_transport_http::Http::with_client(client, url);
73        let is_local = transport.guess_local();
74
75        self.transport(transport, is_local)
76    }
77
78    /// Convenience function to create a new [`RpcClient`] with a `hyper` HTTP transport.
79    #[cfg(all(not(target_family = "wasm"), feature = "hyper"))]
80    pub fn hyper_http(self, url: url::Url) -> RpcClient
81    where
82        L: Layer<alloy_transport_http::HyperTransport>,
83        L::Service: IntoBoxTransport,
84    {
85        let transport = alloy_transport_http::HyperTransport::new_hyper(url);
86        let is_local = transport.guess_local();
87
88        self.transport(transport, is_local)
89    }
90
91    /// Connect a pubsub transport, producing an [`RpcClient`] with the provided
92    /// connection.
93    #[cfg(feature = "pubsub")]
94    pub async fn pubsub<C>(self, pubsub_connect: C) -> TransportResult<RpcClient>
95    where
96        C: alloy_pubsub::PubSubConnect,
97        L: Layer<alloy_pubsub::PubSubFrontend>,
98        L::Service: IntoBoxTransport,
99    {
100        let is_local = pubsub_connect.is_local();
101        let transport = pubsub_connect.into_service().await?;
102        Ok(self.transport(transport, is_local))
103    }
104
105    /// Connect a WS transport, producing an [`RpcClient`] with the provided
106    /// connection.
107    #[cfg(feature = "ws")]
108    pub async fn ws(self, ws_connect: alloy_transport_ws::WsConnect) -> TransportResult<RpcClient>
109    where
110        L: Layer<alloy_pubsub::PubSubFrontend>,
111        L::Service: IntoBoxTransport,
112    {
113        self.pubsub(ws_connect).await
114    }
115
116    /// Connect an IPC transport, producing an [`RpcClient`] with the provided
117    /// connection.
118    #[cfg(feature = "ipc")]
119    pub async fn ipc<T>(
120        self,
121        ipc_connect: alloy_transport_ipc::IpcConnect<T>,
122    ) -> TransportResult<RpcClient>
123    where
124        alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
125        L: Layer<alloy_pubsub::PubSubFrontend>,
126        L::Service: IntoBoxTransport,
127    {
128        self.pubsub(ipc_connect).await
129    }
130
131    /// Connect a transport specified by the given string, producing an [`RpcClient`].
132    ///
133    /// See [`BuiltInConnectionString`] for more information.
134    pub async fn connect(self, s: &str) -> TransportResult<RpcClient>
135    where
136        L: Layer<BoxTransport>,
137        L::Service: IntoBoxTransport,
138    {
139        self.connect_with(s.parse::<BuiltInConnectionString>()?).await
140    }
141
142    /// Connect a transport specified by the given string with custom configuration, producing an
143    /// [`RpcClient`].
144    ///
145    /// This method allows for fine-grained control over connection settings
146    /// such as authentication, retry behavior, and transport-specific options.
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
152    /// use alloy_rpc_client::{ClientBuilder, ConnectionConfig};
153    /// use alloy_transport::Authorization;
154    /// use std::time::Duration;
155    ///
156    /// let config = ConnectionConfig::new()
157    ///     .with_auth(Authorization::bearer("my-token"))
158    ///     .with_max_retries(3)
159    ///     .with_retry_interval(Duration::from_secs(2));
160    ///
161    /// let client =
162    ///     ClientBuilder::default().connect_with_config("ws://localhost:8545", config).await?;
163    /// # Ok(())
164    /// # }
165    /// ```
166    ///
167    /// See [`BuiltInConnectionString`] and [`ConnectionConfig`] for more information.
168    pub async fn connect_with_config(
169        self,
170        s: &str,
171        config: ConnectionConfig,
172    ) -> TransportResult<RpcClient>
173    where
174        L: Layer<BoxTransport>,
175        L::Service: IntoBoxTransport,
176    {
177        let transport = BuiltInConnectionString::connect_with(s, config).await?;
178        let transport = self.builder.service(transport);
179        Ok(RpcClient::new(transport.into_box_transport(), false))
180    }
181
182    /// Connect a transport, producing an [`RpcClient`].
183    pub async fn connect_with<C>(self, connect: C) -> TransportResult<RpcClient>
184    where
185        C: TransportConnect,
186        L: Layer<BoxTransport>,
187        L::Service: IntoBoxTransport,
188    {
189        let transport = connect.get_transport().await?;
190        Ok(self.transport(transport, connect.is_local()))
191    }
192}