1use crate::{
2 fillers::{
3 BlobGasEstimator, BlobGasFiller, CachedNonceManager, ChainIdFiller, FillerControlFlow,
4 GasFiller, JoinFill, NonceFiller, NonceManager, RecommendedFillers, SimpleNonceManager,
5 TxFiller, WalletFiller,
6 },
7 layers::{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 connect_provider<P>(self, provider: P) -> F::Provider
364 where
365 L: ProviderLayer<P, N>,
366 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
367 P: Provider<N>,
368 N: Network,
369 {
370 let Self { layer, filler, network: PhantomData } = self;
371 let stack = Stack::new(layer, filler);
372 stack.layer(provider)
373 }
374
375 pub fn connect_client(self, client: RpcClient) -> F::Provider
381 where
382 L: ProviderLayer<RootProvider<N>, N>,
383 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
384 N: Network,
385 {
386 self.connect_provider(RootProvider::new(client))
387 }
388
389 pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
395 where
396 L: ProviderLayer<RootProvider<N>, N>,
397 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
398 N: Network,
399 {
400 self.connect_client(RpcClient::mocked(asserter))
401 }
402
403 #[doc(alias = "on_builtin")]
407 pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
408 where
409 L: ProviderLayer<RootProvider<N>, N>,
410 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
411 N: Network,
412 {
413 let client = ClientBuilder::default().connect(s).await?;
414 Ok(self.connect_client(client))
415 }
416
417 pub async fn connect_with_config(
443 self,
444 s: &str,
445 config: ConnectionConfig,
446 ) -> Result<F::Provider, TransportError>
447 where
448 L: ProviderLayer<RootProvider<N>, N>,
449 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
450 N: Network,
451 {
452 let client = ClientBuilder::default().connect_with_config(s, config).await?;
453 Ok(self.connect_client(client))
454 }
455
456 pub async fn connect_with<C>(self, connect: &C) -> Result<F::Provider, TransportError>
458 where
459 L: ProviderLayer<RootProvider<N>, N>,
460 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
461 N: Network,
462 C: TransportConnect,
463 {
464 connect
465 .get_transport()
466 .await
467 .map(|t| RpcClient::new(t, connect.is_local()))
468 .map(|client| self.connect_client(client))
469 }
470
471 #[cfg(feature = "pubsub")]
476 pub async fn connect_pubsub_with<C>(self, connect: C) -> Result<F::Provider, TransportError>
477 where
478 L: ProviderLayer<RootProvider<N>, N>,
479 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
480 N: Network,
481 C: alloy_pubsub::PubSubConnect,
482 {
483 ClientBuilder::default().pubsub(connect).await.map(|client| self.connect_client(client))
484 }
485
486 #[cfg(feature = "ws")]
488 pub async fn connect_ws(
489 self,
490 connect: alloy_transport_ws::WsConnect,
491 ) -> Result<F::Provider, TransportError>
492 where
493 L: ProviderLayer<RootProvider<N>, N>,
494 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
495 N: Network,
496 {
497 let client = ClientBuilder::default().ws(connect).await?;
498 Ok(self.connect_client(client))
499 }
500
501 #[cfg(feature = "ipc")]
503 pub async fn connect_ipc<T>(
504 self,
505 connect: alloy_transport_ipc::IpcConnect<T>,
506 ) -> Result<F::Provider, TransportError>
507 where
508 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
509 L: ProviderLayer<RootProvider<N>, N>,
510 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
511 N: Network,
512 {
513 let client = ClientBuilder::default().ipc(connect).await?;
514 Ok(self.connect_client(client))
515 }
516
517 #[cfg(any(test, feature = "reqwest"))]
519 pub fn connect_http(self, url: reqwest::Url) -> F::Provider
520 where
521 L: ProviderLayer<crate::RootProvider<N>, N>,
522 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
523 N: Network,
524 {
525 let client = ClientBuilder::default().http(url);
526 self.connect_client(client)
527 }
528
529 #[cfg(any(test, feature = "reqwest"))]
531 pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
532 where
533 L: ProviderLayer<crate::RootProvider<N>, N>,
534 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
535 N: Network,
536 C: Into<reqwest::Client>,
537 {
538 let client = ClientBuilder::default().http_with_client(client.into(), url);
539 self.connect_client(client)
540 }
541
542 #[cfg(any(test, feature = "reqwest"))]
544 pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
545 where
546 L: ProviderLayer<crate::RootProvider<N>, N>,
547 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
548 N: Network,
549 B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
550 {
551 self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
552 }
553
554 #[cfg(feature = "hyper")]
556 pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
557 where
558 L: ProviderLayer<crate::RootProvider<N>, N>,
559 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
560 N: Network,
561 {
562 let client = ClientBuilder::default().hyper_http(url);
563 self.connect_client(client)
564 }
565}
566
567#[cfg(any(test, feature = "anvil-node"))]
568type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
569
570#[cfg(any(test, feature = "anvil-node"))]
571type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
572
573#[cfg(any(test, feature = "anvil-node"))]
574impl<L, F, N: Network> ProviderBuilder<L, F, N> {
575 pub fn connect_anvil(self) -> F::Provider
577 where
578 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
579 L: crate::builder::ProviderLayer<
580 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
581 N,
582 >,
583 {
584 self.connect_anvil_with_config(std::convert::identity)
585 }
586
587 pub fn connect_anvil_with_wallet(
591 self,
592 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
593 where
594 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
595 L: crate::builder::ProviderLayer<
596 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
597 N,
598 >,
599 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
600 {
601 self.connect_anvil_with_wallet_and_config(std::convert::identity)
602 .expect("failed to build provider")
603 }
604
605 pub fn connect_anvil_with_config(
608 self,
609 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
610 ) -> F::Provider
611 where
612 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
613 L: crate::builder::ProviderLayer<
614 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
615 N,
616 >,
617 {
618 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
619 let url = anvil_layer.endpoint_url();
620
621 let rpc_client = ClientBuilder::default().http(url);
622
623 self.layer(anvil_layer).connect_client(rpc_client)
624 }
625
626 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
629 pub fn on_anvil_with_config(
630 self,
631 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
632 ) -> F::Provider
633 where
634 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
635 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
636 {
637 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
638 let url = anvil_layer.endpoint_url();
639
640 let rpc_client = ClientBuilder::default().http(url);
641
642 self.layer(anvil_layer).connect_client(rpc_client)
643 }
644
645 pub fn connect_anvil_with_wallet_and_config(
648 self,
649 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
650 ) -> AnvilProviderResult<
651 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
652 >
653 where
654 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
655 L: crate::builder::ProviderLayer<
656 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
657 N,
658 >,
659 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
660 {
661 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
662 let url = anvil_layer.endpoint_url();
663
664 let wallet = anvil_layer
665 .instance()
666 .wallet()
667 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
668
669 let rpc_client = ClientBuilder::default().http(url);
670
671 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
672 }
673
674 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
677 pub fn on_anvil_with_wallet_and_config(
678 self,
679 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
680 ) -> AnvilProviderResult<
681 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
682 >
683 where
684 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
685 L: crate::builder::ProviderLayer<
686 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
687 N,
688 >,
689 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
690 {
691 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
692 let url = anvil_layer.endpoint_url();
693
694 let wallet = anvil_layer
695 .instance()
696 .wallet()
697 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
698
699 let rpc_client = ClientBuilder::default().http(url);
700
701 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
702 }
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708 use crate::Provider;
709 use alloy_network::AnyNetwork;
710
711 #[tokio::test]
712 async fn basic() {
713 let provider = ProviderBuilder::new()
714 .with_cached_nonce_management()
715 .with_call_batching()
716 .connect_http("http://localhost:8545".parse().unwrap());
717 let _ = provider.get_account(Default::default());
718 let provider = provider.erased();
719 let _ = provider.get_account(Default::default());
720 }
721
722 #[tokio::test]
723 #[cfg(feature = "reqwest")]
724 async fn test_connect_reqwest() {
725 let provider = ProviderBuilder::new()
726 .with_cached_nonce_management()
727 .with_call_batching()
728 .connect_reqwest(
729 reqwest::Client::new(),
730 reqwest::Url::parse("http://localhost:8545").unwrap(),
731 );
732 let _ = provider.get_account(Default::default());
733 let provider = provider.erased();
734 let _ = provider.get_account(Default::default());
735 }
736
737 #[tokio::test]
738 #[cfg(feature = "reqwest")]
739 async fn test_with_reqwest() {
740 let provider = ProviderBuilder::new()
741 .with_cached_nonce_management()
742 .with_call_batching()
743 .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
744 builder
745 .user_agent("alloy/test")
746 .timeout(std::time::Duration::from_secs(10))
747 .build()
748 .expect("failed to build reqwest client")
749 });
750 let _ = provider.get_account(Default::default());
751 let provider = provider.erased();
752 let _ = provider.get_account(Default::default());
753 }
754
755 #[tokio::test]
756 async fn compile_with_network() {
757 let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
758 let num = p.get_block_number().await.unwrap();
759 assert_eq!(num, 0);
760 }
761}