use crate::{PendingTransactionBuilder, Provider};
use alloy_network::{Network, TransactionBuilder};
use alloy_primitives::{address, Address, BlockNumber, Bytes, B256, U256};
use alloy_rpc_types_eth::{state::StateOverride, BlockId, TransactionInputKind};
use alloy_sol_types::SolCall;
use bindings::IMulticall3::{
blockAndAggregateCall, blockAndAggregateReturn, tryBlockAndAggregateCall,
tryBlockAndAggregateReturn, Call, Call3, Call3Value,
};
pub mod bindings;
use crate::provider::multicall::bindings::IMulticall3::{
aggregate3Call, aggregate3ValueCall, aggregateCall, getBasefeeCall, getBlockHashCall,
getBlockNumberCall, getChainIdCall, getCurrentBlockCoinbaseCall, getCurrentBlockDifficultyCall,
getCurrentBlockGasLimitCall, getCurrentBlockTimestampCall, getEthBalanceCall,
getLastBlockHashCall, tryAggregateCall,
};
mod inner_types;
pub use inner_types::{
CallInfoTrait, CallItem, CallItemBuilder, Dynamic, Failure, MulticallError, MulticallItem,
Result,
};
mod tuple;
use tuple::TuplePush;
pub use tuple::{CallTuple, Empty};
pub const MULTICALL3_ADDRESS: Address = address!("0xcA11bde05977b3631167028862bE2a173976CA11");
pub const ARB_SYS_ADDRESS: Address = address!("0x0000000000000000000000000000000000000064");
#[derive(Debug)]
pub struct MulticallBuilder<T: CallTuple, P: Provider<N>, N: Network> {
calls: Vec<Call3Value>,
provider: P,
block: Option<BlockId>,
state_override: Option<StateOverride>,
address: Address,
input_kind: TransactionInputKind,
_pd: std::marker::PhantomData<(T, N)>,
}
impl<P, N> MulticallBuilder<Empty, P, N>
where
P: Provider<N>,
N: Network,
{
pub fn new(provider: P) -> Self {
Self {
calls: Vec::new(),
provider,
_pd: Default::default(),
block: None,
state_override: None,
address: MULTICALL3_ADDRESS,
input_kind: TransactionInputKind::default(),
}
}
pub fn dynamic<D: SolCall + 'static>(self) -> MulticallBuilder<Dynamic<D>, P, N> {
MulticallBuilder {
calls: self.calls,
provider: self.provider,
block: self.block,
state_override: self.state_override,
address: self.address,
input_kind: self.input_kind,
_pd: Default::default(),
}
}
}
impl<D: SolCall + 'static, P, N> MulticallBuilder<Dynamic<D>, P, N>
where
P: Provider<N>,
N: Network,
{
pub fn new_dynamic(provider: P) -> Self {
MulticallBuilder::new(provider).dynamic()
}
pub fn add_dynamic(mut self, item: impl MulticallItem<Decoder = D>) -> Self {
let call: CallItem<D> = item.into();
self.calls.push(call.to_call3_value());
self
}
pub fn add_call_dynamic(mut self, call: CallItem<D>) -> Self {
self.calls.push(call.to_call3_value());
self
}
pub fn extend(
mut self,
items: impl IntoIterator<Item = impl MulticallItem<Decoder = D>>,
) -> Self {
for item in items {
self = self.add_dynamic(item);
}
self
}
pub fn extend_calls(mut self, calls: impl IntoIterator<Item = CallItem<D>>) -> Self {
for call in calls {
self = self.add_call_dynamic(call);
}
self
}
}
impl<T, P, N> MulticallBuilder<T, &P, N>
where
T: CallTuple,
P: Provider<N> + Clone,
N: Network,
{
pub fn with_cloned_provider(&self) -> MulticallBuilder<Empty, P, N> {
MulticallBuilder {
calls: Vec::new(),
provider: self.provider.clone(),
block: None,
state_override: None,
address: MULTICALL3_ADDRESS,
input_kind: TransactionInputKind::default(),
_pd: Default::default(),
}
}
}
impl<T, P, N> MulticallBuilder<T, P, N>
where
T: CallTuple,
P: Provider<N>,
N: Network,
{
pub const fn address(mut self, address: Address) -> Self {
self.address = address;
self
}
pub const fn block(mut self, block: BlockId) -> Self {
self.block = Some(block);
self
}
pub fn overrides(mut self, state_override: impl Into<StateOverride>) -> Self {
self.state_override = Some(state_override.into());
self
}
#[expect(clippy::should_implement_trait)]
pub fn add<Item: MulticallItem>(self, item: Item) -> MulticallBuilder<T::Pushed, P, N>
where
Item::Decoder: 'static,
T: TuplePush<Item::Decoder>,
<T as TuplePush<Item::Decoder>>::Pushed: CallTuple,
{
let call: CallItem<Item::Decoder> = item.into();
self.add_call(call)
}
pub fn add_call<D>(mut self, call: CallItem<D>) -> MulticallBuilder<T::Pushed, P, N>
where
D: SolCall + 'static,
T: TuplePush<D>,
<T as TuplePush<D>>::Pushed: CallTuple,
{
self.calls.push(call.to_call3_value());
MulticallBuilder {
calls: self.calls,
provider: self.provider,
block: self.block,
state_override: self.state_override,
address: self.address,
input_kind: self.input_kind,
_pd: Default::default(),
}
}
fn to_aggregate3_value_call(&self) -> aggregate3ValueCall {
aggregate3ValueCall { calls: self.calls.to_vec() }
}
fn to_block_and_aggregate_call(&self) -> blockAndAggregateCall {
let calls = self
.calls
.iter()
.map(|c| Call { target: c.target, callData: c.callData.clone() })
.collect::<Vec<_>>();
blockAndAggregateCall { calls }
}
fn to_try_block_and_aggregate_call(&self, require_success: bool) -> tryBlockAndAggregateCall {
let calls = self
.calls
.iter()
.map(|c| Call { target: c.target, callData: c.callData.clone() })
.collect::<Vec<_>>();
tryBlockAndAggregateCall { requireSuccess: require_success, calls }
}
pub async fn aggregate(&self) -> Result<T::SuccessReturns> {
let output = self.build_and_call(self.to_aggregate_call(), None).await?;
T::decode_returns(&output.returnData)
}
pub async fn send_aggregate(&self) -> Result<PendingTransactionBuilder<N>> {
self.build_and_send(self.to_aggregate_call(), None).await
}
pub fn to_aggregate_request(&self) -> N::TransactionRequest {
self.build_request(self.to_aggregate_call(), None)
}
fn to_aggregate_call(&self) -> aggregateCall {
let calls = self
.calls
.iter()
.map(|c| Call { target: c.target, callData: c.callData.clone() })
.collect::<Vec<_>>();
aggregateCall { calls }
}
pub async fn try_aggregate(&self, require_success: bool) -> Result<T::Returns> {
let output = self.build_and_call(self.to_try_aggregate_call(require_success), None).await?;
T::decode_return_results(&output)
}
pub async fn send_try_aggregate(
&self,
require_success: bool,
) -> Result<PendingTransactionBuilder<N>> {
self.build_and_send(self.to_try_aggregate_call(require_success), None).await
}
pub fn to_try_aggregate_request(&self, require_success: bool) -> N::TransactionRequest {
self.build_request(self.to_try_aggregate_call(require_success), None)
}
fn to_try_aggregate_call(&self, require_success: bool) -> tryAggregateCall {
let calls = self
.calls
.iter()
.map(|c| Call { target: c.target, callData: c.callData.clone() })
.collect::<Vec<_>>();
tryAggregateCall { requireSuccess: require_success, calls }
}
pub async fn aggregate3(&self) -> Result<T::Returns> {
let call = self.to_aggregate3_call();
let output = self.build_and_call(call, None).await?;
T::decode_return_results(&output)
}
pub async fn send_aggregate3(&self) -> Result<PendingTransactionBuilder<N>> {
self.build_and_send(self.to_aggregate3_call(), None).await
}
pub fn to_aggregate3_request(&self) -> N::TransactionRequest {
self.build_request(self.to_aggregate3_call(), None)
}
pub async fn send_aggregate3_value(&self) -> Result<PendingTransactionBuilder<N>> {
let total_value = self.calls.iter().map(|c| c.value).fold(U256::ZERO, |acc, x| acc + x);
let call = self.to_aggregate3_value_call();
self.build_and_send(call, Some(total_value)).await
}
fn to_aggregate3_call(&self) -> aggregate3Call {
let calls = self
.calls
.iter()
.map(|c| Call3 {
target: c.target,
callData: c.callData.clone(),
allowFailure: c.allowFailure,
})
.collect::<Vec<_>>();
aggregate3Call { calls }
}
pub async fn aggregate3_value(&self) -> Result<T::Returns> {
let total_value = self.calls.iter().map(|c| c.value).fold(U256::ZERO, |acc, x| acc + x);
let call = aggregate3ValueCall { calls: self.calls.to_vec() };
let output = self.build_and_call(call, Some(total_value)).await?;
T::decode_return_results(&output)
}
pub async fn block_and_aggregate(&self) -> Result<(u64, B256, T::SuccessReturns)> {
let call = self.to_block_and_aggregate_call();
let output = self.build_and_call(call, None).await?;
let blockAndAggregateReturn { blockNumber, blockHash, returnData } = output;
let result = T::decode_return_results(&returnData)?;
Ok((blockNumber.to::<u64>(), blockHash, T::try_into_success(result)?))
}
pub async fn send_block_and_aggregate(&self) -> Result<PendingTransactionBuilder<N>> {
let call = self.to_block_and_aggregate_call();
self.build_and_send(call, None).await
}
pub async fn try_block_and_aggregate(
&self,
require_success: bool,
) -> Result<(u64, B256, T::Returns)> {
let call = self.to_try_block_and_aggregate_call(require_success);
let output = self.build_and_call(call, None).await?;
let tryBlockAndAggregateReturn { blockNumber, blockHash, returnData } = output;
Ok((blockNumber.to::<u64>(), blockHash, T::decode_return_results(&returnData)?))
}
pub async fn send_try_block_and_aggregate(
&self,
require_success: bool,
) -> Result<PendingTransactionBuilder<N>> {
let call = self.to_try_block_and_aggregate_call(require_success);
self.build_and_send(call, None).await
}
fn build_request<M: SolCall>(
&self,
call_type: M,
value: Option<U256>,
) -> N::TransactionRequest {
let call = call_type.abi_encode();
let mut tx = N::TransactionRequest::default()
.with_to(self.address)
.with_input_kind(Bytes::from_iter(call), self.input_kind);
if let Some(value) = value {
tx.set_value(value);
}
tx
}
async fn build_and_call<M: SolCall>(
&self,
call_type: M,
value: Option<U256>,
) -> Result<M::Return> {
let tx = self.build_request(call_type, value);
let mut eth_call = self.provider.root().call(tx);
if let Some(block) = self.block {
eth_call = eth_call.block(block);
}
if let Some(overrides) = self.state_override.clone() {
eth_call = eth_call.overrides(overrides);
}
let res = eth_call.await.map_err(MulticallError::TransportError)?;
M::abi_decode_returns(&res).map_err(MulticallError::DecodeError)
}
async fn build_and_send<M: SolCall>(
&self,
call_type: M,
value: Option<U256>,
) -> Result<PendingTransactionBuilder<N>> {
let tx = self.build_request(call_type, value);
let pending_tx =
self.provider.send_transaction(tx).await.map_err(MulticallError::TransportError)?;
Ok(pending_tx)
}
pub fn get_block_hash(self, number: BlockNumber) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getBlockHashCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getBlockHashCall>::new(
self.address,
getBlockHashCall { blockNumber: U256::from(number) }.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_current_block_coinbase(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getCurrentBlockCoinbaseCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getCurrentBlockCoinbaseCall>::new(
self.address,
getCurrentBlockCoinbaseCall {}.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_block_number(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getBlockNumberCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getBlockNumberCall>::new(
self.address,
getBlockNumberCall {}.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_current_block_difficulty(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getCurrentBlockDifficultyCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getCurrentBlockDifficultyCall>::new(
self.address,
getCurrentBlockDifficultyCall {}.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_current_block_gas_limit(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getCurrentBlockGasLimitCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getCurrentBlockGasLimitCall>::new(
self.address,
getCurrentBlockGasLimitCall {}.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_current_block_timestamp(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getCurrentBlockTimestampCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getCurrentBlockTimestampCall>::new(
self.address,
getCurrentBlockTimestampCall {}.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_chain_id(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getChainIdCall>,
T::Pushed: CallTuple,
{
let call =
CallItem::<getChainIdCall>::new(self.address, getChainIdCall {}.abi_encode().into());
self.add_call(call)
}
pub fn get_base_fee(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getBasefeeCall>,
T::Pushed: CallTuple,
{
let call =
CallItem::<getBasefeeCall>::new(self.address, getBasefeeCall {}.abi_encode().into());
self.add_call(call)
}
pub fn get_eth_balance(self, address: Address) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getEthBalanceCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getEthBalanceCall>::new(
self.address,
getEthBalanceCall { addr: address }.abi_encode().into(),
);
self.add_call(call)
}
pub fn get_last_block_hash(self) -> MulticallBuilder<T::Pushed, P, N>
where
T: TuplePush<getLastBlockHashCall>,
T::Pushed: CallTuple,
{
let call = CallItem::<getLastBlockHashCall>::new(
self.address,
getLastBlockHashCall {}.abi_encode().into(),
);
self.add_call(call)
}
pub fn clear(self) -> MulticallBuilder<Empty, P, N> {
MulticallBuilder {
calls: Vec::new(),
provider: self.provider,
block: self.block,
state_override: self.state_override,
address: self.address,
input_kind: self.input_kind,
_pd: Default::default(),
}
}
pub const fn len(&self) -> usize {
self.calls.len()
}
pub const fn is_empty(&self) -> bool {
self.calls.is_empty()
}
pub const fn with_input_kind(mut self, input_kind: TransactionInputKind) -> Self {
self.input_kind = input_kind;
self
}
pub const fn input_kind(&self) -> TransactionInputKind {
self.input_kind
}
}