1use serde_json::{json, Value};
6use std::sync::Arc;
7
8use crate::client::HyperliquidSDKInner;
9use crate::error::Result;
10
11pub struct EVM {
13 inner: Arc<HyperliquidSDKInner>,
14 debug: bool,
15}
16
17impl EVM {
18 pub(crate) fn new(inner: Arc<HyperliquidSDKInner>) -> Self {
19 Self {
20 inner,
21 debug: false,
22 }
23 }
24
25 pub fn with_debug(mut self, debug: bool) -> Self {
27 self.debug = debug;
28 self
29 }
30
31 fn evm_url(&self) -> String {
33 self.inner.evm_url(self.debug)
34 }
35
36 async fn rpc(&self, method: &str, params: Value) -> Result<Value> {
38 let url = self.evm_url();
39
40 let body = json!({
41 "jsonrpc": "2.0",
42 "method": method,
43 "params": params,
44 "id": 1,
45 });
46
47 let response = self
48 .inner
49 .http_client
50 .post(&url)
51 .json(&body)
52 .send()
53 .await?;
54
55 let status = response.status();
56 let text = response.text().await?;
57
58 if !status.is_success() {
59 return Err(crate::Error::NetworkError(format!(
60 "EVM request failed {}: {}",
61 status, text
62 )));
63 }
64
65 let result: Value = serde_json::from_str(&text)?;
66
67 if let Some(error) = result.get("error") {
69 let message = error
70 .get("message")
71 .and_then(|m| m.as_str())
72 .unwrap_or("Unknown error");
73 return Err(crate::Error::ApiError {
74 code: crate::error::ErrorCode::Unknown,
75 message: message.to_string(),
76 guidance: "Check your EVM request parameters.".to_string(),
77 raw: Some(error.to_string()),
78 });
79 }
80
81 Ok(result.get("result").cloned().unwrap_or(Value::Null))
82 }
83
84 pub async fn block_number(&self) -> Result<u64> {
90 let result = self.rpc("eth_blockNumber", json!([])).await?;
91 parse_hex_u64(&result)
92 }
93
94 pub async fn chain_id(&self) -> Result<u64> {
96 let result = self.rpc("eth_chainId", json!([])).await?;
97 parse_hex_u64(&result)
98 }
99
100 pub async fn syncing(&self) -> Result<Value> {
102 self.rpc("eth_syncing", json!([])).await
103 }
104
105 pub async fn gas_price(&self) -> Result<u64> {
107 let result = self.rpc("eth_gasPrice", json!([])).await?;
108 parse_hex_u64(&result)
109 }
110
111 pub async fn net_version(&self) -> Result<String> {
113 let result = self.rpc("net_version", json!([])).await?;
114 Ok(result.as_str().unwrap_or("").to_string())
115 }
116
117 pub async fn web3_client_version(&self) -> Result<String> {
119 let result = self.rpc("web3_clientVersion", json!([])).await?;
120 Ok(result.as_str().unwrap_or("").to_string())
121 }
122
123 pub async fn get_balance(&self, address: &str, block: Option<&str>) -> Result<String> {
129 let block = block.unwrap_or("latest");
130 let result = self.rpc("eth_getBalance", json!([address, block])).await?;
131 Ok(result.as_str().unwrap_or("0x0").to_string())
132 }
133
134 pub async fn get_transaction_count(&self, address: &str, block: Option<&str>) -> Result<u64> {
136 let block = block.unwrap_or("latest");
137 let result = self
138 .rpc("eth_getTransactionCount", json!([address, block]))
139 .await?;
140 parse_hex_u64(&result)
141 }
142
143 pub async fn get_code(&self, address: &str, block: Option<&str>) -> Result<String> {
145 let block = block.unwrap_or("latest");
146 let result = self.rpc("eth_getCode", json!([address, block])).await?;
147 Ok(result.as_str().unwrap_or("0x").to_string())
148 }
149
150 pub async fn get_storage_at(
152 &self,
153 address: &str,
154 position: &str,
155 block: Option<&str>,
156 ) -> Result<String> {
157 let block = block.unwrap_or("latest");
158 let result = self
159 .rpc("eth_getStorageAt", json!([address, position, block]))
160 .await?;
161 Ok(result.as_str().unwrap_or("0x0").to_string())
162 }
163
164 pub async fn accounts(&self) -> Result<Vec<String>> {
166 let result = self.rpc("eth_accounts", json!([])).await?;
167 Ok(result
168 .as_array()
169 .map(|arr| {
170 arr.iter()
171 .filter_map(|v| v.as_str().map(|s| s.to_string()))
172 .collect()
173 })
174 .unwrap_or_default())
175 }
176
177 pub async fn call(&self, tx: &Value, block: Option<&str>) -> Result<String> {
183 let block = block.unwrap_or("latest");
184 let result = self.rpc("eth_call", json!([tx, block])).await?;
185 Ok(result.as_str().unwrap_or("0x").to_string())
186 }
187
188 pub async fn estimate_gas(&self, tx: &Value) -> Result<u64> {
190 let result = self.rpc("eth_estimateGas", json!([tx])).await?;
191 parse_hex_u64(&result)
192 }
193
194 pub async fn send_raw_transaction(&self, signed_tx: &str) -> Result<String> {
196 let result = self.rpc("eth_sendRawTransaction", json!([signed_tx])).await?;
197 Ok(result.as_str().unwrap_or("").to_string())
198 }
199
200 pub async fn get_transaction_by_hash(&self, tx_hash: &str) -> Result<Value> {
202 self.rpc("eth_getTransactionByHash", json!([tx_hash])).await
203 }
204
205 pub async fn get_transaction_receipt(&self, tx_hash: &str) -> Result<Value> {
207 self.rpc("eth_getTransactionReceipt", json!([tx_hash])).await
208 }
209
210 pub async fn get_block_by_number(
216 &self,
217 block_number: &str,
218 full_transactions: bool,
219 ) -> Result<Value> {
220 self.rpc(
221 "eth_getBlockByNumber",
222 json!([block_number, full_transactions]),
223 )
224 .await
225 }
226
227 pub async fn get_block_by_hash(&self, block_hash: &str, full_transactions: bool) -> Result<Value> {
229 self.rpc(
230 "eth_getBlockByHash",
231 json!([block_hash, full_transactions]),
232 )
233 .await
234 }
235
236 pub async fn get_logs(&self, filter: &Value) -> Result<Value> {
242 self.rpc("eth_getLogs", json!([filter])).await
243 }
244
245 pub async fn fee_history(
251 &self,
252 block_count: u64,
253 newest_block: &str,
254 reward_percentiles: Option<&[f64]>,
255 ) -> Result<Value> {
256 let percentiles = reward_percentiles.unwrap_or(&[]);
257 self.rpc(
258 "eth_feeHistory",
259 json!([format!("0x{:x}", block_count), newest_block, percentiles]),
260 )
261 .await
262 }
263
264 pub async fn max_priority_fee_per_gas(&self) -> Result<u64> {
266 let result = self.rpc("eth_maxPriorityFeePerGas", json!([])).await?;
267 parse_hex_u64(&result)
268 }
269
270 pub async fn get_block_receipts(&self, block_number: &str) -> Result<Value> {
272 self.rpc("eth_getBlockReceipts", json!([block_number])).await
273 }
274
275 pub async fn get_block_transaction_count_by_hash(&self, block_hash: &str) -> Result<u64> {
277 let result = self
278 .rpc("eth_getBlockTransactionCountByHash", json!([block_hash]))
279 .await?;
280 parse_hex_u64(&result)
281 }
282
283 pub async fn get_block_transaction_count_by_number(&self, block_number: &str) -> Result<u64> {
285 let result = self
286 .rpc("eth_getBlockTransactionCountByNumber", json!([block_number]))
287 .await?;
288 parse_hex_u64(&result)
289 }
290
291 pub async fn get_transaction_by_block_hash_and_index(
293 &self,
294 block_hash: &str,
295 index: u64,
296 ) -> Result<Value> {
297 self.rpc(
298 "eth_getTransactionByBlockHashAndIndex",
299 json!([block_hash, format!("0x{:x}", index)]),
300 )
301 .await
302 }
303
304 pub async fn get_transaction_by_block_number_and_index(
306 &self,
307 block_number: &str,
308 index: u64,
309 ) -> Result<Value> {
310 self.rpc(
311 "eth_getTransactionByBlockNumberAndIndex",
312 json!([block_number, format!("0x{:x}", index)]),
313 )
314 .await
315 }
316
317 pub async fn debug_trace_transaction(
323 &self,
324 tx_hash: &str,
325 tracer_config: Option<&Value>,
326 ) -> Result<Value> {
327 if !self.debug {
328 return Err(crate::Error::ConfigError(
329 "Debug mode not enabled. Use .with_debug(true)".to_string(),
330 ));
331 }
332 let config = tracer_config.cloned().unwrap_or(json!({}));
333 self.rpc("debug_traceTransaction", json!([tx_hash, config]))
334 .await
335 }
336
337 pub async fn trace_transaction(&self, tx_hash: &str) -> Result<Value> {
339 if !self.debug {
340 return Err(crate::Error::ConfigError(
341 "Debug mode not enabled. Use .with_debug(true)".to_string(),
342 ));
343 }
344 self.rpc("trace_transaction", json!([tx_hash])).await
345 }
346
347 pub async fn trace_block(&self, block_number: &str) -> Result<Value> {
349 if !self.debug {
350 return Err(crate::Error::ConfigError(
351 "Debug mode not enabled. Use .with_debug(true)".to_string(),
352 ));
353 }
354 self.rpc("trace_block", json!([block_number])).await
355 }
356}
357
358fn parse_hex_u64(value: &Value) -> Result<u64> {
360 let s = value.as_str().unwrap_or("0x0");
361 let s = s.trim_start_matches("0x");
362 u64::from_str_radix(s, 16)
363 .map_err(|e| crate::Error::JsonError(format!("Invalid hex number: {}", e)))
364}