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, ConnectionConfig, RpcClient};
14use alloy_transport::{TransportConnect, 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 with_arbitrum_call_batching(self) -> ProviderBuilder<Stack<CallBatchLayer, L>, F, N> {
317 self.layer(CallBatchLayer::new().arbitrum_compat())
318 }
319
320 pub fn connect_provider<P>(self, provider: P) -> F::Provider
325 where
326 L: ProviderLayer<P, N>,
327 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
328 P: Provider<N>,
329 N: Network,
330 {
331 let Self { layer, filler, network: PhantomData } = self;
332 let stack = Stack::new(layer, filler);
333 stack.layer(provider)
334 }
335
336 pub fn connect_client(self, client: RpcClient) -> F::Provider
342 where
343 L: ProviderLayer<RootProvider<N>, N>,
344 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
345 N: Network,
346 {
347 self.connect_provider(RootProvider::new(client))
348 }
349
350 pub fn connect_mocked_client(self, asserter: alloy_transport::mock::Asserter) -> F::Provider
356 where
357 L: ProviderLayer<RootProvider<N>, N>,
358 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
359 N: Network,
360 {
361 self.connect_client(RpcClient::mocked(asserter))
362 }
363
364 #[doc(alias = "on_builtin")]
368 pub async fn connect(self, s: &str) -> Result<F::Provider, TransportError>
369 where
370 L: ProviderLayer<RootProvider<N>, N>,
371 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
372 N: Network,
373 {
374 let client = ClientBuilder::default().connect(s).await?;
375 Ok(self.connect_client(client))
376 }
377
378 pub async fn connect_with_config(
404 self,
405 s: &str,
406 config: ConnectionConfig,
407 ) -> 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_with_config(s, config).await?;
414 Ok(self.connect_client(client))
415 }
416
417 pub async fn connect_with<C>(self, connect: &C) -> Result<F::Provider, TransportError>
419 where
420 L: ProviderLayer<RootProvider<N>, N>,
421 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
422 N: Network,
423 C: TransportConnect,
424 {
425 connect
426 .get_transport()
427 .await
428 .map(|t| RpcClient::new(t, connect.is_local()))
429 .map(|client| self.connect_client(client))
430 }
431
432 #[cfg(feature = "pubsub")]
437 pub async fn connect_pubsub_with<C>(self, connect: C) -> Result<F::Provider, TransportError>
438 where
439 L: ProviderLayer<RootProvider<N>, N>,
440 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
441 N: Network,
442 C: alloy_pubsub::PubSubConnect,
443 {
444 ClientBuilder::default().pubsub(connect).await.map(|client| self.connect_client(client))
445 }
446
447 #[cfg(feature = "ws")]
449 pub async fn connect_ws(
450 self,
451 connect: alloy_transport_ws::WsConnect,
452 ) -> Result<F::Provider, TransportError>
453 where
454 L: ProviderLayer<RootProvider<N>, N>,
455 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
456 N: Network,
457 {
458 let client = ClientBuilder::default().ws(connect).await?;
459 Ok(self.connect_client(client))
460 }
461
462 #[cfg(feature = "ipc")]
464 pub async fn connect_ipc<T>(
465 self,
466 connect: alloy_transport_ipc::IpcConnect<T>,
467 ) -> Result<F::Provider, TransportError>
468 where
469 alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
470 L: ProviderLayer<RootProvider<N>, N>,
471 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
472 N: Network,
473 {
474 let client = ClientBuilder::default().ipc(connect).await?;
475 Ok(self.connect_client(client))
476 }
477
478 #[cfg(any(test, feature = "reqwest"))]
480 pub fn connect_http(self, url: reqwest::Url) -> F::Provider
481 where
482 L: ProviderLayer<crate::RootProvider<N>, N>,
483 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
484 N: Network,
485 {
486 let client = ClientBuilder::default().http(url);
487 self.connect_client(client)
488 }
489
490 #[cfg(any(test, feature = "reqwest"))]
492 pub fn connect_reqwest<C>(self, client: C, url: reqwest::Url) -> F::Provider
493 where
494 L: ProviderLayer<crate::RootProvider<N>, N>,
495 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
496 N: Network,
497 C: Into<reqwest::Client>,
498 {
499 let client = ClientBuilder::default().http_with_client(client.into(), url);
500 self.connect_client(client)
501 }
502
503 #[cfg(any(test, feature = "reqwest"))]
505 pub fn with_reqwest<B>(self, url: reqwest::Url, builder: B) -> F::Provider
506 where
507 L: ProviderLayer<crate::RootProvider<N>, N>,
508 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
509 N: Network,
510 B: FnOnce(reqwest::ClientBuilder) -> reqwest::Client,
511 {
512 self.connect_reqwest(builder(reqwest::ClientBuilder::default()), url)
513 }
514
515 #[cfg(feature = "hyper")]
517 pub fn connect_hyper_http(self, url: url::Url) -> F::Provider
518 where
519 L: ProviderLayer<crate::RootProvider<N>, N>,
520 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
521 N: Network,
522 {
523 let client = ClientBuilder::default().hyper_http(url);
524 self.connect_client(client)
525 }
526}
527
528#[cfg(any(test, feature = "anvil-node"))]
529type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
530
531#[cfg(any(test, feature = "anvil-node"))]
532type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
533
534#[cfg(any(test, feature = "anvil-node"))]
535impl<L, F, N: Network> ProviderBuilder<L, F, N> {
536 pub fn connect_anvil(self) -> F::Provider
538 where
539 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
540 L: crate::builder::ProviderLayer<
541 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
542 N,
543 >,
544 {
545 self.connect_anvil_with_config(std::convert::identity)
546 }
547
548 pub fn connect_anvil_with_wallet(
552 self,
553 ) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider
554 where
555 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
556 L: crate::builder::ProviderLayer<
557 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
558 N,
559 >,
560 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
561 {
562 self.connect_anvil_with_wallet_and_config(std::convert::identity)
563 .expect("failed to build provider")
564 }
565
566 pub fn connect_anvil_with_config(
569 self,
570 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
571 ) -> F::Provider
572 where
573 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
574 L: crate::builder::ProviderLayer<
575 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
576 N,
577 >,
578 {
579 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
580 let url = anvil_layer.endpoint_url();
581
582 let rpc_client = ClientBuilder::default().http(url);
583
584 self.layer(anvil_layer).connect_client(rpc_client)
585 }
586
587 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_config` instead")]
590 pub fn on_anvil_with_config(
591 self,
592 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
593 ) -> F::Provider
594 where
595 L: ProviderLayer<crate::layers::AnvilProvider<RootProvider<N>, N>, N>,
596 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
597 {
598 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
599 let url = anvil_layer.endpoint_url();
600
601 let rpc_client = ClientBuilder::default().http(url);
602
603 self.layer(anvil_layer).connect_client(rpc_client)
604 }
605
606 pub fn connect_anvil_with_wallet_and_config(
609 self,
610 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
611 ) -> AnvilProviderResult<
612 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
613 >
614 where
615 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
616 L: crate::builder::ProviderLayer<
617 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
618 N,
619 >,
620 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
621 {
622 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
623 let url = anvil_layer.endpoint_url();
624
625 let wallet = anvil_layer
626 .instance()
627 .wallet()
628 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
629
630 let rpc_client = ClientBuilder::default().http(url);
631
632 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
633 }
634
635 #[deprecated(since = "0.12.6", note = "use `connect_anvil_with_wallet_and_config` instead")]
638 pub fn on_anvil_with_wallet_and_config(
639 self,
640 f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
641 ) -> AnvilProviderResult<
642 <JoinedEthereumWalletFiller<F> as ProviderLayer<L::Provider, N>>::Provider,
643 >
644 where
645 F: TxFiller<N> + ProviderLayer<L::Provider, N>,
646 L: crate::builder::ProviderLayer<
647 crate::layers::AnvilProvider<crate::provider::RootProvider<N>, N>,
648 N,
649 >,
650 alloy_network::EthereumWallet: alloy_network::NetworkWallet<N>,
651 {
652 let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
653 let url = anvil_layer.endpoint_url();
654
655 let wallet = anvil_layer
656 .instance()
657 .wallet()
658 .ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
659
660 let rpc_client = ClientBuilder::default().http(url);
661
662 Ok(self.wallet(wallet).layer(anvil_layer).connect_client(rpc_client))
663 }
664}
665
666#[cfg(test)]
667mod tests {
668 use super::*;
669 use crate::Provider;
670 use alloy_network::AnyNetwork;
671
672 #[tokio::test]
673 async fn basic() {
674 let provider = ProviderBuilder::new()
675 .with_cached_nonce_management()
676 .with_call_batching()
677 .connect_http("http://localhost:8545".parse().unwrap());
678 let _ = provider.get_account(Default::default());
679 let provider = provider.erased();
680 let _ = provider.get_account(Default::default());
681 }
682
683 #[tokio::test]
684 #[cfg(feature = "reqwest")]
685 async fn test_connect_reqwest() {
686 let provider = ProviderBuilder::new()
687 .with_cached_nonce_management()
688 .with_call_batching()
689 .connect_reqwest(
690 reqwest::Client::new(),
691 reqwest::Url::parse("http://localhost:8545").unwrap(),
692 );
693 let _ = provider.get_account(Default::default());
694 let provider = provider.erased();
695 let _ = provider.get_account(Default::default());
696 }
697
698 #[tokio::test]
699 #[cfg(feature = "reqwest")]
700 async fn test_with_reqwest() {
701 let provider = ProviderBuilder::new()
702 .with_cached_nonce_management()
703 .with_call_batching()
704 .with_reqwest(reqwest::Url::parse("http://localhost:8545").unwrap(), |builder| {
705 builder
706 .user_agent("alloy/test")
707 .timeout(std::time::Duration::from_secs(10))
708 .build()
709 .expect("failed to build reqwest client")
710 });
711 let _ = provider.get_account(Default::default());
712 let provider = provider.erased();
713 let _ = provider.get_account(Default::default());
714 }
715
716 #[tokio::test]
717 async fn compile_with_network() {
718 let p = ProviderBuilder::new_with_network::<AnyNetwork>().connect_anvil();
719 let num = p.get_block_number().await.unwrap();
720 assert_eq!(num, 0);
721 }
722}