use alloy_network::{Ethereum as AlloyEthereum, EthereumWallet, NetworkWallet, TransactionBuilder};
use alloy_primitives::{Address, B256, Bytes};
use alloy_provider::fillers::{
BlobGasFiller, ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller,
};
use alloy_provider::{
Identity, PendingTransactionError, Provider, ProviderBuilder, RootProvider, WalletProvider,
};
use alloy_rpc_client::RpcClient;
use alloy_rpc_types_eth::{BlockId, TransactionReceipt, TransactionRequest};
use alloy_signer::Signer;
use alloy_signer_local::PrivateKeySigner;
use alloy_transport::TransportError;
use alloy_transport::layers::{FallbackLayer, ThrottleLayer};
use alloy_transport_http::Http;
use std::num::NonZeroUsize;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, LazyLock};
use tower::ServiceBuilder;
use x402_types::chain::{ChainId, ChainProviderOps, FromConfig};
#[cfg(feature = "telemetry")]
use tracing::Instrument;
use crate::chain::config::{Eip155ChainConfig, RpcConfig};
use crate::chain::pending_nonce_manager::PendingNonceManager;
use crate::chain::permit2::{EXACT_PERMIT2_PROXY_ADDRESS, PERMIT2_ADDRESS};
use crate::chain::types::Eip155ChainReference;
use crate::v1_eip155_exact::VALIDATOR_ADDRESS;
pub type InnerFiller = JoinFill<
GasFiller,
JoinFill<BlobGasFiller, JoinFill<NonceFiller<PendingNonceManager>, ChainIdFiller>>,
>;
const REQUIRED_CONTRACT_ADDRESSES: LazyLock<Vec<Address>> = LazyLock::new(|| {
vec![
VALIDATOR_ADDRESS,
PERMIT2_ADDRESS,
EXACT_PERMIT2_PROXY_ADDRESS,
]
});
pub type InnerProvider = FillProvider<
JoinFill<JoinFill<Identity, InnerFiller>, WalletFiller<EthereumWallet>>,
RootProvider,
>;
#[derive(Debug)]
pub struct Eip155ChainProvider {
chain: Eip155ChainReference,
eip1559: bool,
flashblocks: bool,
receipt_timeout_secs: u64,
inner: InnerProvider,
signer_addresses: Arc<Vec<Address>>,
signer_cursor: Arc<AtomicUsize>,
nonce_manager: PendingNonceManager,
}
impl Eip155ChainProvider {
#[allow(unused_variables)] pub fn rpc_client(chain_id: ChainId, rpc: &[RpcConfig]) -> RpcClient {
let transports = rpc
.iter()
.filter_map(|provider_config| {
let scheme = provider_config.http.scheme();
let is_http = scheme == "http" || scheme == "https";
if !is_http {
return None;
}
let rpc_url = provider_config.http.deref().clone();
#[cfg(feature = "telemetry")]
tracing::info!(chain=%chain_id, rpc_url=%rpc_url, rate_limit=?provider_config.rate_limit, "Using HTTP transport");
let rate_limit = provider_config.rate_limit.unwrap_or(u32::MAX);
let service = ServiceBuilder::new()
.layer(ThrottleLayer::new(rate_limit))
.service(Http::new(rpc_url));
Some(service)
})
.collect::<Vec<_>>();
let fallback = ServiceBuilder::new()
.layer(
FallbackLayer::default().with_active_transport_count(
NonZeroUsize::new(transports.len())
.expect("Non-zero amount of stateless transports"),
),
)
.service(transports);
RpcClient::new(fallback, false)
}
fn next_signer_address(&self) -> Address {
debug_assert!(!self.signer_addresses.is_empty());
if self.signer_addresses.len() == 1 {
self.signer_addresses[0]
} else {
let next =
self.signer_cursor.fetch_add(1, Ordering::Relaxed) % self.signer_addresses.len();
self.signer_addresses[next]
}
}
}
#[async_trait::async_trait]
impl FromConfig<Eip155ChainConfig> for Eip155ChainProvider {
async fn from_config(config: &Eip155ChainConfig) -> Result<Self, Box<dyn std::error::Error>> {
let signers = config
.signers()
.iter()
.map(|s| B256::from_slice(s.inner().as_bytes()))
.map(|b| {
PrivateKeySigner::from_bytes(&b)
.map(|s| s.with_chain_id(Some(config.chain_reference().inner())))
})
.collect::<Result<Vec<_>, _>>()?;
if signers.is_empty() {
return Err("at least one signer should be provided".into());
}
let wallet = {
let mut iter = signers.into_iter();
let first_signer = iter
.next()
.expect("iterator contains at least one element by construction");
let mut wallet = EthereumWallet::from(first_signer);
for signer in iter {
wallet.register_signer(signer);
}
wallet
};
let signer_addresses =
NetworkWallet::<AlloyEthereum>::signer_addresses(&wallet).collect::<Vec<_>>();
let signer_addresses = Arc::new(signer_addresses);
let signer_cursor = Arc::new(AtomicUsize::new(0));
let client = Self::rpc_client(config.chain_id(), config.rpc());
let nonce_manager = PendingNonceManager::default();
let filler = JoinFill::new(
GasFiller,
JoinFill::new(
BlobGasFiller::default(),
JoinFill::new(
NonceFiller::new(nonce_manager.clone()),
ChainIdFiller::default(),
),
),
);
let inner: InnerProvider = ProviderBuilder::default()
.filler(filler)
.wallet(wallet)
.connect_client(client);
assert_contracts_exists(&inner).await?;
#[cfg(feature = "telemetry")]
tracing::info!(chain=%config.chain_id(), signers=?signer_addresses, "Using EVM provider");
Ok(Self {
chain: config.chain_reference(),
eip1559: config.eip1559(),
flashblocks: config.flashblocks(),
receipt_timeout_secs: config.receipt_timeout_secs(),
inner,
signer_addresses,
signer_cursor,
nonce_manager,
})
}
}
impl Eip155MetaTransactionProvider for Eip155ChainProvider {
type Error = MetaTransactionSendError;
type Inner = InnerProvider;
fn inner(&self) -> &Self::Inner {
&self.inner
}
fn chain(&self) -> &Eip155ChainReference {
&self.chain
}
async fn send_transaction(
&self,
tx: MetaTransaction,
) -> Result<TransactionReceipt, Self::Error> {
let from_address = self.next_signer_address();
let mut txr = TransactionRequest::default()
.with_to(tx.to)
.with_from(from_address)
.with_input(tx.calldata);
if !self.eip1559 {
let provider = &self.inner;
let gas_fut = provider.get_gas_price();
#[cfg(feature = "telemetry")]
let gas: u128 = gas_fut
.instrument(tracing::info_span!("get_gas_price"))
.await?;
#[cfg(not(feature = "telemetry"))]
let gas: u128 = gas_fut.await?;
txr.set_gas_price(gas);
}
if txr.gas.is_none() {
let block_id = if self.flashblocks {
BlockId::latest()
} else {
BlockId::pending()
};
let gas_limit = self.inner.estimate_gas(txr.clone()).block(block_id).await?;
txr.set_gas_limit(gas_limit)
}
let pending_tx = match self.inner.send_transaction(txr).await {
Ok(pending) => pending,
Err(e) => {
self.nonce_manager.reset_nonce(from_address).await;
return Err(MetaTransactionSendError::Transport(e));
}
};
let timeout = std::time::Duration::from_secs(self.receipt_timeout_secs);
let watcher = pending_tx
.with_required_confirmations(tx.confirmations)
.with_timeout(Some(timeout));
match watcher.get_receipt().await {
Ok(receipt) => Ok(receipt),
Err(e) => {
self.nonce_manager.reset_nonce(from_address).await;
Err(MetaTransactionSendError::PendingTransaction(e))
}
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum MetaTransactionSendError {
#[error(transparent)]
Transport(#[from] TransportError),
#[error(transparent)]
PendingTransaction(#[from] PendingTransactionError),
#[allow(dead_code)] #[error("{0}")]
Custom(String),
}
impl ChainProviderOps for Eip155ChainProvider {
fn signer_addresses(&self) -> Vec<String> {
self.inner
.signer_addresses()
.map(|a| a.to_string())
.collect()
}
fn chain_id(&self) -> ChainId {
self.chain.into()
}
}
pub struct MetaTransaction {
pub to: Address,
pub calldata: Bytes,
pub confirmations: u64,
}
impl MetaTransaction {
pub fn new(to: Address, calldata: Bytes) -> Self {
Self {
to,
calldata,
confirmations: 1,
}
}
}
pub trait Eip155MetaTransactionProvider {
type Error;
type Inner: Provider;
fn inner(&self) -> &Self::Inner;
fn chain(&self) -> &Eip155ChainReference;
fn send_transaction(
&self,
tx: MetaTransaction,
) -> impl Future<Output = Result<TransactionReceipt, Self::Error>> + Send;
}
impl<T: Eip155MetaTransactionProvider> Eip155MetaTransactionProvider for Arc<T> {
type Error = T::Error;
type Inner = T::Inner;
fn inner(&self) -> &Self::Inner {
(**self).inner()
}
fn chain(&self) -> &Eip155ChainReference {
(**self).chain()
}
fn send_transaction(
&self,
tx: MetaTransaction,
) -> impl Future<Output = Result<TransactionReceipt, Self::Error>> + Send {
(**self).send_transaction(tx)
}
}
pub async fn assert_contracts_exists<P: Provider>(
provider: &P,
) -> Result<(), Box<dyn std::error::Error>> {
for address in REQUIRED_CONTRACT_ADDRESSES.deref() {
let code = provider.get_code_at(*address).await?;
if code.is_empty() {
return Err(
format!("Contract at address {address} does not exist (empty code)").into(),
);
}
}
Ok(())
}