mod chain_id;
use alloy_eips::{BlockId, BlockNumberOrTag};
use alloy_primitives::{
Address, BlockHash, BlockNumber, StorageKey, StorageValue, TxHash, B256, U128, U256,
};
use alloy_rpc_client::NoParams;
#[cfg(feature = "pubsub")]
use alloy_rpc_types_eth::pubsub::{Params, SubscriptionKind};
use alloy_rpc_types_eth::{Bundle, Index, SyncStatus};
pub use chain_id::ChainIdFiller;
use std::borrow::Cow;
mod wallet;
pub use wallet::WalletFiller;
mod nonce;
pub use nonce::{CachedNonceManager, NonceFiller, NonceManager, SimpleNonceManager};
mod gas;
pub use gas::{
BlobGasEstimator, BlobGasEstimatorFn, BlobGasEstimatorFunction, BlobGasFiller, GasFillable,
GasFiller,
};
mod join_fill;
pub use join_fill::JoinFill;
use tracing::error;
#[cfg(feature = "pubsub")]
use crate::GetSubscription;
use crate::{
provider::SendableTx, EthCall, EthCallMany, EthGetBlock, FilterPollerBuilder, Identity,
PendingTransaction, PendingTransactionBuilder, PendingTransactionConfig,
PendingTransactionError, Provider, ProviderCall, ProviderLayer, RootProvider, RpcWithBlock,
SendableTxErr,
};
use alloy_json_rpc::RpcError;
use alloy_network::{AnyNetwork, Ethereum, Network};
use alloy_primitives::{Bytes, U64};
use alloy_rpc_types_eth::{
erc4337::TransactionConditional,
simulate::{SimulatePayload, SimulatedBlock},
AccessListResult, EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Filter,
FilterChanges, Log, StorageValuesRequest, StorageValuesResponse,
};
use alloy_transport::{TransportError, TransportResult};
use async_trait::async_trait;
use futures_utils_wasm::impl_future;
use serde_json::value::RawValue;
use std::marker::PhantomData;
pub type RecommendedFiller =
JoinFill<JoinFill<JoinFill<Identity, GasFiller>, NonceFiller>, ChainIdFiller>;
#[derive(Debug, thiserror::Error)]
pub enum FillEnvelopeError<T> {
#[error("transport error during filling: {0}")]
Transport(TransportError),
#[error("transaction not ready: {0}")]
NotReady(SendableTxErr<T>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FillerControlFlow {
Missing(Vec<(&'static str, Vec<&'static str>)>),
Ready,
Finished,
}
impl FillerControlFlow {
pub fn absorb(self, other: Self) -> Self {
if other.is_finished() {
return self;
}
if self.is_finished() {
return other;
}
if other.is_ready() || self.is_ready() {
return Self::Ready;
}
if let (Self::Missing(mut a), Self::Missing(b)) = (self, other) {
a.extend(b);
return Self::Missing(a);
}
unreachable!()
}
pub fn missing(name: &'static str, missing: Vec<&'static str>) -> Self {
Self::Missing(vec![(name, missing)])
}
pub fn as_missing(&self) -> Option<&[(&'static str, Vec<&'static str>)]> {
match self {
Self::Missing(missing) => Some(missing),
_ => None,
}
}
pub const fn is_missing(&self) -> bool {
matches!(self, Self::Missing(_))
}
pub const fn is_ready(&self) -> bool {
matches!(self, Self::Ready)
}
pub const fn is_finished(&self) -> bool {
matches!(self, Self::Finished)
}
}
#[doc(alias = "TransactionFiller")]
pub trait TxFiller<N: Network = Ethereum>: Clone + Send + Sync + std::fmt::Debug {
type Fillable: Send + Sync + 'static;
fn join_with<T>(self, other: T) -> JoinFill<Self, T>
where
T: TxFiller<N>,
{
JoinFill::new(self, other)
}
fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow;
fn continue_filling(&self, tx: &SendableTx<N>) -> bool {
tx.as_builder().is_some_and(|tx| self.status(tx).is_ready())
}
fn ready(&self, tx: &N::TransactionRequest) -> bool {
self.status(tx).is_ready()
}
fn finished(&self, tx: &N::TransactionRequest) -> bool {
self.status(tx).is_finished()
}
fn fill_sync(&self, tx: &mut SendableTx<N>);
fn prepare<P: Provider<N>>(
&self,
provider: &P,
tx: &N::TransactionRequest,
) -> impl_future!(<Output = TransportResult<Self::Fillable>>);
fn fill(
&self,
fillable: Self::Fillable,
tx: SendableTx<N>,
) -> impl_future!(<Output = TransportResult<SendableTx<N>>>);
fn fill_envelope(
&self,
fillable: Self::Fillable,
tx: SendableTx<N>,
) -> impl_future!(<Output = Result<N::TxEnvelope, FillEnvelopeError<N::TransactionRequest>>>)
{
async move {
let tx = self.fill(fillable, tx).await.map_err(FillEnvelopeError::Transport)?;
let envelope = tx.try_into_envelope().map_err(FillEnvelopeError::NotReady)?;
Ok(envelope)
}
}
fn prepare_and_fill<P>(
&self,
provider: &P,
tx: SendableTx<N>,
) -> impl_future!(<Output = TransportResult<SendableTx<N>>>)
where
P: Provider<N>,
{
async move {
if tx.is_envelope() {
return Ok(tx);
}
let fillable =
self.prepare(provider, tx.as_builder().expect("checked by is_envelope")).await?;
self.fill(fillable, tx).await
}
}
fn prepare_call(
&self,
tx: &mut N::TransactionRequest,
) -> impl_future!(<Output = TransportResult<()>>) {
let _ = tx;
futures::future::ready(Ok(()))
}
fn prepare_call_sync(&self, tx: &mut N::TransactionRequest) -> TransportResult<()> {
let _ = tx;
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct FillProvider<F, P, N = Ethereum>
where
F: TxFiller<N>,
P: Provider<N>,
N: Network,
{
pub(crate) inner: P,
pub(crate) filler: F,
_pd: PhantomData<fn() -> N>,
}
impl<F, P, N> FillProvider<F, P, N>
where
F: TxFiller<N>,
P: Provider<N>,
N: Network,
{
pub fn new(inner: P, filler: F) -> Self {
Self { inner, filler, _pd: PhantomData }
}
pub const fn filler(&self) -> &F {
&self.filler
}
pub const fn filler_mut(&mut self) -> &mut F {
&mut self.filler
}
pub const fn inner(&self) -> &P {
&self.inner
}
pub const fn inner_mut(&mut self) -> &mut P {
&mut self.inner
}
pub fn join_with<Other: TxFiller<N>>(
self,
other: Other,
) -> FillProvider<JoinFill<F, Other>, P, N> {
self.filler.join_with(other).layer(self.inner)
}
async fn fill_inner(&self, mut tx: SendableTx<N>) -> TransportResult<SendableTx<N>> {
let mut count = 0;
while self.filler.continue_filling(&tx) {
self.filler.fill_sync(&mut tx);
tx = self.filler.prepare_and_fill(&self.inner, tx).await?;
count += 1;
if count >= 20 {
const ERROR: &str = "Tx filler loop detected. This indicates a bug in some filler implementation. Please file an issue containing this message.";
error!(
?tx, ?self.filler,
ERROR
);
panic!("{}, {:?}, {:?}", ERROR, &tx, &self.filler);
}
}
Ok(tx)
}
pub async fn fill(&self, tx: N::TransactionRequest) -> TransportResult<SendableTx<N>> {
self.fill_inner(SendableTx::Builder(tx)).await
}
pub fn prepare_call(
&self,
mut tx: N::TransactionRequest,
) -> TransportResult<N::TransactionRequest> {
self.filler.prepare_call_sync(&mut tx)?;
Ok(tx)
}
}
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
#[cfg_attr(not(target_family = "wasm"), async_trait)]
impl<F, P, N> Provider<N> for FillProvider<F, P, N>
where
F: TxFiller<N>,
P: Provider<N>,
N: Network,
{
fn root(&self) -> &RootProvider<N> {
self.inner.root()
}
fn get_accounts(&self) -> ProviderCall<NoParams, Vec<Address>> {
self.inner.get_accounts()
}
fn get_blob_base_fee(&self) -> ProviderCall<NoParams, U128, u128> {
self.inner.get_blob_base_fee()
}
fn get_block_number(&self) -> ProviderCall<NoParams, U64, BlockNumber> {
self.inner.get_block_number()
}
fn call<'req>(&self, tx: N::TransactionRequest) -> EthCall<N, Bytes> {
let mut tx = tx;
let _ = self.filler.prepare_call_sync(&mut tx);
self.inner.call(tx)
}
fn call_many<'req>(
&self,
bundles: &'req [Bundle],
) -> EthCallMany<'req, N, Vec<Vec<EthCallResponse>>> {
self.inner.call_many(bundles)
}
fn simulate<'req>(
&self,
payload: &'req SimulatePayload,
) -> RpcWithBlock<&'req SimulatePayload, Vec<SimulatedBlock<N::BlockResponse>>> {
self.inner.simulate(payload)
}
fn get_chain_id(&self) -> ProviderCall<NoParams, U64, u64> {
self.inner.get_chain_id()
}
fn create_access_list<'a>(
&self,
request: &'a N::TransactionRequest,
) -> RpcWithBlock<&'a N::TransactionRequest, AccessListResult> {
self.inner.create_access_list(request)
}
fn estimate_gas<'req>(&self, tx: N::TransactionRequest) -> EthCall<N, U64, u64> {
let mut tx = tx;
let _ = self.filler.prepare_call_sync(&mut tx);
self.inner.estimate_gas(tx)
}
async fn get_fee_history(
&self,
block_count: u64,
last_block: BlockNumberOrTag,
reward_percentiles: &[f64],
) -> TransportResult<FeeHistory> {
self.inner.get_fee_history(block_count, last_block, reward_percentiles).await
}
fn get_gas_price(&self) -> ProviderCall<NoParams, U128, u128> {
self.inner.get_gas_price()
}
fn get_account_info(
&self,
address: Address,
) -> RpcWithBlock<Address, alloy_rpc_types_eth::AccountInfo> {
self.inner.get_account_info(address)
}
fn get_account(&self, address: Address) -> RpcWithBlock<Address, alloy_consensus::TrieAccount> {
self.inner.get_account(address)
}
fn get_balance(&self, address: Address) -> RpcWithBlock<Address, U256, U256> {
self.inner.get_balance(address)
}
fn get_block(&self, block: BlockId) -> EthGetBlock<N::BlockResponse> {
self.inner.get_block(block)
}
fn get_block_by_hash(&self, hash: BlockHash) -> EthGetBlock<N::BlockResponse> {
self.inner.get_block_by_hash(hash)
}
fn get_block_by_number(&self, number: BlockNumberOrTag) -> EthGetBlock<N::BlockResponse> {
self.inner.get_block_by_number(number)
}
async fn get_block_transaction_count_by_hash(
&self,
hash: BlockHash,
) -> TransportResult<Option<u64>> {
self.inner.get_block_transaction_count_by_hash(hash).await
}
async fn get_block_transaction_count_by_number(
&self,
block_number: BlockNumberOrTag,
) -> TransportResult<Option<u64>> {
self.inner.get_block_transaction_count_by_number(block_number).await
}
fn get_block_receipts(
&self,
block: BlockId,
) -> ProviderCall<(BlockId,), Option<Vec<N::ReceiptResponse>>> {
self.inner.get_block_receipts(block)
}
async fn get_header(&self, block: BlockId) -> TransportResult<Option<N::HeaderResponse>> {
self.inner.get_header(block).await
}
async fn get_header_by_hash(
&self,
hash: BlockHash,
) -> TransportResult<Option<N::HeaderResponse>> {
self.inner.get_header_by_hash(hash).await
}
async fn get_header_by_number(
&self,
number: BlockNumberOrTag,
) -> TransportResult<Option<N::HeaderResponse>> {
self.inner.get_header_by_number(number).await
}
fn get_code_at(&self, address: Address) -> RpcWithBlock<Address, Bytes> {
self.inner.get_code_at(address)
}
async fn watch_blocks(&self) -> TransportResult<FilterPollerBuilder<B256>> {
self.inner.watch_blocks().await
}
async fn watch_pending_transactions(&self) -> TransportResult<FilterPollerBuilder<B256>> {
self.inner.watch_pending_transactions().await
}
async fn watch_logs(&self, filter: &Filter) -> TransportResult<FilterPollerBuilder<Log>> {
self.inner.watch_logs(filter).await
}
async fn watch_full_pending_transactions(
&self,
) -> TransportResult<FilterPollerBuilder<N::TransactionResponse>> {
self.inner.watch_full_pending_transactions().await
}
async fn get_filter_changes_dyn(&self, id: U256) -> TransportResult<FilterChanges> {
self.inner.get_filter_changes_dyn(id).await
}
async fn get_filter_logs(&self, id: U256) -> TransportResult<Vec<Log>> {
self.inner.get_filter_logs(id).await
}
async fn uninstall_filter(&self, id: U256) -> TransportResult<bool> {
self.inner.uninstall_filter(id).await
}
async fn watch_pending_transaction(
&self,
config: PendingTransactionConfig,
) -> Result<PendingTransaction, PendingTransactionError> {
self.inner.watch_pending_transaction(config).await
}
async fn get_logs(&self, filter: &Filter) -> TransportResult<Vec<Log>> {
self.inner.get_logs(filter).await
}
fn get_proof(
&self,
address: Address,
keys: Vec<StorageKey>,
) -> RpcWithBlock<(Address, Vec<StorageKey>), EIP1186AccountProofResponse> {
self.inner.get_proof(address, keys)
}
fn get_storage_at(
&self,
address: Address,
key: U256,
) -> RpcWithBlock<(Address, U256), StorageValue> {
self.inner.get_storage_at(address, key)
}
fn get_storage_values(
&self,
requests: StorageValuesRequest,
) -> RpcWithBlock<(StorageValuesRequest,), StorageValuesResponse> {
self.inner.get_storage_values(requests)
}
fn get_transaction_by_hash(
&self,
hash: TxHash,
) -> ProviderCall<(TxHash,), Option<N::TransactionResponse>> {
self.inner.get_transaction_by_hash(hash)
}
fn get_transaction_by_sender_nonce(
&self,
sender: Address,
nonce: u64,
) -> ProviderCall<(Address, U64), Option<N::TransactionResponse>> {
self.inner.get_transaction_by_sender_nonce(sender, nonce)
}
fn get_transaction_by_block_hash_and_index(
&self,
block_hash: B256,
index: usize,
) -> ProviderCall<(B256, Index), Option<N::TransactionResponse>> {
self.inner.get_transaction_by_block_hash_and_index(block_hash, index)
}
fn get_raw_transaction_by_block_hash_and_index(
&self,
block_hash: B256,
index: usize,
) -> ProviderCall<(B256, Index), Option<Bytes>> {
self.inner.get_raw_transaction_by_block_hash_and_index(block_hash, index)
}
fn get_transaction_by_block_number_and_index(
&self,
block_number: BlockNumberOrTag,
index: usize,
) -> ProviderCall<(BlockNumberOrTag, Index), Option<N::TransactionResponse>> {
self.inner.get_transaction_by_block_number_and_index(block_number, index)
}
fn get_raw_transaction_by_block_number_and_index(
&self,
block_number: BlockNumberOrTag,
index: usize,
) -> ProviderCall<(BlockNumberOrTag, Index), Option<Bytes>> {
self.inner.get_raw_transaction_by_block_number_and_index(block_number, index)
}
fn get_raw_transaction_by_hash(&self, hash: TxHash) -> ProviderCall<(TxHash,), Option<Bytes>> {
self.inner.get_raw_transaction_by_hash(hash)
}
fn get_transaction_count(
&self,
address: Address,
) -> RpcWithBlock<Address, U64, u64, fn(U64) -> u64> {
self.inner.get_transaction_count(address)
}
fn get_transaction_receipt(
&self,
hash: TxHash,
) -> ProviderCall<(TxHash,), Option<N::ReceiptResponse>> {
self.inner.get_transaction_receipt(hash)
}
async fn get_uncle(&self, tag: BlockId, idx: u64) -> TransportResult<Option<N::BlockResponse>> {
self.inner.get_uncle(tag, idx).await
}
async fn get_uncle_count(&self, tag: BlockId) -> TransportResult<u64> {
self.inner.get_uncle_count(tag).await
}
fn get_max_priority_fee_per_gas(&self) -> ProviderCall<NoParams, U128, u128> {
self.inner.get_max_priority_fee_per_gas()
}
async fn new_block_filter(&self) -> TransportResult<U256> {
self.inner.new_block_filter().await
}
async fn new_filter(&self, filter: &Filter) -> TransportResult<U256> {
self.inner.new_filter(filter).await
}
async fn new_pending_transactions_filter(&self, full: bool) -> TransportResult<U256> {
self.inner.new_pending_transactions_filter(full).await
}
async fn send_raw_transaction(
&self,
encoded_tx: &[u8],
) -> TransportResult<PendingTransactionBuilder<N>> {
self.inner.send_raw_transaction(encoded_tx).await
}
async fn send_raw_transaction_conditional(
&self,
encoded_tx: &[u8],
conditional: TransactionConditional,
) -> TransportResult<PendingTransactionBuilder<N>> {
self.inner.send_raw_transaction_conditional(encoded_tx, conditional).await
}
async fn send_transaction_internal(
&self,
mut tx: SendableTx<N>,
) -> TransportResult<PendingTransactionBuilder<N>> {
tx = self.fill_inner(tx).await?;
if let Some(builder) = tx.as_builder() {
if let FillerControlFlow::Missing(missing) = self.filler.status(builder) {
let message = format!("missing properties: {missing:?}");
return Err(RpcError::local_usage_str(&message));
}
}
self.inner.send_transaction_internal(tx).await
}
async fn send_transaction_sync_internal(
&self,
mut tx: SendableTx<N>,
) -> TransportResult<N::ReceiptResponse> {
tx = self.fill_inner(tx).await?;
if let Some(builder) = tx.as_builder() {
if let FillerControlFlow::Missing(missing) = self.filler.status(builder) {
let message = format!("missing properties: {missing:?}");
return Err(RpcError::local_usage_str(&message));
}
}
self.inner.send_transaction_sync_internal(tx).await
}
async fn sign_transaction(&self, tx: N::TransactionRequest) -> TransportResult<Bytes> {
let tx = self.fill(tx).await?;
let tx = tx.try_into_request().map_err(TransportError::local_usage)?;
self.inner.sign_transaction(tx).await
}
#[cfg(feature = "pubsub")]
fn subscribe_blocks(&self) -> GetSubscription<(SubscriptionKind,), N::HeaderResponse> {
self.inner.subscribe_blocks()
}
#[cfg(feature = "pubsub")]
fn subscribe_pending_transactions(&self) -> GetSubscription<(SubscriptionKind,), B256> {
self.inner.subscribe_pending_transactions()
}
#[cfg(feature = "pubsub")]
fn subscribe_full_pending_transactions(
&self,
) -> GetSubscription<(SubscriptionKind, Params), N::TransactionResponse> {
self.inner.subscribe_full_pending_transactions()
}
#[cfg(feature = "pubsub")]
fn subscribe_logs(&self, filter: &Filter) -> GetSubscription<(SubscriptionKind, Params), Log> {
self.inner.subscribe_logs(filter)
}
#[cfg(feature = "pubsub")]
async fn unsubscribe(&self, id: B256) -> TransportResult<()> {
self.inner.unsubscribe(id).await
}
fn syncing(&self) -> ProviderCall<NoParams, SyncStatus> {
self.inner.syncing()
}
fn get_client_version(&self) -> ProviderCall<NoParams, String> {
self.inner.get_client_version()
}
fn get_sha3(&self, data: &[u8]) -> ProviderCall<(String,), B256> {
self.inner.get_sha3(data)
}
fn get_net_version(&self) -> ProviderCall<NoParams, U64, u64> {
self.inner.get_net_version()
}
async fn raw_request_dyn(
&self,
method: Cow<'static, str>,
params: &RawValue,
) -> TransportResult<Box<RawValue>> {
self.inner.raw_request_dyn(method, params).await
}
fn transaction_request(&self) -> N::TransactionRequest {
self.inner.transaction_request()
}
}
pub trait RecommendedFillers: Network {
type RecommendedFillers: TxFiller<Self>;
fn recommended_fillers() -> Self::RecommendedFillers;
}
impl RecommendedFillers for Ethereum {
type RecommendedFillers =
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>;
fn recommended_fillers() -> Self::RecommendedFillers {
Default::default()
}
}
impl RecommendedFillers for AnyNetwork {
type RecommendedFillers =
JoinFill<GasFiller, JoinFill<BlobGasFiller, JoinFill<NonceFiller, ChainIdFiller>>>;
fn recommended_fillers() -> Self::RecommendedFillers {
Default::default()
}
}