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 Call3 {
27 address target;
28 bool allowFailure;
29 bytes callData;
30 }
31
32 struct Result {
33 bool success;
34 bytes returnData;
35 }
36
37 function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);
38 }
39}
40
41pub const MULTICALL3_ADDRESS: &str = "0xcA11bde05977b3631167028862bE2a173976CA11";
43
44#[derive(Debug)]
49pub struct BaseContract {
50 client: Arc<BlockchainHttpRpcClient>,
52 multicall_address: Address,
54}
55
56#[derive(Debug)]
58pub struct ContractCall {
59 pub target: Address,
61 pub allow_failure: bool,
63 pub call_data: Vec<u8>,
65}
66
67impl BaseContract {
68 #[must_use]
74 pub fn new(client: Arc<BlockchainHttpRpcClient>) -> Self {
75 let multicall_address =
76 validate_address(MULTICALL3_ADDRESS).expect("Invalid multicall address");
77
78 Self {
79 client,
80 multicall_address,
81 }
82 }
83
84 #[must_use]
86 pub const fn client(&self) -> &Arc<BlockchainHttpRpcClient> {
87 &self.client
88 }
89
90 pub async fn execute_call(
96 &self,
97 contract_address: &Address,
98 call_data: &[u8],
99 ) -> Result<Vec<u8>, BlockchainRpcClientError> {
100 let rpc_request = self
101 .client
102 .construct_eth_call(&contract_address.to_string(), call_data);
103
104 let encoded_response = self
105 .client
106 .execute_eth_call::<String>(rpc_request)
107 .await
108 .map_err(|e| BlockchainRpcClientError::ClientError(format!("RPC call failed: {e}")))?;
109
110 decode_hex_response(&encoded_response)
111 }
112
113 pub async fn execute_multicall(
119 &self,
120 calls: Vec<ContractCall>,
121 ) -> Result<Vec<Multicall3::Result>, BlockchainRpcClientError> {
122 let multicall_calls: Vec<Multicall3::Call3> = calls
124 .into_iter()
125 .map(|call| Multicall3::Call3 {
126 target: call.target,
127 allowFailure: call.allow_failure,
128 callData: call.call_data.into(),
129 })
130 .collect();
131
132 let multicall_data = Multicall3::aggregate3Call {
133 calls: multicall_calls,
134 }
135 .abi_encode();
136 let rpc_request = self.client.construct_eth_call(
137 &self.multicall_address.to_string(),
138 multicall_data.as_slice(),
139 );
140
141 let encoded_response = self
142 .client
143 .execute_eth_call::<String>(rpc_request)
144 .await
145 .map_err(|e| BlockchainRpcClientError::ClientError(format!("Multicall failed: {e}")))?;
146
147 let bytes = decode_hex_response(&encoded_response)?;
148 let results = Multicall3::aggregate3Call::abi_decode_returns(&bytes).map_err(|e| {
149 BlockchainRpcClientError::AbiDecodingError(format!(
150 "Failed to decode multicall results: {e}"
151 ))
152 })?;
153
154 Ok(results)
155 }
156}
157
158pub fn decode_hex_response(encoded_response: &str) -> Result<Vec<u8>, BlockchainRpcClientError> {
164 let encoded_str = encoded_response
166 .strip_prefix("0x")
167 .unwrap_or(encoded_response);
168 hex::decode(encoded_str).map_err(|e| {
169 BlockchainRpcClientError::AbiDecodingError(format!("Error decoding hex response: {e}"))
170 })
171}