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 Provider, RootProvider,
10};
11use alloy_chains::NamedChain;
12use alloy_network::{Ethereum, IntoWallet, Network};
13use alloy_primitives::ChainId;
14use alloy_rpc_client::{ClientBuilder, ConnectionConfig, RpcClient};
15use alloy_transport::{TransportConnect, TransportError, TransportResult};
16use std::marker::PhantomData;
17
18pub trait ProviderLayer<P: Provider<N>, N: Network = Ethereum> {
22 type Provider: Provider<N>;
24
25 fn layer(&self, inner: P) -> Self::Provider;
27}
28
29#[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#[derive(Debug)]
76pub struct Stack<Inner, Outer> {
77 inner: Inner,
78 outer: Outer,
79}
80
81impl<Inner, Outer> Stack<Inner, Outer> {
82 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#[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, <Ethereum as RecommendedFillers>::RecommendedFillers>,
128 Ethereum,
129 >
130{
131 pub fn new() -> Self {
141 ProviderBuilder::default().with_recommended_fillers()
142 }
143
144 pub fn disable_recommended_fillers(self) -> ProviderBuilder<Identity, Identity, Ethereum> {
149 ProviderBuilder { layer: self.layer, filler: Identity, network: self.network }
150 }
151}
152
153impl<N> Default for ProviderBuilder<Identity, Identity, N> {
154 fn default() -> Self {
155 Self { layer: Identity, filler: Identity, network: PhantomData }
156 }
157}
158
159impl ProviderBuilder<Identity, Identity, Ethereum> {
160 pub fn new_with_network<Net: RecommendedFillers>(
163 ) -> ProviderBuilder<Identity, JoinFill<Identity, Net::RecommendedFillers>, Net> {
164 ProviderBuilder {
165 layer: Identity,
166 filler: JoinFill::new(Identity, Net::recommended_fillers()),
167 network: PhantomData,
168 }
169 }
170}
171
172impl<L, N: Network> ProviderBuilder<L, Identity, N> {
173 pub fn with_recommended_fillers(
176 self,
177 ) -> ProviderBuilder<L, JoinFill<Identity, N::RecommendedFillers>, N>
178 where
179 N: RecommendedFillers,
180 {
181 self.filler(N::recommended_fillers())
182 }
183}
184
185impl<L, F, N> ProviderBuilder<L, F, N> {
186 pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
198 ProviderBuilder {
199 layer: Stack::new(layer, self.layer),
200 filler: self.filler,
201 network: PhantomData,
202 }
203 }
204
205 pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
209 ProviderBuilder {
210 layer: self.layer,
211 filler: JoinFill::new(self.filler, filler),
212 network: PhantomData,
213 }
214 }
215
216 pub fn network<Net: Network>(self) -> ProviderBuilder<L, F, Net> {
225 ProviderBuilder { layer: self.layer, filler: self.filler, network: PhantomData }
226 }
227
228 pub fn with_chain(self, chain: NamedChain) -> ProviderBuilder<Stack<ChainLayer, L>, F, N> {
233 self.layer(ChainLayer::new(chain))
234 }
235
236 pub fn with_blob_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
242 self.filler(BlobGasFiller::default())
243 }
244
245 pub fn with_blob_gas_estimator(
249 self,
250 estimator: BlobGasEstimator,
251 ) -> ProviderBuilder<L, JoinFill<F, BlobGasFiller>, N> {
252 self.filler(BlobGasFiller { estimator })
253 }
254
255 pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
259 self.filler(GasFiller)
260 }
261
262 pub fn with_nonce_management<M: NonceManager>(
266 self,
267 nonce_manager: M,
268 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<M>>, N> {
269 self.filler(NonceFiller::new(nonce_manager))
270 }
271
272 pub fn with_simple_nonce_management(
276 self,
277 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<SimpleNonceManager>>, N> {
278 self.with_nonce_management(SimpleNonceManager::default())
279 }
280
281 pub fn with_cached_nonce_management(
285 self,
286 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<CachedNonceManager>>, N> {
287 self.with_nonce_management(CachedNonceManager::default())
288 }
289
290 pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
295 self.filler(ChainIdFiller::default())
296 }
297
298 pub fn with_chain_id(
302 self,
303 chain_id: ChainId,
304 ) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
305 self.filler(ChainIdFiller::new(Some(chain_id)))
306 }
307
308 pub fn wallet<W: IntoWallet<N>>(
312 self,
313 wallet: W,
314 ) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W::NetworkWallet>>, N>
315 where
316 N: Network,
317 {
318 self.filler(WalletFiller::new(wallet.into_wallet()))
319 }
320
321 pub fn with_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
327 self.layer(CallBatchLayer::new())
328 }
329
330 pub fn with_arbitrum_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
335 self.layer(CallBatchLayer::new().arbitrum_compat())
336 }
337
338 #[cfg(not(target_family = "wasm"))]
342 pub fn with_caching(
343 self,
344 max_items: u32,
345 ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
346 self.layer(crate::layers::CacheLayer::new(max_items))
347 }
348
349 #[cfg(not(target_family = "wasm"))]
353 pub fn with_default_caching(
354 self,
355 ) -> ProviderBuilder<Stack<crate::layers::CacheLayer, L>, F, N> {
356 self.with_caching(100)
357 }
358
359 pub fn with_default_block(
363 self,
364 block_id: alloy_eips::BlockId,
365 ) -> ProviderBuilder<Stack<BlockIdLayer, L>, F, N> {
366 self.layer(BlockIdLayer::new(block_id))
367 }
368
369 pub fn connect_provider<P>(self, provider: P) -> F::Provider
374 where
375 L: ProviderLayer<P, N>,
376 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
377 P: Provider<N>,
378 N: Network,
379 {
380 let Self { layer, filler, network: PhantomData } = self;
381 let stack = Stack::new(layer, filler);
382 stack.layer(provider)
383 }
384
385 pub fn connect_client(self, client: RpcClient) -> F::Provider
391 where
392 L: ProviderLayer<RootProvider<N>, N>,
393 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
394 N: Network,
395 {
396 self.connect_provider(RootProvider::new(client))
397 }
398
399 pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
405 where
406 L: ProviderLayer<RootProvider<N>, N>,
407 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
408 N: Network,
409 {
410 self.connect_client(RpcClient::mocked(asserter))
411 }
412
413 #[doc(alias = "on_builtin")]
417 pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
418 where
419 L: ProviderLayer<RootProvider<N>, N>,
420 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
421 N: Network,
422 {
423 let client = ClientBuilder::default().connect(s).await?;
424 Ok(self.connect_client(client))
425 }
426
427 pub async fn connect_with_config(
453 self,
454 s: &str,
455 config: ConnectionConfig,
456 ) -> Result<F::Provider, TransportError>
457 where
458 L: ProviderLayer<RootProvider<N>, N>,
459 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
460 N: Network,
461 {
462 let client = ClientBuilder::default().connect_with_config(s, config).await?;
463 Ok(self.connect_client(client))
464 }
465
466 pub async fn connect_with<C>(self, connect: &C) -> Result<F::Provider, TransportError>
468 where
469 L: ProviderLayer<RootProvider<N>, N>,
470 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
471 N: Network,
472 C: TransportConnect,
473 {
474 connect
475 .get_transport()
476 .await
477 .map(|t| RpcClient::new(t, connect.is_local()))
478 .map(|client| self.connect_client(client))
479 }
480
481 #[cfg(feature = "pubsub")]
486 pub async fn connect_pubsub_with<C>(self, connect: C) -> Result<F::Provider, TransportError>
487 where
488 L: ProviderLayer<RootProvider<N>, N>,
489 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
490 N: Network,
491 C: alloy_pubsub::PubSubConnect,
492 {
493 ClientBuilder::default().pubsub(connect).await.map(|client| self.connect_client(client))
494 }
495
496 #[cfg(feature = "ws")]
498 pub async fn connect_ws(
499 self,
500 connect: alloy_transport_ws::WsConnect,
501 ) -> Result<F::Provider, TransportError>
502 where
503 L: ProviderLayer<RootProvider<N>, N>,
504 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
505 N: Network,
506 {
507 let client = ClientBuilder::default().ws(connect).await?;
508 Ok(self.connect_client(client))
509 }
510
511 #[cfg(feature = "ipc")]
513 pub async fn connect_ipc<T>(
514 self,
515 connect: alloy_transport_ipc::IpcConnect<T>,
516 ) -> Result<F::Provider, TransportError>
517 where
518 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
519 L: ProviderLayer<RootProvider<N>, N>,
520 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
521 N: Network,
522 {
523 let client = ClientBuilder::default().ipc(connect).await?;
524 Ok(self.connect_client(client))
525 }
526
527 #[cfg(any(test, feature = "reqwest"))]
529 pub fn connect_http(self, url: reqwest::Url) -> F::Provider
530 where
531 L: ProviderLayer<crate::RootProvider<N>, N>,
532 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
533 N: Network,
534 {
535 let client = ClientBuilder::default().http(url);
536 self.connect_client(client)
537 }
538
539 #[cfg(any(test, feature = "reqwest"))]
541 pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
542 where
543 L: ProviderLayer<crate::RootProvider<N>, N>,
544 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
545 N: Network,
546 C: Into<reqwest::Client>,
547 {
548 let client = ClientBuilder::default().http_with_client(client.into(), url);
549 self.connect_client(client)
550 }
551
552 #[cfg(any(test, feature = "reqwest"))]
554 pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
555 where
556 L: ProviderLayer<crate::RootProvider<N>, N>,
557 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
558 N: Network,
559 B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
560 {
561 self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
562 }
563
564 #[cfg(feature = "hyper")]
566 pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
567 where
568 L: ProviderLayer<crate::RootProvider<N>, N>,
569 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
570 N: Network,
571 {
572 let client = ClientBuilder::default().hyper_http(url);
573 self.connect_client(client)
574 }
575}
576
577#[cfg(any(test, feature = "anvil-node"))]
578type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
579
580#[cfg(any(test, feature = "anvil-node"))]
581type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
582
583#[cfg(any(test, feature = "anvil-node"))]
584impl<L, F, N: Network> ProviderBuilder<L, F, N> {
585 pub fn connect_anvil(self) -> F::Provider
587 where
588 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
589 L: crate::builder::ProviderLayer<
590 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
591 N,
592 >,
593 {
594 self.connect_anvil_with_config(std::convert::identity)
595 }
596
597 pub fn connect_anvil_with_wallet(
601 self,
602 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
603 where
604 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
605 L: crate::builder::ProviderLayer<
606 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
607 N,
608 >,
609 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
610 {
611 self.connect_anvil_with_wallet_and_config(std::convert::identity)
612 .expect("failed to build provider")
613 }
614
615 pub fn connect_anvil_with_config(
618 self,
619 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
620 ) -> F::Provider
621 where
622 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
623 L: crate::builder::ProviderLayer<
624 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
625 N,
626 >,
627 {
628 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
629 let url = anvil_layer.endpoint_url();
630
631 let rpc_client = ClientBuilder::default().http(url);
632
633 self.layer(anvil_layer).connect_client(rpc_client)
634 }
635
636 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
639 pub fn on_anvil_with_config(
640 self,
641 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
642 ) -> F::Provider
643 where
644 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
645 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
646 {
647 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
648 let url = anvil_layer.endpoint_url();
649
650 let rpc_client = ClientBuilder::default().http(url);
651
652 self.layer(anvil_layer).connect_client(rpc_client)
653 }
654
655 pub fn connect_anvil_with_wallet_and_config(
658 self,
659 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
660 ) -> AnvilProviderResult<
661 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
662 >
663 where
664 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
665 L: crate::builder::ProviderLayer<
666 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
667 N,
668 >,
669 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
670 {
671 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
672 let url = anvil_layer.endpoint_url();
673
674 let wallet = anvil_layer
675 .instance()
676 .wallet()
677 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
678
679 let rpc_client = ClientBuilder::default().http(url);
680
681 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
682 }
683
684 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
687 pub fn on_anvil_with_wallet_and_config(
688 self,
689 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
690 ) -> AnvilProviderResult<
691 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
692 >
693 where
694 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
695 L: crate::builder::ProviderLayer<
696 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
697 N,
698 >,
699 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
700 {
701 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
702 let url = anvil_layer.endpoint_url();
703
704 let wallet = anvil_layer
705 .instance()
706 .wallet()
707 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
708
709 let rpc_client = ClientBuilder::default().http(url);
710
711 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718 use crate::Provider;
719 use alloy_network::AnyNetwork;
720
721 #[tokio::test]
722 async fn basic() {
723 let provider = ProviderBuilder::new()
724 .with_cached_nonce_management()
725 .with_call_batching()
726 .connect_http("http://localhost:8545".parse().unwrap());
727 let _ = provider.get_account(Default::default());
728 let provider = provider.erased();
729 let _ = provider.get_account(Default::default());
730 }
731
732 #[tokio::test]
733 #[cfg(feature = "reqwest")]
734 async fn test_connect_reqwest() {
735 let provider = ProviderBuilder::new()
736 .with_cached_nonce_management()
737 .with_call_batching()
738 .connect_reqwest(
739 reqwest::Client::new(),
740 reqwest::Url::parse("http://localhost:8545").unwrap(),
741 );
742 let _ = provider.get_account(Default::default());
743 let provider = provider.erased();
744 let _ = provider.get_account(Default::default());
745 }
746
747 #[tokio::test]
748 #[cfg(feature = "reqwest")]
749 async fn test_with_reqwest() {
750 let provider = ProviderBuilder::new()
751 .with_cached_nonce_management()
752 .with_call_batching()
753 .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
754 builder
755 .user_agent("alloy/test")
756 .timeout(std::time::Duration::from_secs(10))
757 .build()
758 .expect("failed to build reqwest client")
759 });
760 let _ = provider.get_account(Default::default());
761 let provider = provider.erased();
762 let _ = provider.get_account(Default::default());
763 }
764
765 #[tokio::test]
766 async fn compile_with_network() {
767 let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
768 let num = p.get_block_number().await.unwrap();
769 assert_eq!(num, 0);
770 }
771}