use crate::{
fillers::{
CachedNonceManager, ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller,
NonceManager, RecommendedFillers, SimpleNonceManager, TxFiller, WalletFiller,
},
provider::SendableTx,
Provider, RootProvider,
};
use alloy_chains::NamedChain;
use alloy_network::{Ethereum, Network};
use alloy_primitives::ChainId;
use alloy_rpc_client::{BuiltInConnectionString, ClientBuilder, RpcClient};
use alloy_transport::{BoxTransport, Transport, TransportError, TransportResult};
use std::marker::PhantomData;
pub trait ProviderLayer<P: Provider<T, N>, T: Transport + Clone, N: Network = Ethereum> {
type Provider: Provider<T, N>;
fn layer(&self, inner: P) -> Self::Provider;
}
#[derive(Clone, Copy, Debug)]
pub struct Identity;
impl<N> TxFiller<N> for Identity
where
N: Network,
{
type Fillable = ();
fn status(&self, _tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
FillerControlFlow::Finished
}
fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
async fn prepare<P, T>(
&self,
_provider: &P,
_tx: &N::TransactionRequest,
) -> TransportResult<Self::Fillable> {
Ok(())
}
async fn fill(
&self,
_to_fill: Self::Fillable,
tx: SendableTx<N>,
) -> TransportResult<SendableTx<N>> {
Ok(tx)
}
}
impl<P, T, N> ProviderLayer<P, T, N> for Identity
where
T: Transport + Clone,
N: Network,
P: Provider<T, N>,
{
type Provider = P;
fn layer(&self, inner: P) -> Self::Provider {
inner
}
}
#[derive(Debug)]
pub struct Stack<Inner, Outer> {
inner: Inner,
outer: Outer,
}
impl<Inner, Outer> Stack<Inner, Outer> {
pub const fn new(inner: Inner, outer: Outer) -> Self {
Self { inner, outer }
}
}
impl<P, T, N, Inner, Outer> ProviderLayer<P, T, N> for Stack<Inner, Outer>
where
T: Transport + Clone,
N: Network,
P: Provider<T, N>,
Inner: ProviderLayer<P, T, N>,
Outer: ProviderLayer<Inner::Provider, T, N>,
{
type Provider = Outer::Provider;
fn layer(&self, provider: P) -> Self::Provider {
let inner = self.inner.layer(provider);
self.outer.layer(inner)
}
}
#[derive(Debug)]
pub struct ProviderBuilder<L, F, N = Ethereum> {
layer: L,
filler: F,
network: PhantomData<fn() -> N>,
}
impl ProviderBuilder<Identity, Identity, Ethereum> {
pub const fn new() -> Self {
Self { layer: Identity, filler: Identity, network: PhantomData }
}
}
impl<N> Default for ProviderBuilder<Identity, Identity, N> {
fn default() -> Self {
Self { layer: Identity, filler: Identity, network: PhantomData }
}
}
impl<L, N: Network> ProviderBuilder<L, Identity, N> {
pub fn with_recommended_fillers(
self,
) -> ProviderBuilder<L, JoinFill<Identity, N::RecomendedFillers>, N>
where
N: RecommendedFillers,
{
self.filler(N::recommended_fillers())
}
pub fn with_gas_estimation(self) -> ProviderBuilder<L, JoinFill<Identity, GasFiller>, N> {
self.filler(GasFiller)
}
pub fn with_nonce_management<M: NonceManager>(
self,
nonce_manager: M,
) -> ProviderBuilder<L, JoinFill<Identity, NonceFiller<M>>, N> {
self.filler(NonceFiller::new(nonce_manager))
}
pub fn with_simple_nonce_management(
self,
) -> ProviderBuilder<L, JoinFill<Identity, NonceFiller>, N> {
self.with_nonce_management(SimpleNonceManager::default())
}
pub fn with_cached_nonce_management(
self,
) -> ProviderBuilder<L, JoinFill<Identity, NonceFiller<CachedNonceManager>>, N> {
self.with_nonce_management(CachedNonceManager::default())
}
pub fn fetch_chain_id(self) -> ProviderBuilder<L, JoinFill<Identity, ChainIdFiller>, N> {
self.filler(ChainIdFiller::default())
}
pub fn with_chain_id(
self,
chain_id: ChainId,
) -> ProviderBuilder<L, JoinFill<Identity, ChainIdFiller>, N> {
self.filler(ChainIdFiller::new(Some(chain_id)))
}
}
impl<L, F, N> ProviderBuilder<L, F, N> {
pub fn layer<Inner>(self, layer: Inner) -> ProviderBuilder<Stack<Inner, L>, F, N> {
ProviderBuilder {
layer: Stack::new(layer, self.layer),
filler: self.filler,
network: PhantomData,
}
}
pub fn filler<F2>(self, filler: F2) -> ProviderBuilder<L, JoinFill<F, F2>, N> {
ProviderBuilder {
layer: self.layer,
filler: JoinFill::new(self.filler, filler),
network: PhantomData,
}
}
pub fn wallet<W>(self, wallet: W) -> ProviderBuilder<L, JoinFill<F, WalletFiller<W>>, N> {
self.filler(WalletFiller::new(wallet))
}
pub fn network<Net: Network>(self) -> ProviderBuilder<L, F, Net> {
ProviderBuilder { layer: self.layer, filler: self.filler, network: PhantomData }
}
pub fn with_chain(
self,
chain: NamedChain,
) -> ProviderBuilder<Stack<crate::layers::ChainLayer, L>, F, N> {
let chain_layer = crate::layers::ChainLayer::from(chain);
self.layer(chain_layer)
}
pub fn on_provider<P, T>(self, provider: P) -> F::Provider
where
L: ProviderLayer<P, T, N>,
F: TxFiller<N> + ProviderLayer<L::Provider, T, N>,
P: Provider<T, N>,
T: Transport + Clone,
N: Network,
{
let Self { layer, filler, .. } = self;
let stack = Stack::new(layer, filler);
stack.layer(provider)
}
pub fn on_client<T>(self, client: RpcClient<T>) -> F::Provider
where
L: ProviderLayer<RootProvider<T, N>, T, N>,
F: TxFiller<N> + ProviderLayer<L::Provider, T, N>,
T: Transport + Clone,
N: Network,
{
self.on_provider(RootProvider::new(client))
}
pub async fn on_builtin(self, s: &str) -> Result<F::Provider, TransportError>
where
L: ProviderLayer<RootProvider<BoxTransport, N>, BoxTransport, N>,
F: TxFiller<N> + ProviderLayer<L::Provider, BoxTransport, N>,
N: Network,
{
let connect: BuiltInConnectionString = s.parse()?;
let client = ClientBuilder::default().connect_boxed(connect).await?;
Ok(self.on_client(client))
}
#[cfg(feature = "ws")]
pub async fn on_ws(
self,
connect: alloy_transport_ws::WsConnect,
) -> Result<F::Provider, TransportError>
where
L: ProviderLayer<
RootProvider<alloy_pubsub::PubSubFrontend, N>,
alloy_pubsub::PubSubFrontend,
N,
>,
F: TxFiller<N> + ProviderLayer<L::Provider, alloy_pubsub::PubSubFrontend, N>,
N: Network,
{
let client = ClientBuilder::default().ws(connect).await?;
Ok(self.on_client(client))
}
#[cfg(feature = "ipc")]
pub async fn on_ipc<T>(
self,
connect: alloy_transport_ipc::IpcConnect<T>,
) -> Result<F::Provider, TransportError>
where
alloy_transport_ipc::IpcConnect<T>: alloy_pubsub::PubSubConnect,
L: ProviderLayer<
RootProvider<alloy_pubsub::PubSubFrontend, N>,
alloy_pubsub::PubSubFrontend,
N,
>,
F: TxFiller<N> + ProviderLayer<L::Provider, alloy_pubsub::PubSubFrontend, N>,
N: Network,
{
let client = ClientBuilder::default().ipc(connect).await?;
Ok(self.on_client(client))
}
#[cfg(any(test, feature = "reqwest"))]
pub fn on_http(self, url: reqwest::Url) -> F::Provider
where
L: ProviderLayer<crate::ReqwestProvider<N>, alloy_transport_http::Http<reqwest::Client>, N>,
F: TxFiller<N> + ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, N>,
N: Network,
{
let client = ClientBuilder::default().http(url);
self.on_client(client)
}
#[cfg(feature = "hyper")]
pub fn on_hyper_http(self, url: url::Url) -> F::Provider
where
L: ProviderLayer<crate::HyperProvider<N>, alloy_transport_http::HyperTransport, N>,
F: TxFiller<N> + ProviderLayer<L::Provider, alloy_transport_http::HyperTransport, N>,
N: Network,
{
let client = ClientBuilder::default().hyper_http(url);
self.on_client(client)
}
}
#[cfg(any(test, feature = "anvil-node"))]
type JoinedEthereumWalletFiller<F> = JoinFill<F, WalletFiller<alloy_network::EthereumWallet>>;
#[cfg(any(test, feature = "anvil-node"))]
type AnvilProviderResult<T> = Result<T, alloy_node_bindings::NodeError>;
#[cfg(any(test, feature = "anvil-node"))]
impl<L, F> ProviderBuilder<L, F, Ethereum> {
pub fn on_anvil(self) -> F::Provider
where
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
>,
{
self.on_anvil_with_config(std::convert::identity)
}
pub fn on_anvil_with_wallet(
self,
) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<
L::Provider,
alloy_transport_http::Http<reqwest::Client>,
>>::Provider
where
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
>,
{
self.on_anvil_with_wallet_and_config(std::convert::identity)
}
pub fn on_anvil_with_config(
self,
f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
) -> F::Provider
where
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
>,
{
let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
let url = anvil_layer.endpoint_url();
self.layer(anvil_layer).on_http(url)
}
pub fn on_anvil_with_wallet_and_config(
self,
f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
) -> <JoinedEthereumWalletFiller<F> as ProviderLayer<
L::Provider,
alloy_transport_http::Http<reqwest::Client>,
>>::Provider
where
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
>,
{
self.try_on_anvil_with_wallet_and_config(f).unwrap()
}
pub fn try_on_anvil_with_wallet_and_config(
self,
f: impl FnOnce(alloy_node_bindings::Anvil) -> alloy_node_bindings::Anvil,
) -> AnvilProviderResult<
<JoinedEthereumWalletFiller<F> as ProviderLayer<
L::Provider,
alloy_transport_http::Http<reqwest::Client>,
>>::Provider,
>
where
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
L: crate::builder::ProviderLayer<
crate::layers::AnvilProvider<
crate::provider::RootProvider<alloy_transport_http::Http<reqwest::Client>>,
alloy_transport_http::Http<reqwest::Client>,
>,
alloy_transport_http::Http<reqwest::Client>,
>,
{
use alloy_signer::Signer;
let anvil_layer = crate::layers::AnvilLayer::from(f(Default::default()));
let url = anvil_layer.endpoint_url();
let default_keys = anvil_layer.instance().keys().to_vec();
let (default_key, remaining_keys) =
default_keys.split_first().ok_or(alloy_node_bindings::NodeError::NoKeysAvailable)?;
let default_signer = alloy_signer_local::LocalSigner::from(default_key.clone())
.with_chain_id(Some(anvil_layer.instance().chain_id()));
let mut wallet = alloy_network::EthereumWallet::from(default_signer);
for key in remaining_keys {
wallet.register_signer(alloy_signer_local::LocalSigner::from(key.clone()))
}
Ok(self.wallet(wallet).layer(anvil_layer).on_http(url))
}
}