alloy_provider/
builder.rs

1#[cfg(all(not(target_arch = "wasm32"), any(test, feature = "reqwest", feature = "hyper")))]
2use crate::layers::{Asserter, MockLayer, MockProvider};
3use crate::{
4    fillers::{
5        CachedNonceManager, ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller,
6        NonceManager, RecommendedFillers, SimpleNonceManager, TxFiller, WalletFiller,
7    },
8    provider::SendableTx,
9    Provider, RootProvider,
10};
11use alloy_chains::NamedChain;
12use alloy_network::{Ethereum, IntoWallet, Network};
13use alloy_primitives::ChainId;
14use alloy_rpc_client::{ClientBuilder, RpcClient};
15use alloy_transport::{TransportError, TransportResult};
16use std::marker::PhantomData;
17
18/// A layering abstraction in the vein of [`tower::Layer`]
19///
20/// [`tower::Layer`]: https://docs.rs/tower/latest/tower/trait.Layer.html
21pub trait ProviderLayer<P: Provider<N>, N: Network = Ethereum> {
22    /// The provider constructed by this layer.
23    type Provider: Provider<N>;
24
25    /// Wrap the given provider in the layer's provider.
26    fn layer(&self, inner: P) -> Self::Provider;
27}
28
29/// An identity layer that does nothing.
30#[derive(Clone, Copy, Debug)]
31pub struct Identity;
32
33impl<N> TxFiller<N> for Identity
34where
35    N: Network,
36{
37    type Fillable = ();
38
39    fn status(&self, _tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
40        FillerControlFlow::Finished
41    }
42
43    fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
44
45    async fn prepare<P>(
46        &self,
47        _provider: &P,
48        _tx: &N::TransactionRequest,
49    ) -> TransportResult<Self::Fillable> {
50        Ok(())
51    }
52
53    async fn fill(
54        &self,
55        _to_fill: Self::Fillable,
56        tx: SendableTx<N>,
57    ) -> TransportResult<SendableTx<N>> {
58        Ok(tx)
59    }
60}
61
62impl<P, N> ProviderLayer<P, N> for Identity
63where
64    N: Network,
65    P: Provider<N>,
66{
67    type Provider = P;
68
69    fn layer(&self, inner: P) -> Self::Provider {
70        inner
71    }
72}
73
74/// A stack of two providers.
75#[derive(Debug)]
76pub struct Stack<Inner, Outer> {
77    inner: Inner,
78    outer: Outer,
79}
80
81impl<Inner, Outer> Stack<Inner, Outer> {
82    /// Create a new `Stack`.
83    pub const fn new(inner: Inner, outer: Outer) -> Self {
84        Self { inner, outer }
85    }
86}
87
88impl<P, N, Inner, Outer> ProviderLayer<P, N> for Stack<Inner, Outer>
89where
90    N: Network,
91    P: Provider<N>,
92    Inner: ProviderLayer<P, N>,
93    Outer: ProviderLayer<Inner::Provider, N>,
94{
95    type Provider = Outer::Provider;
96
97    fn layer(&self, provider: P) -> Self::Provider {
98        let inner = self.inner.layer(provider);
99
100        self.outer.layer(inner)
101    }
102}
103
104/// A builder for constructing a [`Provider`] from various layers.
105///
106/// This type is similar to [`tower::ServiceBuilder`], with extra complication
107/// around maintaining the network and transport types.
108///
109/// The [`ProviderBuilder`] can be instantiated in two ways, using `ProviderBuilder::new()` or
110/// `ProviderBuilder::default()`.
111///
112/// `ProviderBuilder::new()` will create a new [`ProviderBuilder`] with the [`RecommendedFillers`]
113/// enabled, whereas `ProviderBuilder::default()` will instantiate it in its vanilla
114/// [`ProviderBuilder`] form i.e with no fillers enabled.
115///
116/// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html
117#[derive(Debug)]
118pub struct ProviderBuilder<L, F, N = Ethereum> {
119    layer: L,
120    filler: F,
121    network: PhantomData<fn() -> N>,
122}
123
124impl
125    ProviderBuilder<
126        Identity,
127        JoinFill<Identity, <alloy_network::Ethereum as RecommendedFillers>::RecommendedFillers>,
128        Ethereum,
129    >
130{
131    /// Create a new [`ProviderBuilder`] with the recommended filler enabled.
132    ///
133    /// Recommended fillers are preconfigured set of fillers that handle gas estimation, nonce
134    /// management, and chain-id fetching.
135    ///
136    /// Building a provider with this setting enabled will return a [`crate::fillers::FillProvider`]
137    /// with [`crate::utils::JoinedRecommendedFillers`].
138    ///
139    /// You can opt-out of using these fillers by using the `.disable_recommended_fillers()` method.
140    pub fn new() -> Self {
141        ProviderBuilder::default().with_recommended_fillers()
142    }
143
144    /// Opt-out of the recommended fillers by reseting the fillers stack in the [`ProviderBuilder`].
145    ///
146    /// This is equivalent to creating the builder using `ProviderBuilder::default()`.
147    pub fn disable_recommended_fillers(self) -> ProviderBuilder<Identity, Identity, Ethereum> {
148        ProviderBuilder { layer: self.layer, filler: Identity, network: self.network }
149    }
150
151    /// Create a new [`MockProvider`] for the [`Ethereum`] network.
152    ///
153    /// Sets the dummy RPC_URL to `http://localhost:8545`.
154    #[cfg(all(not(target_arch = "wasm32"), any(test, feature = "reqwest", feature = "hyper")))]
155    pub fn mocked() -> MockProvider<RootProvider, Ethereum> {
156        Self::mocked_network()
157    }
158
159    /// Create a new [`MockProvider`] for a specific network.
160    ///
161    /// Sets the dummy RPC_URL to `http://localhost:8545`.
162    #[cfg(all(not(target_arch = "wasm32"), any(test, feature = "reqwest", feature = "hyper")))]
163    pub fn mocked_network<Net: Network>() -> MockProvider<RootProvider<Net>, Net> {
164        let asserter = Asserter::new();
165        let layer = MockLayer::new(asserter);
166
167        let builder = ProviderBuilder::<_, _, Net>::default().layer(layer);
168
169        #[cfg(any(test, feature = "reqwest"))]
170        let mock_provider = builder.on_http("http://localhost:8545".parse().unwrap());
171
172        #[cfg(all(feature = "hyper", not(feature = "reqwest")))]
173        let mock_provider = builder.on_hyper_http("http://localhost:8545".parse().unwrap());
174
175        mock_provider
176    }
177}
178
179impl<N> Default for ProviderBuilder<Identity, Identity, N> {
180    fn default() -> Self {
181        Self { layer: Identity, filler: Identity, network: PhantomData }
182    }
183}
184
185impl<L, N: Network> ProviderBuilder<L, Identity, N> {
186    /// Add preconfigured set of layers handling gas estimation, nonce
187    /// management, and chain-id fetching.
188    pub fn with_recommended_fillers(
189        self,
190    ) -> ProviderBuilder<L, JoinFill<Identity, N::RecommendedFillers>, N>
191    where
192        N: RecommendedFillers,
193    {
194        self.filler(N::recommended_fillers())
195    }
196
197    /// Add gas estimation to the stack being built.
198    ///
199    /// See [`GasFiller`]
200    pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<Identity, GasFiller>, N> {
201        self.filler(GasFiller)
202    }
203
204    /// Add nonce management to the stack being built.
205    ///
206    /// See [`NonceFiller`]
207    pub fn with_nonce_management<M: NonceManager>(
208        self,
209        nonce_manager: M,
210    ) -> ProviderBuilder<L, JoinFill<Identity, NonceFiller<M>>, N> {
211        self.filler(NonceFiller::new(nonce_manager))
212    }
213
214    /// Add simple nonce management to the stack being built.
215    ///
216    /// See [`SimpleNonceManager`]
217    pub fn with_simple_nonce_management(
218        self,
219    ) -> ProviderBuilder<L, JoinFill<Identity, NonceFiller>, N> {
220        self.with_nonce_management(SimpleNonceManager::default())
221    }
222
223    /// Add cached nonce management to the stack being built.
224    ///
225    /// See [`CachedNonceManager`]
226    pub fn with_cached_nonce_management(
227        self,
228    ) -> ProviderBuilder<L, JoinFill<Identity, NonceFiller<CachedNonceManager>>, N> {
229        self.with_nonce_management(CachedNonceManager::default())
230    }
231
232    /// Add a chain ID filler to the stack being built. The filler will attempt
233    /// to fetch the chain ID from the provider using
234    /// [`Provider::get_chain_id`]. the first time a transaction is prepared,
235    /// and will cache it for future transactions.
236    pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<Identity, ChainIdFiller>, N> {
237        self.filler(ChainIdFiller::default())
238    }
239
240    /// Add a specific chain ID to the stack being built. The filler will
241    /// fill transactions with the provided chain ID, regardless of the chain ID
242    /// that the provider reports via [`Provider::get_chain_id`].
243    pub fn with_chain_id(
244        self,
245        chain_id: ChainId,
246    ) -> ProviderBuilder<L, JoinFill<Identity, ChainIdFiller>, N> {
247        self.filler(ChainIdFiller::new(Some(chain_id)))
248    }
249}
250
251impl<L, F, N> ProviderBuilder<L, F, N> {
252    /// Add a layer to the stack being built. This is similar to
253    /// [`tower::ServiceBuilder::layer`].
254    ///
255    /// ## Note:
256    ///
257    /// Layers are added in outer-to-inner order, as in
258    /// [`tower::ServiceBuilder`]. The first layer added will be the first to
259    /// see the request.
260    ///
261    /// [`tower::ServiceBuilder::layer`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html#method.layer
262    /// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html
263    pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
264        ProviderBuilder {
265            layer: Stack::new(layer, self.layer),
266            filler: self.filler,
267            network: PhantomData,
268        }
269    }
270
271    /// Add a transaction filler to the stack being built. Transaction fillers
272    /// are used to fill in missing fields on transactions before they are sent,
273    /// and are all joined to form the outermost layer of the stack.
274    pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
275        ProviderBuilder {
276            layer: self.layer,
277            filler: JoinFill::new(self.filler, filler),
278            network: PhantomData,
279        }
280    }
281
282    /// Change the network.
283    ///
284    /// By default, the network is `Ethereum`. This method must be called to configure a different
285    /// network.
286    ///
287    /// ```ignore
288    /// builder.network::<Arbitrum>()
289    /// ```
290    pub fn network<Net: Network>(self) -> ProviderBuilder<L, F, Net> {
291        ProviderBuilder { layer: self.layer, filler: self.filler, network: PhantomData }
292    }
293
294    /// Add a chain layer to the stack being built. The layer will set
295    /// the client's poll interval based on the average block time for this chain.
296    ///
297    /// Does nothing to the client with a local transport.
298    pub fn with_chain(
299        self,
300        chain: NamedChain,
301    ) -> ProviderBuilder<Stack<crate::layers::ChainLayer, L>, F, N> {
302        let chain_layer = crate::layers::ChainLayer::from(chain);
303        self.layer(chain_layer)
304    }
305
306    /// Finish the layer stack by providing a root [`Provider`], outputting
307    /// the final [`Provider`] type with all stack components.
308    pub fn on_provider<P>(self, provider: P) -> F::Provider
309    where
310        L: ProviderLayer<P, N>,
311        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
312        P: Provider<N>,
313        N: Network,
314    {
315        let Self { layer, filler, network: PhantomData } = self;
316        let stack = Stack::new(layer, filler);
317        stack.layer(provider)
318    }
319
320    /// Finish the layer stack by providing a root [`RpcClient`], outputting
321    /// the final [`Provider`] type with all stack components.
322    ///
323    /// This is a convenience function for
324    /// `ProviderBuilder::provider<RpcClient>`.
325    pub fn on_client(self, client: RpcClient) -> F::Provider
326    where
327        L: ProviderLayer<RootProvider<N>, N>,
328        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
329        N: Network,
330    {
331        self.on_provider(RootProvider::new(client))
332    }
333
334    /// Finish the layer stack by providing a connection string for a built-in
335    /// transport type, outputting the final [`Provider`] type with all stack
336    /// components.
337    #[doc(alias = "on_builtin")]
338    pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
339    where
340        L: ProviderLayer<RootProvider<N>, N>,
341        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
342        N: Network,
343    {
344        let client = ClientBuilder::default().connect(s).await?;
345        Ok(self.on_client(client))
346    }
347
348    /// Finish the layer stack by providing a connection string for a built-in
349    /// transport type, outputting the final [`Provider`] type with all stack
350    /// components.
351    #[deprecated = "use `connect` instead"]
352    #[doc(hidden)]
353    pub async fn on_builtin(self, s: &str) -> Result<F::Provider, TransportError>
354    where
355        L: ProviderLayer<RootProvider<N>, N>,
356        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
357        N: Network,
358    {
359        self.connect(s).await
360    }
361
362    /// Build this provider with a websocket connection.
363    #[cfg(feature = "ws")]
364    pub async fn on_ws(
365        self,
366        connect: alloy_transport_ws::WsConnect,
367    ) -> Result<F::Provider, TransportError>
368    where
369        L: ProviderLayer<RootProvider<N>, N>,
370        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
371        N: Network,
372    {
373        let client = ClientBuilder::default().ws(connect).await?;
374        Ok(self.on_client(client))
375    }
376
377    /// Build this provider with an IPC connection.
378    #[cfg(feature = "ipc")]
379    pub async fn on_ipc<T>(
380        self,
381        connect: alloy_transport_ipc::IpcConnect<T>,
382    ) -> Result<F::Provider, TransportError>
383    where
384        alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
385        L: ProviderLayer<RootProvider<N>, N>,
386        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
387        N: Network,
388    {
389        let client = ClientBuilder::default().ipc(connect).await?;
390        Ok(self.on_client(client))
391    }
392
393    /// Build this provider with an Reqwest HTTP transport.
394    #[cfg(any(test, feature = "reqwest"))]
395    pub fn on_http(self, url: reqwest::Url) -> F::Provider
396    where
397        L: ProviderLayer<crate::RootProvider<N>, N>,
398        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
399        N: Network,
400    {
401        let client = ClientBuilder::default().http(url);
402        self.on_client(client)
403    }
404
405    /// Build this provider with an Hyper HTTP transport.
406    #[cfg(feature = "hyper")]
407    pub fn on_hyper_http(self, url: url::Url) -> F::Provider
408    where
409        L: ProviderLayer<crate::RootProvider<N>, N>,
410        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
411        N: Network,
412    {
413        let client = ClientBuilder::default().hyper_http(url);
414        self.on_client(client)
415    }
416}
417
418impl<L, F, N: Network> ProviderBuilder<L, F, N> {
419    /// Add a wallet layer to the stack being built.
420    ///
421    /// See [`WalletFiller`].
422    #[allow(clippy::type_complexity)]
423    pub fn wallet<W: IntoWallet<N>>(
424        self,
425        wallet: W,
426    ) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W::NetworkWallet>>, N> {
427        self.filler(WalletFiller::new(wallet.into_wallet()))
428    }
429}
430
431#[cfg(any(test, feature = "anvil-node"))]
432type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
433
434#[cfg(any(test, feature = "anvil-node"))]
435type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
436
437// Enabled when the `anvil` feature is enabled, or when both in test and the
438// `reqwest` feature is enabled.
439#[cfg(any(test, feature = "anvil-node"))]
440impl<L, F> ProviderBuilder<L, F, Ethereum> {
441    /// Build this provider with anvil, using the BoxTransport.
442    pub fn on_anvil(self) -> F::Provider
443    where
444        F: TxFiller<Ethereum> + ProviderLayer<L::Provider, Ethereum>,
445        L: crate::builder::ProviderLayer<
446            crate::layers::AnvilProvider<crate::provider::RootProvider>,
447        >,
448    {
449        self.on_anvil_with_config(std::convert::identity)
450    }
451
452    /// Build this provider with anvil, using the BoxTransport. This
453    /// function configures a wallet backed by anvil keys, and is intended for
454    /// use in tests.
455    pub fn on_anvil_with_wallet(
456        self,
457    ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider>>::Provider
458    where
459        F: TxFiller<Ethereum> + ProviderLayer<L::Provider, Ethereum>,
460        L: crate::builder::ProviderLayer<
461            crate::layers::AnvilProvider<crate::provider::RootProvider>,
462        >,
463    {
464        self.on_anvil_with_wallet_and_config(std::convert::identity)
465            .expect("failed to build provider")
466    }
467
468    /// Build this provider with anvil, using the BoxTransport. The
469    /// given function is used to configure the anvil instance.
470    pub fn on_anvil_with_config(
471        self,
472        f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
473    ) -> F::Provider
474    where
475        F: TxFiller<Ethereum> + ProviderLayer<L::Provider, Ethereum>,
476        L: crate::builder::ProviderLayer<
477            crate::layers::AnvilProvider<crate::provider::RootProvider>,
478        >,
479    {
480        let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
481        let url = anvil_layer.endpoint_url();
482
483        let rpc_client = ClientBuilder::default().http(url);
484
485        self.layer(anvil_layer).on_client(rpc_client)
486    }
487
488    /// Build this provider with anvil, using the BoxTransport.
489    /// This calls `try_on_anvil_with_wallet_and_config` and panics on error.
490    pub fn on_anvil_with_wallet_and_config(
491        self,
492        f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
493    ) -> AnvilProviderResult<<JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider>>::Provider>
494    where
495        F: TxFiller<Ethereum> + ProviderLayer<L::Provider, Ethereum>,
496        L: crate::builder::ProviderLayer<
497            crate::layers::AnvilProvider<crate::provider::RootProvider>,
498        >,
499    {
500        let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
501        let url = anvil_layer.endpoint_url();
502
503        let wallet = anvil_layer
504            .instance()
505            .wallet()
506            .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
507
508        let rpc_client = ClientBuilder::default().http(url);
509
510        Ok(self.wallet(wallet).layer(anvil_layer).on_client(rpc_client))
511    }
512}
513
514// Copyright (c) 2019 Tower Contributors
515
516// Permission is hereby granted, free of charge, to any
517// person obtaining a copy of this software and associated
518// documentation files (the "Software"), to deal in the
519// Software without restriction, including without
520// limitation the rights to use, copy, modify, merge,
521// publish, distribute, sublicense, and/or sell copies of
522// the Software, and to permit persons to whom the Software
523// is furnished to do so, subject to the following
524// conditions:
525
526// The above copyright notice and this permission notice
527// shall be included in all copies or substantial portions
528// of the Software.
529
530// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
531// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
532// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
533// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
534// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
535// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
536// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
537// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
538// DEALINGS IN THE SOFTWARE.