Skip to main content

alloy_provider/
builder.rs

1use crate::{
2    fillers::{
3        BlobGasEstimator, BlobGasFiller, CachedNonceManager, ChainIdFiller, FillerControlFlow,
4        GasFiller, JoinFill, NonceFiller, NonceManager, RecommendedFillers, SimpleNonceManager,
5        TxFiller, WalletFiller,
6    },
7    layers::{BlockIdLayer, CallBatchLayer, ChainLayer},
8    provider::SendableTx,
9    utils::Eip1559Estimator,
10    Provider, RootProvider,
11};
12use alloy_chains::NamedChain;
13use alloy_network::{Ethereum, IntoWallet, Network};
14use alloy_primitives::ChainId;
15use alloy_rpc_client::{ClientBuilder, ConnectionConfig, RpcClient};
16use alloy_transport::{TransportConnect, TransportError, TransportResult};
17use std::marker::PhantomData;
18
19/// A layering abstraction in the vein of [`tower::Layer`]
20///
21/// [`tower::Layer`]: https://docs.rs/tower/latest/tower/trait.Layer.html
22pub trait ProviderLayer<P: Provider<N>, N: Network = Ethereum> {
23    /// The provider constructed by this layer.
24    type Provider: Provider<N>;
25
26    /// Wrap the given provider in the layer's provider.
27    fn layer(&self, inner: P) -> Self::Provider;
28}
29
30/// An identity layer that does nothing.
31#[derive(Clone, Copy, Debug)]
32pub struct Identity;
33
34impl<N> TxFiller<N> for Identity
35where
36    N: Network,
37{
38    type Fillable = ();
39
40    fn status(&self, _tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
41        FillerControlFlow::Finished
42    }
43
44    fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
45
46    async fn prepare<P>(
47        &self,
48        _provider: &P,
49        _tx: &N::TransactionRequest,
50    ) -> TransportResult<Self::Fillable> {
51        Ok(())
52    }
53
54    async fn fill(
55        &self,
56        _to_fill: Self::Fillable,
57        tx: SendableTx<N>,
58    ) -> TransportResult<SendableTx<N>> {
59        Ok(tx)
60    }
61}
62
63impl<P, N> ProviderLayer<P, N> for Identity
64where
65    N: Network,
66    P: Provider<N>,
67{
68    type Provider = P;
69
70    fn layer(&self, inner: P) -> Self::Provider {
71        inner
72    }
73}
74
75/// A stack of two providers.
76#[derive(Debug)]
77pub struct Stack<Inner, Outer> {
78    inner: Inner,
79    outer: Outer,
80}
81
82impl<Inner, Outer> Stack<Inner, Outer> {
83    /// Create a new `Stack`.
84    pub const fn new(inner: Inner, outer: Outer) -> Self {
85        Self { inner, outer }
86    }
87}
88
89impl<P, N, Inner, Outer> ProviderLayer<P, N> for Stack<Inner, Outer>
90where
91    N: Network,
92    P: Provider<N>,
93    Inner: ProviderLayer<P, N>,
94    Outer: ProviderLayer<Inner::Provider, N>,
95{
96    type Provider = Outer::Provider;
97
98    fn layer(&self, provider: P) -> Self::Provider {
99        let inner = self.inner.layer(provider);
100
101        self.outer.layer(inner)
102    }
103}
104
105/// A builder for constructing a [`Provider`] from various layers.
106///
107/// This type is similar to [`tower::ServiceBuilder`], with extra complication
108/// around maintaining the network and transport types.
109///
110/// The [`ProviderBuilder`] can be instantiated in two ways, using `ProviderBuilder::new()` or
111/// `ProviderBuilder::default()`.
112///
113/// `ProviderBuilder::new()` will create a new [`ProviderBuilder`] with the [`RecommendedFillers`]
114/// enabled, whereas `ProviderBuilder::default()` will instantiate it in its vanilla
115/// [`ProviderBuilder`] form i.e with no fillers enabled.
116///
117/// # Filler ordering
118///
119/// For Ethereum, `new()` already includes gas, blob-gas, cached-nonce, and chain-ID fillers.
120/// Methods such as [`with_gas_estimation`](Self::with_gas_estimation) append another filler; they
121/// do not replace one from the recommended set. Start with
122/// [`disable_recommended_fillers`](Self::disable_recommended_fillers) (or `default()`) when
123/// assembling those fillers explicitly.
124///
125/// [`network`](Self::network) replaces the entire filler stack with the target network's
126/// recommended fillers, so select the network before adding custom fillers or a wallet. Fillers
127/// that modify a transaction request should be added before [`wallet`](Self::wallet), which signs
128/// the request and turns it into an envelope.
129///
130/// ```
131/// use alloy_provider::ProviderBuilder;
132///
133/// // Use the complete recommended set as-is.
134/// let _recommended = ProviderBuilder::new();
135///
136/// // Or opt out before assembling an explicit set; these calls append in order.
137/// let _custom = ProviderBuilder::new()
138///     .disable_recommended_fillers()
139///     .with_gas_estimation()
140///     .with_cached_nonce_management()
141///     .fetch_chain_id();
142/// ```
143///
144/// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html
145#[derive(Debug)]
146pub struct ProviderBuilder<L, F, N = Ethereum> {
147    layer: L,
148    filler: F,
149    network: PhantomData<fn() -> N>,
150}
151
152impl
153    ProviderBuilder<
154        Identity,
155        JoinFill<Identity, <Ethereum as RecommendedFillers>::RecommendedFillers>,
156        Ethereum,
157    >
158{
159    /// Create a new [`ProviderBuilder`] with the recommended filler enabled.
160    ///
161    /// For Ethereum, the recommended set handles gas and blob-gas estimation, cached nonce
162    /// management, and chain-ID fetching.
163    ///
164    /// Building a provider with this setting enabled will return a [`crate::fillers::FillProvider`]
165    /// with [`crate::utils::JoinedRecommendedFillers`].
166    ///
167    /// You can opt-out of using these fillers by using the `.disable_recommended_fillers()` method.
168    pub fn new() -> Self {
169        ProviderBuilder::default().with_recommended_fillers()
170    }
171
172    /// Opt-out of the recommended fillers by resetting the fillers stack in the
173    /// [`ProviderBuilder`].
174    ///
175    /// This is equivalent to creating the builder using `ProviderBuilder::default()`.
176    pub fn disable_recommended_fillers(self) -> ProviderBuilder<Identity, Identity, Ethereum> {
177        ProviderBuilder { layer: self.layer, filler: Identity, network: self.network }
178    }
179}
180
181impl<N> Default for ProviderBuilder<Identity, Identity, N> {
182    fn default() -> Self {
183        Self { layer: Identity, filler: Identity, network: PhantomData }
184    }
185}
186
187impl ProviderBuilder<Identity, Identity, Ethereum> {
188    /// Create a new [`ProviderBuilder`] with the [`RecommendedFillers`] for the provided
189    /// [`Network`].
190    pub fn new_with_network<Net: RecommendedFillers>(
191    ) -> ProviderBuilder<Identity, JoinFill<Identity, Net::RecommendedFillers>, Net> {
192        ProviderBuilder {
193            layer: Identity,
194            filler: JoinFill::new(Identity, Net::recommended_fillers()),
195            network: PhantomData,
196        }
197    }
198}
199
200impl<L, N: Network> ProviderBuilder<L, Identity, N> {
201    /// Add the network's preconfigured set of transaction fillers.
202    pub fn with_recommended_fillers(
203        self,
204    ) -> ProviderBuilder<L, JoinFill<Identity, N::RecommendedFillers>, N>
205    where
206        N: RecommendedFillers,
207    {
208        self.filler(N::recommended_fillers())
209    }
210}
211
212impl<L, F, N> ProviderBuilder<L, F, N> {
213    /// Apply a function to this builder.
214    ///
215    /// This is useful for extracting reusable builder-style helper functions without manually
216    /// reconstructing the [`ProviderBuilder`].
217    pub fn apply<T>(self, f: impl FnOnce(Self) -> T) -> T {
218        f(self)
219    }
220
221    /// Map the layer stack to a new type.
222    ///
223    /// This is useful for customizing or replacing the accumulated layers in a reusable helper.
224    pub fn map_layer<L2>(self, f: impl FnOnce(L) -> L2) -> ProviderBuilder<L2, F, N> {
225        ProviderBuilder { layer: f(self.layer), filler: self.filler, network: PhantomData }
226    }
227
228    /// Map the filler stack to a new type.
229    ///
230    /// This is useful for customizing or replacing the accumulated fillers in a reusable helper.
231    pub fn map_filler<F2>(self, f: impl FnOnce(F) -> F2) -> ProviderBuilder<L, F2, N> {
232        ProviderBuilder { layer: self.layer, filler: f(self.filler), network: PhantomData }
233    }
234
235    /// Add a layer to the stack being built. This is similar to
236    /// [`tower::ServiceBuilder::layer`].
237    ///
238    /// ## Note:
239    ///
240    /// Layers are added in outer-to-inner order, as in
241    /// [`tower::ServiceBuilder`]. The first layer added will be the first to
242    /// see the request.
243    ///
244    /// [`tower::ServiceBuilder::layer`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html#method.layer
245    /// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html
246    pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
247        self.map_layer(|current| Stack::new(layer, current))
248    }
249
250    /// Add a transaction filler to the stack being built. Transaction fillers
251    /// are used to fill in missing fields on transactions before they are sent,
252    /// and are all joined to form the outermost layer of the stack.
253    pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
254        self.map_filler(|current| JoinFill::new(current, filler))
255    }
256
257    /// Change the network.
258    ///
259    /// By default, the network is `Ethereum`. This method must be called to configure a different
260    /// network.
261    ///
262    /// This replaces the filler stack with the target network's recommended fillers. Any custom
263    /// fillers should be added **after** calling `.network()`.
264    ///
265    /// ```ignore
266    /// builder.network::<Arbitrum>()
267    /// ```
268    pub fn network<Net: RecommendedFillers>(
269        self,
270    ) -> ProviderBuilder<L, JoinFill<Identity, Net::RecommendedFillers>, Net> {
271        ProviderBuilder {
272            layer: self.layer,
273            filler: JoinFill::new(Identity, Net::recommended_fillers()),
274            network: PhantomData,
275        }
276    }
277
278    /// Add a chain layer to the stack being built. The layer will set
279    /// the client's poll interval based on the average block time for this chain.
280    ///
281    /// Does nothing to the client with a local transport.
282    pub fn with_chain(self, chain: NamedChain) -> ProviderBuilder<Stack<ChainLayer, L>, F, N> {
283        self.layer(ChainLayer::new(chain))
284    }
285
286    // --- Fillers ---
287
288    /// Add blob gas estimation to the stack being built.
289    ///
290    /// See [`BlobGasFiller`] for more information.
291    pub fn with_blob_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
292        self.filler(BlobGasFiller::default())
293    }
294
295    /// Add blob gas estimation to the stack being built, using the provided estimator.
296    ///
297    /// See [`BlobGasFiller`] and [`BlobGasEstimator`] for more information.
298    pub fn with_blob_gas_estimator(
299        self,
300        estimator: BlobGasEstimator,
301    ) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
302        self.filler(BlobGasFiller { estimator })
303    }
304
305    /// Add gas estimation to the stack being built.
306    ///
307    /// See [`GasFiller`] for more information.
308    pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
309        self.filler(GasFiller::default())
310    }
311
312    /// Add EIP-1559 gas estimation to the stack being built, using the provided estimator.
313    ///
314    /// See [`GasFiller`] and [`Eip1559Estimator`] for more information.
315    pub fn with_eip1559_estimator(
316        self,
317        estimator: Eip1559Estimator,
318    ) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
319        self.filler(GasFiller { estimator })
320    }
321
322    /// Add nonce management to the stack being built.
323    ///
324    /// See [`NonceFiller`] for more information.
325    pub fn with_nonce_management<M: NonceManager>(
326        self,
327        nonce_manager: M,
328    ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<M>>, N> {
329        self.filler(NonceFiller::new(nonce_manager))
330    }
331
332    /// Add simple nonce management to the stack being built.
333    ///
334    /// See [`SimpleNonceManager`] for more information.
335    pub fn with_simple_nonce_management(
336        self,
337    ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<SimpleNonceManager>>, N> {
338        self.with_nonce_management(SimpleNonceManager::default())
339    }
340
341    /// Add cached nonce management to the stack being built.
342    ///
343    /// See [`CachedNonceManager`] for more information.
344    pub fn with_cached_nonce_management(
345        self,
346    ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<CachedNonceManager>>, N> {
347        self.with_nonce_management(CachedNonceManager::default())
348    }
349
350    /// Add a chain ID filler to the stack being built. The filler will attempt
351    /// to fetch the chain ID from the provider using
352    /// [`Provider::get_chain_id`]. the first time a transaction is prepared,
353    /// and will cache it for future transactions.
354    pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
355        self.filler(ChainIdFiller::default())
356    }
357
358    /// Add a specific chain ID to the stack being built. The filler will
359    /// fill transactions with the provided chain ID, regardless of the chain ID
360    /// that the provider reports via [`Provider::get_chain_id`].
361    pub fn with_chain_id(
362        self,
363        chain_id: ChainId,
364    ) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
365        self.filler(ChainIdFiller::new(Some(chain_id)))
366    }
367
368    /// Add a wallet layer to the stack being built.
369    ///
370    /// See [`WalletFiller`].
371    pub fn wallet<W: IntoWallet<N>>(
372        self,
373        wallet: W,
374    ) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W::NetworkWallet>>, N>
375    where
376        N: Network,
377    {
378        self.filler(WalletFiller::new(wallet.into_wallet()))
379    }
380
381    // --- Layers ---
382
383    /// Aggregate multiple `eth_call` requests into a single batch request using Multicall3.
384    ///
385    /// See [`CallBatchLayer`] for more information.
386    pub fn with_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
387        self.layer(CallBatchLayer::new())
388    }
389
390    /// Aggregate multiple `eth_call` requests with block number queries done by calling Arbsym
391    /// precompile.
392    ///
393    /// See [`CallBatchLayer`] for more information.
394    pub fn with_arbitrum_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
395        self.layer(CallBatchLayer::new().arbitrum_compat())
396    }
397
398    /// Add response caching to the stack being built with the specified maximum cache size.
399    ///
400    /// See [`CacheLayer`](crate::layers::CacheLayer) for more information.
401    #[cfg(not(target_family = "wasm"))]
402    pub fn with_caching(
403        self,
404        max_items: u32,
405    ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
406        self.layer(crate::layers::CacheLayer::new(max_items))
407    }
408
409    /// Add response caching to the stack being built with a default cache size of 100 items.
410    ///
411    /// See [`CacheLayer`](crate::layers::CacheLayer) for more information.
412    #[cfg(not(target_family = "wasm"))]
413    pub fn with_default_caching(
414        self,
415    ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
416        self.with_caching(100)
417    }
418
419    /// Set a default [`BlockId`] for `eth_call` and `eth_estimateGas`.
420    ///
421    /// [`BlockId`]: alloy_eips::BlockId
422    pub fn with_default_block(
423        self,
424        block_id: alloy_eips::BlockId,
425    ) -> ProviderBuilder<Stack<BlockIdLayer, L>, F, N> {
426        self.layer(BlockIdLayer::new(block_id))
427    }
428
429    // --- Build to Provider ---
430
431    /// Finish the layer stack by providing a root [`Provider`], outputting
432    /// the final [`Provider`] type with all stack components.
433    pub fn connect_provider<P>(self, provider: P) -> F::Provider
434    where
435        L: ProviderLayer<P, N>,
436        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
437        P: Provider<N>,
438        N: Network,
439    {
440        let Self { layer, filler, network: PhantomData } = self;
441        let stack = Stack::new(layer, filler);
442        stack.layer(provider)
443    }
444
445    /// Finish the layer stack by providing a root [`RpcClient`], outputting
446    /// the final [`Provider`] type with all stack components.
447    ///
448    /// This is a convenience function for
449    /// `ProviderBuilder::on_provider(RootProvider::new(client))`.
450    pub fn connect_client(self, client: RpcClient) -> F::Provider
451    where
452        L: ProviderLayer<RootProvider<N>, N>,
453        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
454        N: Network,
455    {
456        self.connect_provider(RootProvider::new(client))
457    }
458
459    /// Finish the layer stack by providing a [`RpcClient`] that mocks responses, outputting
460    /// the final [`Provider`] type with all stack components.
461    ///
462    /// This is a convenience function for
463    /// `ProviderBuilder::on_client(RpcClient::mocked(asserter))`.
464    pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
465    where
466        L: ProviderLayer<RootProvider<N>, N>,
467        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
468        N: Network,
469    {
470        self.connect_client(RpcClient::mocked(asserter))
471    }
472
473    /// Finish the layer stack by providing a connection string for a built-in
474    /// transport type, outputting the final [`Provider`] type with all stack
475    /// components.
476    #[doc(alias = "on_builtin")]
477    pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
478    where
479        L: ProviderLayer<RootProvider<N>, N>,
480        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
481        N: Network,
482    {
483        let client = ClientBuilder::default().connect(s).await?;
484        Ok(self.connect_client(client))
485    }
486
487    /// Finish the layer stack by providing a connection string with custom configuration.
488    ///
489    /// This method allows for fine-grained control over connection settings
490    /// such as authentication, retry behavior, and transport-specific options.
491    /// The transport type is extracted from the connection string and configured
492    /// using the provided [`ConnectionConfig`].
493    ///
494    /// # Examples
495    ///
496    /// ```
497    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
498    /// use alloy_provider::{ConnectionConfig, ProviderBuilder};
499    /// use alloy_transport::Authorization;
500    /// use std::time::Duration;
501    ///
502    /// let config = ConnectionConfig::new()
503    ///     .with_auth(Authorization::bearer("my-token"))
504    ///     .with_max_retries(3)
505    ///     .with_retry_interval(Duration::from_secs(2));
506    ///
507    /// let provider =
508    ///     ProviderBuilder::new().connect_with_config("ws://localhost:8545", config).await?;
509    /// # Ok(())
510    /// # }
511    /// ```
512    pub async fn connect_with_config(
513        self,
514        s: &str,
515        config: ConnectionConfig,
516    ) -> Result<F::Provider, TransportError>
517    where
518        L: ProviderLayer<RootProvider<N>, N>,
519        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
520        N: Network,
521    {
522        let client = ClientBuilder::default().connect_with_config(s, config).await?;
523        Ok(self.connect_client(client))
524    }
525
526    /// Finish the layer stack by providing a [`TransportConnect`] instance.
527    pub async fn connect_with<C>(self, connect: &C) -> Result<F::Provider, TransportError>
528    where
529        L: ProviderLayer<RootProvider<N>, N>,
530        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
531        N: Network,
532        C: TransportConnect,
533    {
534        connect
535            .get_transport()
536            .await
537            .map(|t| RpcClient::new(t, connect.is_local()))
538            .map(|client| self.connect_client(client))
539    }
540
541    /// Finish the layer stack by providing a [`PubSubConnect`] instance,
542    /// producing a [`Provider`] with pubsub capabilities.
543    ///
544    /// [`PubSubConnect`]: alloy_pubsub::PubSubConnect
545    #[cfg(feature = "pubsub")]
546    pub async fn connect_pubsub_with<C>(self, connect: C) -> Result<F::Provider, TransportError>
547    where
548        L: ProviderLayer<RootProvider<N>, N>,
549        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
550        N: Network,
551        C: alloy_pubsub::PubSubConnect,
552    {
553        ClientBuilder::default().pubsub(connect).await.map(|client| self.connect_client(client))
554    }
555
556    /// Build this provider with a websocket connection.
557    #[cfg(feature = "ws-base")]
558    pub async fn connect_ws(
559        self,
560        connect: alloy_transport_ws::WsConnect,
561    ) -> Result<F::Provider, TransportError>
562    where
563        L: ProviderLayer<RootProvider<N>, N>,
564        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
565        N: Network,
566    {
567        let client = ClientBuilder::default().ws(connect).await?;
568        Ok(self.connect_client(client))
569    }
570
571    /// Build this provider with an IPC connection.
572    #[cfg(feature = "ipc")]
573    pub async fn connect_ipc<T>(
574        self,
575        connect: alloy_transport_ipc::IpcConnect<T>,
576    ) -> Result<F::Provider, TransportError>
577    where
578        alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
579        L: ProviderLayer<RootProvider<N>, N>,
580        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
581        N: Network,
582    {
583        let client = ClientBuilder::default().ipc(connect).await?;
584        Ok(self.connect_client(client))
585    }
586
587    /// Build this provider with an Reqwest HTTP transport.
588    #[cfg(any(test, all(feature = "reqwest", not(all(target_os = "wasi", target_env = "p1")))))]
589    pub fn connect_http(self, url: reqwest::Url) -> F::Provider
590    where
591        L: ProviderLayer<crate::RootProvider<N>, N>,
592        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
593        N: Network,
594    {
595        let client = ClientBuilder::default().http(url);
596        self.connect_client(client)
597    }
598
599    /// Build this provider with a pre-built Reqwest client.
600    #[cfg(any(test, all(feature = "reqwest", not(all(target_os = "wasi", target_env = "p1")))))]
601    pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
602    where
603        L: ProviderLayer<crate::RootProvider<N>, N>,
604        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
605        N: Network,
606        C: Into<reqwest::Client>,
607    {
608        let client = ClientBuilder::default().http_with_client(client.into(), url);
609        self.connect_client(client)
610    }
611
612    /// Build this provider with a provided Reqwest client builder.
613    #[cfg(any(test, all(feature = "reqwest", not(all(target_os = "wasi", target_env = "p1")))))]
614    pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
615    where
616        L: ProviderLayer<crate::RootProvider<N>, N>,
617        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
618        N: Network,
619        B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
620    {
621        self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
622    }
623
624    /// Build this provider with an Hyper HTTP transport.
625    #[cfg(feature = "hyper")]
626    pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
627    where
628        L: ProviderLayer<crate::RootProvider<N>, N>,
629        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
630        N: Network,
631    {
632        let client = ClientBuilder::default().hyper_http(url);
633        self.connect_client(client)
634    }
635}
636
637#[cfg(any(test, feature = "anvil-node"))]
638type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
639
640#[cfg(any(test, feature = "anvil-node"))]
641type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
642
643#[cfg(any(test, feature = "anvil-node"))]
644impl<L, F, N: Network> ProviderBuilder<L, F, N> {
645    /// Build this provider with anvil, using the BoxTransport.
646    ///
647    /// This method requires the `anvil-node` feature on `alloy-provider`.
648    /// When using the `alloy` meta-crate, enable `provider-anvil-node`, or
649    /// combine `providers` with `node-bindings`.
650    #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
651    pub fn connect_anvil(self) -> F::Provider
652    where
653        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
654        L: crate::builder::ProviderLayer<
655            crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
656            N,
657        >,
658    {
659        self.connect_anvil_with_config(std::convert::identity)
660    }
661
662    /// Build this provider with anvil, using the BoxTransport. This
663    /// function configures a wallet backed by anvil keys, and is intended for
664    /// use in tests.
665    ///
666    /// This method requires the `anvil-node` feature on `alloy-provider`.
667    /// When using the `alloy` meta-crate, enable `provider-anvil-node`, or
668    /// combine `providers` with `node-bindings`.
669    #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
670    pub fn connect_anvil_with_wallet(
671        self,
672    ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
673    where
674        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
675        L: crate::builder::ProviderLayer<
676            crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
677            N,
678        >,
679        alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
680    {
681        self.connect_anvil_with_wallet_and_config(std::convert::identity)
682            .expect("failed to build provider")
683    }
684
685    /// Build this provider with anvil, using the BoxTransport. The
686    /// given function is used to configure the anvil instance.
687    ///
688    /// This method requires the `anvil-node` feature on `alloy-provider`.
689    /// When using the `alloy` meta-crate, enable `provider-anvil-node`, or
690    /// combine `providers` with `node-bindings`.
691    #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
692    pub fn connect_anvil_with_config(
693        self,
694        f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
695    ) -> F::Provider
696    where
697        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
698        L: crate::builder::ProviderLayer<
699            crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
700            N,
701        >,
702    {
703        let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
704        let url = anvil_layer.endpoint_url();
705
706        let rpc_client = ClientBuilder::default().http(url);
707
708        self.layer(anvil_layer).connect_client(rpc_client)
709    }
710
711    /// Build this provider with anvil, using the BoxTransport. The
712    /// given function is used to configure the anvil instance.
713    ///
714    /// This method requires the `anvil-node` feature on `alloy-provider`.
715    /// When using the `alloy` meta-crate, enable `provider-anvil-node`, or
716    /// combine `providers` with `node-bindings`.
717    #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
718    #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
719    pub fn on_anvil_with_config(
720        self,
721        f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
722    ) -> F::Provider
723    where
724        L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
725        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
726    {
727        let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
728        let url = anvil_layer.endpoint_url();
729
730        let rpc_client = ClientBuilder::default().http(url);
731
732        self.layer(anvil_layer).connect_client(rpc_client)
733    }
734
735    /// Build this provider with anvil, using the BoxTransport.
736    /// This calls `try_on_anvil_with_wallet_and_config` and panics on error.
737    ///
738    /// This method requires the `anvil-node` feature on `alloy-provider`.
739    /// When using the `alloy` meta-crate, enable `provider-anvil-node`, or
740    /// combine `providers` with `node-bindings`.
741    #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
742    pub fn connect_anvil_with_wallet_and_config(
743        self,
744        f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
745    ) -> AnvilProviderResult<
746        <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
747    >
748    where
749        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
750        L: crate::builder::ProviderLayer<
751            crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
752            N,
753        >,
754        alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
755    {
756        let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
757        let url = anvil_layer.endpoint_url();
758
759        let wallet = anvil_layer
760            .instance()
761            .wallet()
762            .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
763
764        let rpc_client = ClientBuilder::default().http(url);
765
766        Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
767    }
768
769    /// Build this provider with anvil, using the BoxTransport.
770    /// This calls `try_on_anvil_with_wallet_and_config` and panics on error.
771    ///
772    /// This method requires the `anvil-node` feature on `alloy-provider`.
773    /// When using the `alloy` meta-crate, enable `provider-anvil-node`, or
774    /// combine `providers` with `node-bindings`.
775    #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
776    #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
777    pub fn on_anvil_with_wallet_and_config(
778        self,
779        f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
780    ) -> AnvilProviderResult<
781        <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
782    >
783    where
784        F: TxFiller<N> + ProviderLayer<L::Provider, N>,
785        L: crate::builder::ProviderLayer<
786            crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
787            N,
788        >,
789        alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
790    {
791        let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
792        let url = anvil_layer.endpoint_url();
793
794        let wallet = anvil_layer
795            .instance()
796            .wallet()
797            .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
798
799        let rpc_client = ClientBuilder::default().http(url);
800
801        Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
802    }
803}
804
805#[cfg(test)]
806mod tests {
807    use super::*;
808    use crate::Provider;
809    use alloy_network::AnyNetwork;
810
811    #[tokio::test]
812    async fn basic() {
813        let provider = ProviderBuilder::new()
814            .with_cached_nonce_management()
815            .with_call_batching()
816            .connect_http("http://localhost:8545".parse().unwrap());
817        let _ = provider.get_account(Default::default());
818        let provider = provider.erased();
819        let _ = provider.get_account(Default::default());
820    }
821
822    #[tokio::test]
823    #[cfg(feature = "reqwest")]
824    async fn test_connect_reqwest() {
825        let provider = ProviderBuilder::new()
826            .with_cached_nonce_management()
827            .with_call_batching()
828            .connect_reqwest(
829                reqwest::Client::new(),
830                reqwest::Url::parse("http://localhost:8545").unwrap(),
831            );
832        let _ = provider.get_account(Default::default());
833        let provider = provider.erased();
834        let _ = provider.get_account(Default::default());
835    }
836
837    #[tokio::test]
838    #[cfg(feature = "reqwest")]
839    async fn test_with_reqwest() {
840        let provider = ProviderBuilder::new()
841            .with_cached_nonce_management()
842            .with_call_batching()
843            .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
844                builder
845                    .user_agent("alloy/test")
846                    .timeout(std::time::Duration::from_secs(10))
847                    .build()
848                    .expect("failed to build reqwest client")
849            });
850        let _ = provider.get_account(Default::default());
851        let provider = provider.erased();
852        let _ = provider.get_account(Default::default());
853    }
854
855    #[tokio::test]
856    async fn compile_with_network() {
857        let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
858        let num = p.get_block_number().await.unwrap();
859        assert_eq!(num, 0);
860    }
861
862    // Ensures `.network()` replaces fillers rather than keeping the old ones.
863    #[test]
864    fn network_replaces_fillers() {
865        // Add an extra filler before swapping, it should be dropped.
866        let builder = ProviderBuilder::new().filler(GasFiller::default()).network::<AnyNetwork>();
867
868        let _: ProviderBuilder<
869            Identity,
870            JoinFill<Identity, <AnyNetwork as RecommendedFillers>::RecommendedFillers>,
871            AnyNetwork,
872        > = builder;
873    }
874
875    #[test]
876    fn apply_transforms_builder() {
877        let builder = ProviderBuilder::new()
878            .apply(|builder| builder.disable_recommended_fillers().with_gas_estimation());
879
880        let _: ProviderBuilder<Identity, JoinFill<Identity, GasFiller>, Ethereum> = builder;
881    }
882
883    #[test]
884    fn map_filler_replaces_fillers() {
885        let builder = ProviderBuilder::new().map_filler(|_| GasFiller::default());
886
887        let _: ProviderBuilder<Identity, GasFiller, Ethereum> = builder;
888    }
889
890    #[test]
891    fn map_layer_replaces_layers() {
892        let builder = ProviderBuilder::<Identity, Identity>::default()
893            .map_layer(|_| ChainLayer::new(NamedChain::Mainnet));
894
895        let _: ProviderBuilder<ChainLayer, Identity, Ethereum> = builder;
896    }
897
898    #[tokio::test]
899    async fn network_swap_works_at_runtime() {
900        // Verify that `ProviderBuilder::new().network::<AnyNetwork>()` produces a working provider.
901        let p = ProviderBuilder::new().network::<AnyNetwork>().connect_anvil();
902        let num = p.get_block_number().await.unwrap();
903        assert_eq!(num, 0);
904    }
905}