use std::sync::Arc;
use alloy::{primitives::Address, sol, sol_types::SolCall};
use nautilus_core::hex;
use nautilus_model::defi::validation::validate_address;
use crate::rpc::{error::BlockchainRpcClientError, http::BlockchainHttpRpcClient};
sol! {
#[sol(rpc)]
contract Multicall3 {
struct Call {
address target;
bytes callData;
}
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
struct Result {
bool success;
bytes returnData;
}
function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);
function tryAggregate(bool requireSuccess, Call[] calldata calls) external payable returns (Result[] memory returnData);
}
}
pub const MULTICALL3_ADDRESS: &str = "0xcA11bde05977b3631167028862bE2a173976CA11";
#[derive(Debug)]
pub struct BaseContract {
client: Arc<BlockchainHttpRpcClient>,
multicall_address: Address,
}
#[derive(Debug)]
pub struct ContractCall {
pub target: Address,
pub allow_failure: bool,
pub call_data: Vec<u8>,
}
impl BaseContract {
#[must_use]
pub fn new(client: Arc<BlockchainHttpRpcClient>) -> Self {
let multicall_address =
validate_address(MULTICALL3_ADDRESS).expect("Invalid multicall address");
Self {
client,
multicall_address,
}
}
#[must_use]
pub const fn client(&self) -> &Arc<BlockchainHttpRpcClient> {
&self.client
}
pub async fn execute_call(
&self,
contract_address: &Address,
call_data: &[u8],
block: Option<u64>,
) -> Result<Vec<u8>, BlockchainRpcClientError> {
let rpc_request =
self.client
.construct_eth_call(&contract_address.to_string(), call_data, block);
let encoded_response = self
.client
.execute_rpc_call::<String>(rpc_request)
.await
.map_err(|e| BlockchainRpcClientError::ClientError(format!("RPC call failed: {e}")))?;
decode_hex_response(&encoded_response)
}
pub async fn execute_multicall(
&self,
calls: Vec<ContractCall>,
block: Option<u64>,
) -> Result<Vec<Multicall3::Result>, BlockchainRpcClientError> {
let multicall_calls: Vec<Multicall3::Call> = calls
.into_iter()
.map(|call| Multicall3::Call {
target: call.target,
callData: call.call_data.into(),
})
.collect();
let multicall_data = Multicall3::tryAggregateCall {
requireSuccess: false,
calls: multicall_calls,
}
.abi_encode();
let rpc_request = self.client.construct_eth_call(
&self.multicall_address.to_string(),
multicall_data.as_slice(),
block,
);
let encoded_response = self
.client
.execute_rpc_call::<String>(rpc_request)
.await
.map_err(|e| BlockchainRpcClientError::ClientError(format!("Multicall failed: {e}")))?;
let bytes = decode_hex_response(&encoded_response)?;
let results = Multicall3::tryAggregateCall::abi_decode_returns(&bytes).map_err(|e| {
BlockchainRpcClientError::AbiDecodingError(format!(
"Failed to decode multicall results: {e}"
))
})?;
Ok(results)
}
}
pub fn decode_hex_response(encoded_response: &str) -> Result<Vec<u8>, BlockchainRpcClientError> {
let encoded_str = encoded_response
.strip_prefix("0x")
.unwrap_or(encoded_response);
hex::decode(encoded_str).map_err(|e| {
BlockchainRpcClientError::AbiDecodingError(format!("Error decoding hex response: {e}"))
})
}