1use crate::Result;
4use alloy_primitives::{Address, U256};
5use serde::Deserialize;
6use std::time::Duration;
7
8pub struct RpcClient {
10 client: reqwest::Client,
11 url: String,
12}
13
14impl RpcClient {
15 pub fn new(url: &str) -> Self {
17 Self {
18 client: reqwest::Client::builder()
19 .timeout(Duration::from_secs(30))
20 .build()
21 .expect("Failed to build HTTP client"),
22 url: url.to_string(),
23 }
24 }
25
26 async fn request<T: for<'de> Deserialize<'de>>(&self, method: &str, params: serde_json::Value) -> Result<T> {
28 let request = serde_json::json!({
29 "jsonrpc": "2.0",
30 "method": method,
31 "params": params,
32 "id": 1
33 });
34
35 let response = self.client
36 .post(&self.url)
37 .json(&request)
38 .send()
39 .await?
40 .json::<RpcResponse<T>>()
41 .await?;
42
43 if let Some(error) = response.error {
44 Err(crate::Error::Rpc(error.message))
45 } else {
46 response.result.ok_or_else(|| crate::Error::Rpc("null result".to_string()))
47 }
48 }
49
50 pub async fn get_chain_id(&self) -> Result<u64> {
52 let id: String = self.request("eth_chainId", serde_json::Value::Null).await?;
53 Ok(u64::from_str_radix(&id[2..], 16).map_err(|e| crate::Error::InvalidHex(e.to_string()))?)
54 }
55
56 pub async fn get_block_number(&self) -> Result<u64> {
58 let block: String = self.request("eth_blockNumber", serde_json::Value::Null).await?;
59 Ok(u64::from_str_radix(&block[2..], 16).map_err(|e| crate::Error::InvalidHex(e.to_string()))?)
60 }
61
62 pub async fn get_balance(&self, address: Address) -> Result<U256> {
64 let balance: String = self.request("eth_getBalance", serde_json::json!([address.to_string(), "latest"])).await?;
65 Ok(U256::from_str_radix(&balance[2..], 16).map_err(|e| crate::Error::InvalidHex(e.to_string()))?)
66 }
67
68 pub async fn get_erc20_balance(&self, token: Address, owner: Address) -> Result<U256> {
70 let owner_hex = owner.to_string();
72 let owner_bytes = &owner_hex[2..];
73 let data = format!("0x70a082310000000000000000000000000000000000000000000000000000000000000000{}", owner_bytes);
74
75 let result: String = self.request("eth_call", serde_json::json!([
76 { "to": token.to_string(), "data": data },
77 "latest"
78 ])).await?;
79
80 Ok(U256::from_str_radix(&result[2..], 16).map_err(|e| crate::Error::InvalidHex(e.to_string()))?)
81 }
82
83 pub async fn get_transaction_count(&self, address: Address, block: &str) -> Result<u64> {
85 let nonce: String = self.request("eth_getTransactionCount", serde_json::json!([address.to_string(), block])).await?;
86 Ok(u64::from_str_radix(&nonce[2..], 16).map_err(|e| crate::Error::InvalidHex(e.to_string()))?)
87 }
88
89 pub async fn send_raw_transaction(&self, tx: String) -> Result<String> {
91 self.request("eth_sendRawTransaction", serde_json::json!([tx])).await
92 }
93
94 pub async fn get_transaction_receipt(&self, tx_hash: String) -> Result<Option<TransactionReceipt>> {
96 self.request("eth_getTransactionReceipt", serde_json::json!([tx_hash])).await
97 }
98
99 pub async fn get_transaction_by_hash(&self, tx_hash: String) -> Result<Option<Transaction>> {
101 self.request("eth_getTransactionByHash", serde_json::json!([tx_hash])).await
102 }
103
104 pub async fn get_code(&self, address: Address) -> Result<String> {
106 self.request("eth_getCode", serde_json::json!([address.to_string(), "latest"])).await
107 }
108
109 pub async fn wait_for_receipt(&self, tx_hash: String, timeout_ms: u64) -> Result<TransactionReceipt> {
111 let start = std::time::Instant::now();
112 let poll_interval = std::time::Duration::from_millis(500);
113
114 while start.elapsed().as_millis() < timeout_ms as u128 {
115 if let Some(receipt) = self.get_transaction_receipt(tx_hash.clone()).await? {
116 return Ok(receipt);
117 }
118 tokio::time::sleep(poll_interval).await;
119 }
120
121 Err(crate::Error::Rpc("Transaction receipt timeout".to_string()))
122 }
123}
124
125#[derive(Debug, Deserialize)]
127struct RpcResponse<T> {
128 #[serde(rename = "result")]
129 result: Option<T>,
130 #[serde(rename = "error")]
131 error: Option<RpcError>,
132}
133
134#[derive(Debug, Deserialize)]
136struct RpcError {
137 message: String,
138}
139
140#[derive(Debug, Deserialize, Clone)]
142pub struct TransactionReceipt {
143 pub transaction_hash: String,
144 pub block_number: String,
145 pub status: String,
146 pub gas_used: String,
147}
148
149#[derive(Debug, Deserialize, Clone)]
151pub struct Transaction {
152 pub hash: String,
153 pub from: String,
154 pub to: Option<String>,
155 pub value: String,
156 pub input: String,
157 pub gas: String,
158 pub gas_price: Option<String>,
159 pub nonce: String,
160 #[serde(rename = "type")]
161 pub tx_type: String,
162}
163
164pub fn create_rpc_client(url: &str) -> RpcClient {
166 RpcClient::new(url)
167}