openzeppelin_monitor/services/blockchain/clients/evm/
client.rs1use std::marker::PhantomData;
8
9use anyhow::Context;
10use async_trait::async_trait;
11use futures;
12use serde_json::json;
13use tracing::instrument;
14
15use crate::{
16 models::{BlockType, EVMBlock, EVMReceiptLog, EVMTransactionReceipt, Network},
17 services::{
18 blockchain::{
19 client::BlockChainClient,
20 transports::{BlockchainTransport, EVMTransportClient},
21 BlockFilterFactory,
22 },
23 filter::{evm_helpers::string_to_h256, EVMBlockFilter},
24 },
25};
26
27#[derive(Clone)]
31pub struct EvmClient<T: Send + Sync + Clone> {
32 http_client: T,
34}
35
36impl<T: Send + Sync + Clone> EvmClient<T> {
37 pub fn new_with_transport(http_client: T) -> Self {
39 Self { http_client }
40 }
41}
42
43impl EvmClient<EVMTransportClient> {
44 pub async fn new(network: &Network) -> Result<Self, anyhow::Error> {
52 let client = EVMTransportClient::new(network).await?;
53 Ok(Self::new_with_transport(client))
54 }
55}
56
57impl<T: Send + Sync + Clone + BlockchainTransport> BlockFilterFactory<Self> for EvmClient<T> {
58 type Filter = EVMBlockFilter<Self>;
59 fn filter() -> Self::Filter {
60 EVMBlockFilter {
61 _client: PhantomData,
62 }
63 }
64}
65
66#[async_trait]
68pub trait EvmClientTrait {
69 async fn get_transaction_receipt(
77 &self,
78 transaction_hash: String,
79 ) -> Result<EVMTransactionReceipt, anyhow::Error>;
80
81 async fn get_logs_for_blocks(
90 &self,
91 from_block: u64,
92 to_block: u64,
93 addresses: Option<Vec<String>>,
94 ) -> Result<Vec<EVMReceiptLog>, anyhow::Error>;
95}
96
97#[async_trait]
98impl<T: Send + Sync + Clone + BlockchainTransport> EvmClientTrait for EvmClient<T> {
99 #[instrument(skip(self), fields(transaction_hash))]
101 async fn get_transaction_receipt(
102 &self,
103 transaction_hash: String,
104 ) -> Result<EVMTransactionReceipt, anyhow::Error> {
105 let hash = string_to_h256(&transaction_hash)
106 .map_err(|e| anyhow::anyhow!("Invalid transaction hash: {}", e))?;
107
108 let params = json!([format!("0x{:x}", hash)])
109 .as_array()
110 .with_context(|| "Failed to create JSON-RPC params array")?
111 .to_vec();
112
113 let response = self
114 .http_client
115 .send_raw_request(
116 "eth_getTransactionReceipt",
117 Some(serde_json::Value::Array(params)),
118 )
119 .await
120 .with_context(|| format!("Failed to get transaction receipt: {}", transaction_hash))?;
121
122 let receipt_data = response
124 .get("result")
125 .with_context(|| "Missing 'result' field")?;
126
127 if receipt_data.is_null() {
129 return Err(anyhow::anyhow!("Transaction receipt not found"));
130 }
131
132 Ok(serde_json::from_value(receipt_data.clone())
133 .with_context(|| "Failed to parse transaction receipt")?)
134 }
135
136 #[instrument(skip(self), fields(from_block, to_block))]
145 async fn get_logs_for_blocks(
146 &self,
147 from_block: u64,
148 to_block: u64,
149 addresses: Option<Vec<String>>,
150 ) -> Result<Vec<EVMReceiptLog>, anyhow::Error> {
151 let params = json!([{
153 "fromBlock": format!("0x{:x}", from_block),
154 "toBlock": format!("0x{:x}", to_block),
155 "address": addresses
156 }])
157 .as_array()
158 .with_context(|| "Failed to create JSON-RPC params array")?
159 .to_vec();
160
161 let response = self
162 .http_client
163 .send_raw_request("eth_getLogs", Some(params))
164 .await
165 .with_context(|| {
166 format!(
167 "Failed to get logs for blocks: {} - {}",
168 from_block, to_block
169 )
170 })?;
171
172 let logs_data = response
174 .get("result")
175 .with_context(|| "Missing 'result' field")?;
176
177 Ok(serde_json::from_value(logs_data.clone()).with_context(|| "Failed to parse logs")?)
179 }
180}
181
182#[async_trait]
183impl<T: Send + Sync + Clone + BlockchainTransport> BlockChainClient for EvmClient<T> {
184 #[instrument(skip(self))]
186 async fn get_latest_block_number(&self) -> Result<u64, anyhow::Error> {
187 let response = self
188 .http_client
189 .send_raw_request::<serde_json::Value>("eth_blockNumber", None)
190 .await
191 .with_context(|| "Failed to get latest block number")?;
192
193 let hex_str = response
195 .get("result")
196 .and_then(|v| v.as_str())
197 .ok_or_else(|| anyhow::anyhow!("Missing 'result' field"))?;
198
199 u64::from_str_radix(hex_str.trim_start_matches("0x"), 16)
201 .map_err(|e| anyhow::anyhow!("Failed to parse block number: {}", e))
202 }
203
204 #[instrument(skip(self), fields(start_block, end_block))]
209 async fn get_blocks(
210 &self,
211 start_block: u64,
212 end_block: Option<u64>,
213 ) -> Result<Vec<BlockType>, anyhow::Error> {
214 let block_futures: Vec<_> = (start_block..=end_block.unwrap_or(start_block))
215 .map(|block_number| {
216 let params = json!([
217 format!("0x{:x}", block_number),
218 true ]);
220 let client = self.http_client.clone();
221
222 async move {
223 let response = client
224 .send_raw_request("eth_getBlockByNumber", Some(params))
225 .await
226 .with_context(|| format!("Failed to get block: {}", block_number))?;
227
228 let block_data = response
229 .get("result")
230 .ok_or_else(|| anyhow::anyhow!("Missing 'result' field"))?;
231
232 if block_data.is_null() {
233 return Err(anyhow::anyhow!("Block not found"));
234 }
235
236 let block: EVMBlock = serde_json::from_value(block_data.clone())
237 .map_err(|e| anyhow::anyhow!("Failed to parse block: {}", e))?;
238
239 Ok(BlockType::EVM(Box::new(block)))
240 }
241 })
242 .collect();
243
244 futures::future::join_all(block_futures)
245 .await
246 .into_iter()
247 .collect::<Result<Vec<_>, _>>()
248 }
249}