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
17pub trait ProviderLayer<P: Provider<N>, N: Network = Ethereum> {
21 type Provider: Provider<N>;
23
24 fn layer(&self, inner: P) -> Self::Provider;
26}
27
28#[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#[derive(Debug)]
75pub struct Stack<Inner, Outer> {
76 inner: Inner,
77 outer: Outer,
78}
79
80impl<Inner, Outer> Stack<Inner, Outer> {
81 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#[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 pub fn new() -> Self {
140 ProviderBuilder::default().with_recommended_fillers()
141 }
142
143 pub fn disable_recommended_fillers(self) -> ProviderBuilder<Identity, Identity, Ethereum> {
148 ProviderBuilder { layer: self.layer, filler: Identity, network: self.network }
149 }
150}
151
152impl<N> Default for ProviderBuilder<Identity, Identity, N> {
153 fn default() -> Self {
154 Self { layer: Identity, filler: Identity, network: PhantomData }
155 }
156}
157
158impl ProviderBuilder<Identity, Identity, Ethereum> {
159 pub fn new_with_network<Net: RecommendedFillers>(
162 ) -> ProviderBuilder<Identity, JoinFill<Identity, Net::RecommendedFillers>, Net> {
163 ProviderBuilder {
164 layer: Identity,
165 filler: JoinFill::new(Identity, Net::recommended_fillers()),
166 network: PhantomData,
167 }
168 }
169}
170
171impl<L, N: Network> ProviderBuilder<L, Identity, N> {
172 pub fn with_recommended_fillers(
175 self,
176 ) -> ProviderBuilder<L, JoinFill<Identity, N::RecommendedFillers>, N>
177 where
178 N: RecommendedFillers,
179 {
180 self.filler(N::recommended_fillers())
181 }
182}
183
184impl<L, F, N> ProviderBuilder<L, F, N> {
185 pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
197 ProviderBuilder {
198 layer: Stack::new(layer, self.layer),
199 filler: self.filler,
200 network: PhantomData,
201 }
202 }
203
204 pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
208 ProviderBuilder {
209 layer: self.layer,
210 filler: JoinFill::new(self.filler, filler),
211 network: PhantomData,
212 }
213 }
214
215 pub fn network<Net: Network>(self) -> ProviderBuilder<L, F, Net> {
224 ProviderBuilder { layer: self.layer, filler: self.filler, network: PhantomData }
225 }
226
227 pub fn with_chain(self, chain: NamedChain) -> ProviderBuilder<Stack<ChainLayer, L>, F, N> {
232 self.layer(ChainLayer::new(chain))
233 }
234
235 pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<F, GasFiller>, N> {
241 self.filler(GasFiller)
242 }
243
244 pub fn with_nonce_management<M: NonceManager>(
248 self,
249 nonce_manager: M,
250 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<M>>, N> {
251 self.filler(NonceFiller::new(nonce_manager))
252 }
253
254 pub fn with_simple_nonce_management(
258 self,
259 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<SimpleNonceManager>>, N> {
260 self.with_nonce_management(SimpleNonceManager::default())
261 }
262
263 pub fn with_cached_nonce_management(
267 self,
268 ) -> ProviderBuilder<L, JoinFill<F, NonceFiller<CachedNonceManager>>, N> {
269 self.with_nonce_management(CachedNonceManager::default())
270 }
271
272 pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
277 self.filler(ChainIdFiller::default())
278 }
279
280 pub fn with_chain_id(
284 self,
285 chain_id: ChainId,
286 ) -> ProviderBuilder<L, JoinFill<F, ChainIdFiller>, N> {
287 self.filler(ChainIdFiller::new(Some(chain_id)))
288 }
289
290 pub fn wallet<W: IntoWallet<N>>(
294 self,
295 wallet: W,
296 ) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W::NetworkWallet>>, N>
297 where
298 N: Network,
299 {
300 self.filler(WalletFiller::new(wallet.into_wallet()))
301 }
302
303 pub fn with_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
309 self.layer(CallBatchLayer::new())
310 }
311
312 pub fn connect_provider<P>(self, provider: P) -> F::Provider
317 where
318 L: ProviderLayer<P, N>,
319 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
320 P: Provider<N>,
321 N: Network,
322 {
323 let Self { layer, filler, network: PhantomData } = self;
324 let stack = Stack::new(layer, filler);
325 stack.layer(provider)
326 }
327
328 #[deprecated(since = "0.12.6", note = "use `connect_provider` instead")]
331 pub fn on_provider<P>(self, provider: P) -> F::Provider
332 where
333 L: ProviderLayer<P, N>,
334 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
335 P: Provider<N>,
336 N: Network,
337 {
338 let Self { layer, filler, network: PhantomData } = self;
339 let stack = Stack::new(layer, filler);
340 stack.layer(provider)
341 }
342
343 pub fn connect_client(self, client: RpcClient) -> F::Provider
349 where
350 L: ProviderLayer<RootProvider<N>, N>,
351 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
352 N: Network,
353 {
354 self.connect_provider(RootProvider::new(client))
355 }
356
357 #[deprecated(since = "0.12.6", note = "use `connect_client` instead")]
363 pub fn on_client(self, client: RpcClient) -> F::Provider
364 where
365 L: ProviderLayer<RootProvider<N>, N>,
366 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
367 N: Network,
368 {
369 self.connect_provider(RootProvider::new(client))
370 }
371
372 pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
378 where
379 L: ProviderLayer<RootProvider<N>, N>,
380 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
381 N: Network,
382 {
383 self.connect_client(RpcClient::mocked(asserter))
384 }
385
386 #[deprecated(since = "0.12.6", note = "use `connect_mocked_client` instead")]
392 pub fn on_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
393 where
394 L: ProviderLayer<RootProvider<N>, N>,
395 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
396 N: Network,
397 {
398 self.connect_client(RpcClient::mocked(asserter))
399 }
400
401 #[doc(alias = "on_builtin")]
405 pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
406 where
407 L: ProviderLayer<RootProvider<N>, N>,
408 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
409 N: Network,
410 {
411 let client = ClientBuilder::default().connect(s).await?;
412 Ok(self.connect_client(client))
413 }
414
415 #[deprecated = "use `connect` instead"]
419 #[doc(hidden)]
420 pub async fn on_builtin(self, s: &str) -> Result<F::Provider, TransportError>
421 where
422 L: ProviderLayer<RootProvider<N>, N>,
423 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
424 N: Network,
425 {
426 self.connect(s).await
427 }
428
429 #[cfg(feature = "ws")]
431 pub async fn connect_ws(
432 self,
433 connect: alloy_transport_ws::WsConnect,
434 ) -> Result<F::Provider, TransportError>
435 where
436 L: ProviderLayer<RootProvider<N>, N>,
437 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
438 N: Network,
439 {
440 let client = ClientBuilder::default().ws(connect).await?;
441 Ok(self.connect_client(client))
442 }
443
444 #[cfg(feature = "ws")]
446 #[deprecated(since = "0.12.6", note = "use `connect_ws` instead")]
447 pub async fn on_ws(
448 self,
449 connect: alloy_transport_ws::WsConnect,
450 ) -> Result<F::Provider, TransportError>
451 where
452 L: ProviderLayer<RootProvider<N>, N>,
453 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
454 N: Network,
455 {
456 let client = ClientBuilder::default().ws(connect).await?;
457 Ok(self.connect_client(client))
458 }
459
460 #[cfg(feature = "ipc")]
462 pub async fn connect_ipc<T>(
463 self,
464 connect: alloy_transport_ipc::IpcConnect<T>,
465 ) -> Result<F::Provider, TransportError>
466 where
467 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
468 L: ProviderLayer<RootProvider<N>, N>,
469 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
470 N: Network,
471 {
472 let client = ClientBuilder::default().ipc(connect).await?;
473 Ok(self.connect_client(client))
474 }
475
476 #[cfg(feature = "ipc")]
478 #[deprecated(since = "0.12.6", note = "use `connect_ipc` instead")]
479 pub async fn on_ipc<T>(
480 self,
481 connect: alloy_transport_ipc::IpcConnect<T>,
482 ) -> Result<F::Provider, TransportError>
483 where
484 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
485 L: ProviderLayer<RootProvider<N>, N>,
486 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
487 N: Network,
488 {
489 let client = ClientBuilder::default().ipc(connect).await?;
490 Ok(self.connect_client(client))
491 }
492
493 #[cfg(any(test, feature = "reqwest"))]
495 pub fn connect_http(self, url: reqwest::Url) -> F::Provider
496 where
497 L: ProviderLayer<crate::RootProvider<N>, N>,
498 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
499 N: Network,
500 {
501 let client = ClientBuilder::default().http(url);
502 self.connect_client(client)
503 }
504
505 #[cfg(any(test, feature = "reqwest"))]
507 pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
508 where
509 L: ProviderLayer<crate::RootProvider<N>, N>,
510 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
511 N: Network,
512 C: Into<reqwest::Client>,
513 {
514 let client = ClientBuilder::default().http_with_client(client.into(), url);
515 self.connect_client(client)
516 }
517
518 #[cfg(any(test, feature = "reqwest"))]
520 pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
521 where
522 L: ProviderLayer<crate::RootProvider<N>, N>,
523 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
524 N: Network,
525 B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
526 {
527 self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
528 }
529
530 #[cfg(any(test, feature = "reqwest"))]
532 #[deprecated(since = "0.12.6", note = "use `connect_http` instead")]
533 pub fn on_http(self, url: reqwest::Url) -> F::Provider
534 where
535 L: ProviderLayer<RootProvider<N>, N>,
536 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
537 N: Network,
538 {
539 let client = ClientBuilder::default().http(url);
540 self.connect_client(client)
541 }
542
543 #[cfg(feature = "hyper")]
545 pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
546 where
547 L: ProviderLayer<crate::RootProvider<N>, N>,
548 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
549 N: Network,
550 {
551 let client = ClientBuilder::default().hyper_http(url);
552 self.connect_client(client)
553 }
554
555 #[cfg(feature = "hyper")]
557 #[deprecated(since = "0.12.6", note = "use `connect_hyper_http` instead")]
558 pub fn on_hyper_http(self, url: url::Url) -> F::Provider
559 where
560 L: ProviderLayer<RootProvider<N>, N>,
561 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
562 N: Network,
563 {
564 let client = ClientBuilder::default().hyper_http(url);
565 self.connect_client(client)
566 }
567}
568
569#[cfg(any(test, feature = "anvil-node"))]
570type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
571
572#[cfg(any(test, feature = "anvil-node"))]
573type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
574
575#[cfg(any(test, feature = "anvil-node"))]
576impl<L, F, N: Network> ProviderBuilder<L, F, N> {
577 pub fn connect_anvil(self) -> F::Provider
579 where
580 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
581 L: crate::builder::ProviderLayer<
582 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
583 N,
584 >,
585 {
586 self.connect_anvil_with_config(std::convert::identity)
587 }
588
589 #[deprecated(since = "0.12.6", note = "use `connect_anvil` instead")]
591 pub fn on_anvil(self) -> F::Provider
592 where
593 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
594 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
595 {
596 self.connect_anvil_with_config(std::convert::identity)
597 }
598
599 pub fn connect_anvil_with_wallet(
603 self,
604 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
605 where
606 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
607 L: crate::builder::ProviderLayer<
608 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
609 N,
610 >,
611 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
612 {
613 self.connect_anvil_with_wallet_and_config(std::convert::identity)
614 .expect("failed to build provider")
615 }
616
617 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet` instead")]
621 pub fn on_anvil_with_wallet(
622 self,
623 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
624 where
625 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
626 L: crate::builder::ProviderLayer<
627 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
628 N,
629 >,
630 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
631 {
632 self.connect_anvil_with_wallet_and_config(std::convert::identity)
633 .expect("failed to build provider")
634 }
635
636 pub fn connect_anvil_with_config(
639 self,
640 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
641 ) -> F::Provider
642 where
643 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
644 L: crate::builder::ProviderLayer<
645 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
646 N,
647 >,
648 {
649 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
650 let url = anvil_layer.endpoint_url();
651
652 let rpc_client = ClientBuilder::default().http(url);
653
654 self.layer(anvil_layer).connect_client(rpc_client)
655 }
656
657 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
660 pub fn on_anvil_with_config(
661 self,
662 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
663 ) -> F::Provider
664 where
665 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
666 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
667 {
668 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
669 let url = anvil_layer.endpoint_url();
670
671 let rpc_client = ClientBuilder::default().http(url);
672
673 self.layer(anvil_layer).connect_client(rpc_client)
674 }
675
676 pub fn connect_anvil_with_wallet_and_config(
679 self,
680 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
681 ) -> AnvilProviderResult<
682 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
683 >
684 where
685 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
686 L: crate::builder::ProviderLayer<
687 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
688 N,
689 >,
690 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
691 {
692 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
693 let url = anvil_layer.endpoint_url();
694
695 let wallet = anvil_layer
696 .instance()
697 .wallet()
698 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
699
700 let rpc_client = ClientBuilder::default().http(url);
701
702 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
703 }
704
705 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
708 pub fn on_anvil_with_wallet_and_config(
709 self,
710 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
711 ) -> AnvilProviderResult<
712 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
713 >
714 where
715 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
716 L: crate::builder::ProviderLayer<
717 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
718 N,
719 >,
720 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
721 {
722 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
723 let url = anvil_layer.endpoint_url();
724
725 let wallet = anvil_layer
726 .instance()
727 .wallet()
728 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
729
730 let rpc_client = ClientBuilder::default().http(url);
731
732 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739 use crate::Provider;
740 use alloy_network::AnyNetwork;
741
742 #[tokio::test]
743 async fn basic() {
744 let provider = ProviderBuilder::new()
745 .with_cached_nonce_management()
746 .with_call_batching()
747 .connect_http("http://localhost:8545".parse().unwrap());
748 let _ = provider.get_account(Default::default());
749 let provider = provider.erased();
750 let _ = provider.get_account(Default::default());
751 }
752
753 #[tokio::test]
754 #[cfg(feature = "reqwest")]
755 async fn test_connect_reqwest() {
756 let provider = ProviderBuilder::new()
757 .with_cached_nonce_management()
758 .with_call_batching()
759 .connect_reqwest(
760 reqwest::Client::new(),
761 reqwest::Url::parse("http://localhost:8545").unwrap(),
762 );
763 let _ = provider.get_account(Default::default());
764 let provider = provider.erased();
765 let _ = provider.get_account(Default::default());
766 }
767
768 #[tokio::test]
769 #[cfg(feature = "reqwest")]
770 async fn test_with_reqwest() {
771 let provider = ProviderBuilder::new()
772 .with_cached_nonce_management()
773 .with_call_batching()
774 .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
775 builder
776 .user_agent("alloy/test")
777 .timeout(std::time::Duration::from_secs(10))
778 .build()
779 .expect("failed to build reqwest client")
780 });
781 let _ = provider.get_account(Default::default());
782 let provider = provider.erased();
783 let _ = provider.get_account(Default::default());
784 }
785
786 #[tokio::test]
787 async fn compile_with_network() {
788 let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
789 let num = p.get_block_number().await.unwrap();
790 assert_eq!(num, 0);
791 }
792}