alloy_provider/
builder.rs

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