nautilus_blockchain/contracts/
base.rs1use std::sync::Arc;
17
18use alloy::{primitives::Address, sol, sol_types::SolCall};
19use nautilus_model::defi::validation::validate_address;
20
21use crate::rpc::{error::BlockchainRpcClientError, http::BlockchainHttpRpcClient};
22
23sol! {
24 #[sol(rpc)]
25 contract Multicall3 {
26 struct Call {
27 address target;
28 bytes callData;
29 }
30
31 struct Call3 {
32 address target;
33 bool allowFailure;
34 bytes callData;
35 }
36
37 struct Result {
38 bool success;
39 bytes returnData;
40 }
41
42 function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);
43 function tryAggregate(bool requireSuccess, Call[] calldata calls) external payable returns (Result[] memory returnData);
44 }
45}
46
47pub const MULTICALL3_ADDRESS: &str = "0xcA11bde05977b3631167028862bE2a173976CA11";
49
50#[derive(Debug)]
55pub struct BaseContract {
56 client: Arc<BlockchainHttpRpcClient>,
58 multicall_address: Address,
60}
61
62#[derive(Debug)]
64pub struct ContractCall {
65 pub target: Address,
67 pub allow_failure: bool,
69 pub call_data: Vec<u8>,
71}
72
73impl BaseContract {
74 #[must_use]
80 pub fn new(client: Arc<BlockchainHttpRpcClient>) -> Self {
81 let multicall_address =
82 validate_address(MULTICALL3_ADDRESS).expect("Invalid multicall address");
83
84 Self {
85 client,
86 multicall_address,
87 }
88 }
89
90 #[must_use]
92 pub const fn client(&self) -> &Arc<BlockchainHttpRpcClient> {
93 &self.client
94 }
95
96 pub async fn execute_call(
102 &self,
103 contract_address: &Address,
104 call_data: &[u8],
105 block: Option<u64>,
106 ) -> Result<Vec<u8>, BlockchainRpcClientError> {
107 let rpc_request =
108 self.client
109 .construct_eth_call(&contract_address.to_string(), call_data, block);
110
111 let encoded_response = self
112 .client
113 .execute_eth_call::<String>(rpc_request)
114 .await
115 .map_err(|e| BlockchainRpcClientError::ClientError(format!("RPC call failed: {e}")))?;
116
117 decode_hex_response(&encoded_response)
118 }
119
120 pub async fn execute_multicall(
126 &self,
127 calls: Vec<ContractCall>,
128 block: Option<u64>,
129 ) -> Result<Vec<Multicall3::Result>, BlockchainRpcClientError> {
130 let multicall_calls: Vec<Multicall3::Call> = calls
132 .into_iter()
133 .map(|call| Multicall3::Call {
134 target: call.target,
135 callData: call.call_data.into(),
136 })
137 .collect();
138
139 let multicall_data = Multicall3::tryAggregateCall {
140 requireSuccess: false,
141 calls: multicall_calls,
142 }
143 .abi_encode();
144 let rpc_request = self.client.construct_eth_call(
145 &self.multicall_address.to_string(),
146 multicall_data.as_slice(),
147 block,
148 );
149
150 let encoded_response = self
151 .client
152 .execute_eth_call::<String>(rpc_request)
153 .await
154 .map_err(|e| BlockchainRpcClientError::ClientError(format!("Multicall failed: {e}")))?;
155
156 let bytes = decode_hex_response(&encoded_response)?;
157 let results = Multicall3::tryAggregateCall::abi_decode_returns(&bytes).map_err(|e| {
158 BlockchainRpcClientError::AbiDecodingError(format!(
159 "Failed to decode multicall results: {e}"
160 ))
161 })?;
162
163 Ok(results)
164 }
165}
166
167pub fn decode_hex_response(encoded_response: &str) -> Result<Vec<u8>, BlockchainRpcClientError> {
173 let encoded_str = encoded_response
175 .strip_prefix("0x")
176 .unwrap_or(encoded_response);
177 hex::decode(encoded_str).map_err(|e| {
178 BlockchainRpcClientError::AbiDecodingError(format!("Error decoding hex response: {e}"))
179 })
180}