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
19pub trait ProviderLayer<P: Provider<N>, N: Network = Ethereum> {
23 type Provider: Provider<N>;
25
26 fn layer(&self, inner: P) -> Self::Provider;
28}
29
30#[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#[derive(Debug)]
77pub struct Stack<Inner, Outer> {
78 inner: Inner,
79 outer: Outer,
80}
81
82impl<Inner, Outer> Stack<Inner, Outer> {
83 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#[derive(Debug)]
119pub struct ProviderBuilder<L, F, N = Ethereum> {
120 layer: L,
121 filler: F,
122 network: PhantomData<fn() -> N>,
123}
124
125impl
126 ProviderBuilder<
127 Identity,
128 JoinFill<Identity, <Ethereum as RecommendedFillers>::RecommendedFillers>,
129 Ethereum,
130 >
131{
132 pub fn new() -> Self {
142 ProviderBuilder::default().with_recommended_fillers()
143 }
144
145 pub fn disable_recommended_fillers(self) -> ProviderBuilder<Identity, Identity, Ethereum> {
150 ProviderBuilder { layer: self.layer, filler: Identity, network: self.network }
151 }
152}
153
154impl<N> Default for ProviderBuilder<Identity, Identity, N> {
155 fn default() -> Self {
156 Self { layer: Identity, filler: Identity, network: PhantomData }
157 }
158}
159
160impl ProviderBuilder<Identity, Identity, Ethereum> {
161 pub fn new_with_network<Net: RecommendedFillers>(
164 ) -> ProviderBuilder<Identity, JoinFill<Identity, Net::RecommendedFillers>, Net> {
165 ProviderBuilder {
166 layer: Identity,
167 filler: JoinFill::new(Identity, Net::recommended_fillers()),
168 network: PhantomData,
169 }
170 }
171}
172
173impl<L, N: Network> ProviderBuilder<L, Identity, N> {
174 pub fn with_recommended_fillers(
177 self,
178 ) -> ProviderBuilder<L, JoinFill<Identity, N::RecommendedFillers>, N>
179 where
180 N: RecommendedFillers,
181 {
182 self.filler(N::recommended_fillers())
183 }
184}
185
186impl<L, F, N> ProviderBuilder<L, F, N> {
187 pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
199 ProviderBuilder {
200 layer: Stack::new(layer, self.layer),
201 filler: self.filler,
202 network: PhantomData,
203 }
204 }
205
206 pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
210 ProviderBuilder {
211 layer: self.layer,
212 filler: JoinFill::new(self.filler, filler),
213 network: PhantomData,
214 }
215 }
216
217 pub fn network<Net: RecommendedFillers>(
229 self,
230 ) -> ProviderBuilder<L, JoinFill<Identity, Net::RecommendedFillers>, Net> {
231 ProviderBuilder {
232 layer: self.layer,
233 filler: JoinFill::new(Identity, Net::recommended_fillers()),
234 network: PhantomData,
235 }
236 }
237
238 pub fn with_chain(self, chain: NamedChain) -> ProviderBuilder<Stack<ChainLayer, L>, F, N> {
243 self.layer(ChainLayer::new(chain))
244 }
245
246 pub fn with_blob_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
252 self.filler(BlobGasFiller::default())
253 }
254
255 pub fn with_blob_gas_estimator(
259 self,
260 estimator: BlobGasEstimator,
261 ) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
262 self.filler(BlobGasFiller { estimator })
263 }
264
265 pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
269 self.filler(GasFiller::default())
270 }
271
272 pub fn with_eip1559_estimator(
276 self,
277 estimator: Eip1559Estimator,
278 ) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
279 self.filler(GasFiller { estimator })
280 }
281
282 pub fn with_nonce_management<M: NonceManager>(
286 self,
287 nonce_manager: M,
288 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<M>>, N> {
289 self.filler(NonceFiller::new(nonce_manager))
290 }
291
292 pub fn with_simple_nonce_management(
296 self,
297 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<SimpleNonceManager>>, N> {
298 self.with_nonce_management(SimpleNonceManager::default())
299 }
300
301 pub fn with_cached_nonce_management(
305 self,
306 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<CachedNonceManager>>, N> {
307 self.with_nonce_management(CachedNonceManager::default())
308 }
309
310 pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
315 self.filler(ChainIdFiller::default())
316 }
317
318 pub fn with_chain_id(
322 self,
323 chain_id: ChainId,
324 ) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
325 self.filler(ChainIdFiller::new(Some(chain_id)))
326 }
327
328 pub fn wallet<W: IntoWallet<N>>(
332 self,
333 wallet: W,
334 ) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W::NetworkWallet>>, N>
335 where
336 N: Network,
337 {
338 self.filler(WalletFiller::new(wallet.into_wallet()))
339 }
340
341 pub fn with_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
347 self.layer(CallBatchLayer::new())
348 }
349
350 pub fn with_arbitrum_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
355 self.layer(CallBatchLayer::new().arbitrum_compat())
356 }
357
358 #[cfg(not(target_family = "wasm"))]
362 pub fn with_caching(
363 self,
364 max_items: u32,
365 ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
366 self.layer(crate::layers::CacheLayer::new(max_items))
367 }
368
369 #[cfg(not(target_family = "wasm"))]
373 pub fn with_default_caching(
374 self,
375 ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
376 self.with_caching(100)
377 }
378
379 pub fn with_default_block(
383 self,
384 block_id: alloy_eips::BlockId,
385 ) -> ProviderBuilder<Stack<BlockIdLayer, L>, F, N> {
386 self.layer(BlockIdLayer::new(block_id))
387 }
388
389 pub fn connect_provider<P>(self, provider: P) -> F::Provider
394 where
395 L: ProviderLayer<P, N>,
396 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
397 P: Provider<N>,
398 N: Network,
399 {
400 let Self { layer, filler, network: PhantomData } = self;
401 let stack = Stack::new(layer, filler);
402 stack.layer(provider)
403 }
404
405 pub fn connect_client(self, client: RpcClient) -> F::Provider
411 where
412 L: ProviderLayer<RootProvider<N>, N>,
413 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
414 N: Network,
415 {
416 self.connect_provider(RootProvider::new(client))
417 }
418
419 pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
425 where
426 L: ProviderLayer<RootProvider<N>, N>,
427 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
428 N: Network,
429 {
430 self.connect_client(RpcClient::mocked(asserter))
431 }
432
433 #[doc(alias = "on_builtin")]
437 pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
438 where
439 L: ProviderLayer<RootProvider<N>, N>,
440 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
441 N: Network,
442 {
443 let client = ClientBuilder::default().connect(s).await?;
444 Ok(self.connect_client(client))
445 }
446
447 pub async fn connect_with_config(
473 self,
474 s: &str,
475 config: ConnectionConfig,
476 ) -> Result<F::Provider, TransportError>
477 where
478 L: ProviderLayer<RootProvider<N>, N>,
479 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
480 N: Network,
481 {
482 let client = ClientBuilder::default().connect_with_config(s, config).await?;
483 Ok(self.connect_client(client))
484 }
485
486 pub async fn connect_with<C>(self, connect: &C) -> Result<F::Provider, TransportError>
488 where
489 L: ProviderLayer<RootProvider<N>, N>,
490 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
491 N: Network,
492 C: TransportConnect,
493 {
494 connect
495 .get_transport()
496 .await
497 .map(|t| RpcClient::new(t, connect.is_local()))
498 .map(|client| self.connect_client(client))
499 }
500
501 #[cfg(feature = "pubsub")]
506 pub async fn connect_pubsub_with<C>(self, connect: C) -> Result<F::Provider, TransportError>
507 where
508 L: ProviderLayer<RootProvider<N>, N>,
509 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
510 N: Network,
511 C: alloy_pubsub::PubSubConnect,
512 {
513 ClientBuilder::default().pubsub(connect).await.map(|client| self.connect_client(client))
514 }
515
516 #[cfg(feature = "ws")]
518 pub async fn connect_ws(
519 self,
520 connect: alloy_transport_ws::WsConnect,
521 ) -> Result<F::Provider, TransportError>
522 where
523 L: ProviderLayer<RootProvider<N>, N>,
524 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
525 N: Network,
526 {
527 let client = ClientBuilder::default().ws(connect).await?;
528 Ok(self.connect_client(client))
529 }
530
531 #[cfg(feature = "ipc")]
533 pub async fn connect_ipc<T>(
534 self,
535 connect: alloy_transport_ipc::IpcConnect<T>,
536 ) -> Result<F::Provider, TransportError>
537 where
538 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
539 L: ProviderLayer<RootProvider<N>, N>,
540 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
541 N: Network,
542 {
543 let client = ClientBuilder::default().ipc(connect).await?;
544 Ok(self.connect_client(client))
545 }
546
547 #[cfg(any(test, feature = "reqwest"))]
549 pub fn connect_http(self, url: reqwest::Url) -> F::Provider
550 where
551 L: ProviderLayer<crate::RootProvider<N>, N>,
552 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
553 N: Network,
554 {
555 let client = ClientBuilder::default().http(url);
556 self.connect_client(client)
557 }
558
559 #[cfg(any(test, feature = "reqwest"))]
561 pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
562 where
563 L: ProviderLayer<crate::RootProvider<N>, N>,
564 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
565 N: Network,
566 C: Into<reqwest::Client>,
567 {
568 let client = ClientBuilder::default().http_with_client(client.into(), url);
569 self.connect_client(client)
570 }
571
572 #[cfg(any(test, feature = "reqwest"))]
574 pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
575 where
576 L: ProviderLayer<crate::RootProvider<N>, N>,
577 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
578 N: Network,
579 B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
580 {
581 self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
582 }
583
584 #[cfg(feature = "hyper")]
586 pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
587 where
588 L: ProviderLayer<crate::RootProvider<N>, N>,
589 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
590 N: Network,
591 {
592 let client = ClientBuilder::default().hyper_http(url);
593 self.connect_client(client)
594 }
595}
596
597#[cfg(any(test, feature = "anvil-node"))]
598type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
599
600#[cfg(any(test, feature = "anvil-node"))]
601type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
602
603#[cfg(any(test, feature = "anvil-node"))]
604impl<L, F, N: Network> ProviderBuilder<L, F, N> {
605 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
611 pub fn connect_anvil(self) -> F::Provider
612 where
613 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
614 L: crate::builder::ProviderLayer<
615 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
616 N,
617 >,
618 {
619 self.connect_anvil_with_config(std::convert::identity)
620 }
621
622 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
630 pub fn connect_anvil_with_wallet(
631 self,
632 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
633 where
634 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
635 L: crate::builder::ProviderLayer<
636 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
637 N,
638 >,
639 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
640 {
641 self.connect_anvil_with_wallet_and_config(std::convert::identity)
642 .expect("failed to build provider")
643 }
644
645 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
652 pub fn connect_anvil_with_config(
653 self,
654 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
655 ) -> F::Provider
656 where
657 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
658 L: crate::builder::ProviderLayer<
659 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
660 N,
661 >,
662 {
663 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
664 let url = anvil_layer.endpoint_url();
665
666 let rpc_client = ClientBuilder::default().http(url);
667
668 self.layer(anvil_layer).connect_client(rpc_client)
669 }
670
671 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
678 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
679 pub fn on_anvil_with_config(
680 self,
681 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
682 ) -> F::Provider
683 where
684 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
685 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
686 {
687 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
688 let url = anvil_layer.endpoint_url();
689
690 let rpc_client = ClientBuilder::default().http(url);
691
692 self.layer(anvil_layer).connect_client(rpc_client)
693 }
694
695 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
702 pub fn connect_anvil_with_wallet_and_config(
703 self,
704 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
705 ) -> AnvilProviderResult<
706 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
707 >
708 where
709 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
710 L: crate::builder::ProviderLayer<
711 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
712 N,
713 >,
714 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
715 {
716 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
717 let url = anvil_layer.endpoint_url();
718
719 let wallet = anvil_layer
720 .instance()
721 .wallet()
722 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
723
724 let rpc_client = ClientBuilder::default().http(url);
725
726 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
727 }
728
729 #[cfg_attr(docsrs, doc(cfg(feature = "anvil-node")))]
736 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
737 pub fn on_anvil_with_wallet_and_config(
738 self,
739 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
740 ) -> AnvilProviderResult<
741 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
742 >
743 where
744 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
745 L: crate::builder::ProviderLayer<
746 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
747 N,
748 >,
749 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
750 {
751 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
752 let url = anvil_layer.endpoint_url();
753
754 let wallet = anvil_layer
755 .instance()
756 .wallet()
757 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
758
759 let rpc_client = ClientBuilder::default().http(url);
760
761 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
762 }
763}
764
765#[cfg(test)]
766mod tests {
767 use super::*;
768 use crate::Provider;
769 use alloy_network::AnyNetwork;
770
771 #[tokio::test]
772 async fn basic() {
773 let provider = ProviderBuilder::new()
774 .with_cached_nonce_management()
775 .with_call_batching()
776 .connect_http("http://localhost:8545".parse().unwrap());
777 let _ = provider.get_account(Default::default());
778 let provider = provider.erased();
779 let _ = provider.get_account(Default::default());
780 }
781
782 #[tokio::test]
783 #[cfg(feature = "reqwest")]
784 async fn test_connect_reqwest() {
785 let provider = ProviderBuilder::new()
786 .with_cached_nonce_management()
787 .with_call_batching()
788 .connect_reqwest(
789 reqwest::Client::new(),
790 reqwest::Url::parse("http://localhost:8545").unwrap(),
791 );
792 let _ = provider.get_account(Default::default());
793 let provider = provider.erased();
794 let _ = provider.get_account(Default::default());
795 }
796
797 #[tokio::test]
798 #[cfg(feature = "reqwest")]
799 async fn test_with_reqwest() {
800 let provider = ProviderBuilder::new()
801 .with_cached_nonce_management()
802 .with_call_batching()
803 .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
804 builder
805 .user_agent("alloy/test")
806 .timeout(std::time::Duration::from_secs(10))
807 .build()
808 .expect("failed to build reqwest client")
809 });
810 let _ = provider.get_account(Default::default());
811 let provider = provider.erased();
812 let _ = provider.get_account(Default::default());
813 }
814
815 #[tokio::test]
816 async fn compile_with_network() {
817 let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
818 let num = p.get_block_number().await.unwrap();
819 assert_eq!(num, 0);
820 }
821
822 #[test]
824 fn network_replaces_fillers() {
825 let builder = ProviderBuilder::new().filler(GasFiller::default()).network::<AnyNetwork>();
827
828 let _: ProviderBuilder<
829 Identity,
830 JoinFill<Identity, <AnyNetwork as RecommendedFillers>::RecommendedFillers>,
831 AnyNetwork,
832 > = builder;
833 }
834
835 #[tokio::test]
836 async fn network_swap_works_at_runtime() {
837 let p = ProviderBuilder::new().network::<AnyNetwork>().connect_anvil();
839 let num = p.get_block_number().await.unwrap();
840 assert_eq!(num, 0);
841 }
842}