Skip to main content

alloy_rpc_client/
builder.rs

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