1use crate::{
2 block_number::BlockNumber,
3 serde_helpers::{
4 deserialize_stringified_block_number, deserialize_stringified_numeric,
5 deserialize_stringified_numeric_opt, deserialize_stringified_u64,
6 deserialize_stringified_u64_opt,
7 },
8 Client, EtherscanError, Query, Response, Result,
9};
10use alloy_primitives::{Address, Bytes, B256, U256};
11use serde::{Deserialize, Serialize};
12use std::{
13 borrow::Cow,
14 collections::HashMap,
15 fmt::{Display, Error, Formatter},
16};
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
20pub struct AccountBalance {
21 pub account: Address,
22 pub balance: String,
23}
24
25mod genesis_string {
26 use super::*;
27 use serde::{
28 de::{DeserializeOwned, Error as _},
29 ser::Error as _,
30 Deserializer, Serializer,
31 };
32
33 pub(crate) fn serialize<T, S>(
34 value: &GenesisOption<T>,
35 serializer: S,
36 ) -> std::result::Result<S::Ok, S::Error>
37 where
38 T: Serialize,
39 S: Serializer,
40 {
41 let json = match value {
42 GenesisOption::None => Cow::from(""),
43 GenesisOption::Genesis => Cow::from("GENESIS"),
44 GenesisOption::Some(value) => {
45 serde_json::to_string(value).map_err(S::Error::custom)?.into()
46 }
47 };
48 serializer.serialize_str(&json)
49 }
50
51 pub(crate) fn deserialize<'de, T, D>(
52 deserializer: D,
53 ) -> std::result::Result<GenesisOption<T>, D::Error>
54 where
55 T: DeserializeOwned,
56 D: Deserializer<'de>,
57 {
58 let json = Cow::<'de, str>::deserialize(deserializer)?;
59 if !json.is_empty() && !json.starts_with("GENESIS") {
60 serde_json::from_str(&format!("\"{}\"", &json))
61 .map(GenesisOption::Some)
62 .map_err(D::Error::custom)
63 } else if json.starts_with("GENESIS") {
64 Ok(GenesisOption::Genesis)
65 } else {
66 Ok(GenesisOption::None)
67 }
68 }
69}
70
71mod json_string {
72 use super::*;
73 use serde::{
74 de::{DeserializeOwned, Error as _},
75 ser::Error as _,
76 Deserializer, Serializer,
77 };
78
79 pub(crate) fn serialize<T, S>(
80 value: &Option<T>,
81 serializer: S,
82 ) -> std::result::Result<S::Ok, S::Error>
83 where
84 T: Serialize,
85 S: Serializer,
86 {
87 let json = match value {
88 Option::None => Cow::from(""),
89 Option::Some(value) => serde_json::to_string(value).map_err(S::Error::custom)?.into(),
90 };
91 serializer.serialize_str(&json)
92 }
93
94 pub(crate) fn deserialize<'de, T, D>(
95 deserializer: D,
96 ) -> std::result::Result<Option<T>, D::Error>
97 where
98 T: DeserializeOwned,
99 D: Deserializer<'de>,
100 {
101 let json = Cow::<'de, str>::deserialize(deserializer)?;
102 if json.is_empty() {
103 Ok(Option::None)
104 } else {
105 serde_json::from_str(&format!("\"{}\"", &json))
106 .map(Option::Some)
107 .map_err(D::Error::custom)
108 }
109 }
110}
111
112#[derive(Clone, Debug)]
117pub enum GenesisOption<T> {
118 None,
119 Genesis,
120 Some(T),
121}
122
123impl<T> From<GenesisOption<T>> for Option<T> {
124 fn from(value: GenesisOption<T>) -> Self {
125 match value {
126 GenesisOption::Some(value) => Some(value),
127 _ => None,
128 }
129 }
130}
131
132impl<T> GenesisOption<T> {
133 pub fn is_genesis(&self) -> bool {
134 matches!(self, GenesisOption::Genesis)
135 }
136
137 pub fn value(&self) -> Option<&T> {
138 match self {
139 GenesisOption::Some(value) => Some(value),
140 _ => None,
141 }
142 }
143}
144
145#[derive(Clone, Debug, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct NormalTransaction {
149 pub is_error: String,
150 #[serde(deserialize_with = "deserialize_stringified_block_number")]
151 pub block_number: BlockNumber,
152 pub time_stamp: String,
153 #[serde(with = "genesis_string")]
154 pub hash: GenesisOption<B256>,
155 #[serde(with = "json_string")]
156 pub nonce: Option<U256>,
157 #[serde(with = "json_string")]
158 pub block_hash: Option<U256>,
159 #[serde(deserialize_with = "deserialize_stringified_u64_opt")]
160 pub transaction_index: Option<u64>,
161 #[serde(with = "genesis_string")]
162 pub from: GenesisOption<Address>,
163 #[serde(with = "json_string")]
164 pub to: Option<Address>,
165 #[serde(deserialize_with = "deserialize_stringified_numeric")]
166 pub value: U256,
167 #[serde(deserialize_with = "deserialize_stringified_numeric")]
168 pub gas: U256,
169 #[serde(deserialize_with = "deserialize_stringified_numeric_opt")]
170 pub gas_price: Option<U256>,
171 #[serde(rename = "txreceipt_status")]
172 pub tx_receipt_status: String,
173 pub input: Bytes,
174 #[serde(with = "json_string")]
175 pub contract_address: Option<Address>,
176 #[serde(deserialize_with = "deserialize_stringified_numeric")]
177 pub gas_used: U256,
178 #[serde(deserialize_with = "deserialize_stringified_numeric")]
179 pub cumulative_gas_used: U256,
180 #[serde(deserialize_with = "deserialize_stringified_u64")]
181 pub confirmations: u64,
182 pub method_id: Option<Bytes>,
183 #[serde(with = "json_string")]
184 pub function_name: Option<String>,
185}
186
187#[derive(Clone, Debug, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct InternalTransaction {
191 #[serde(deserialize_with = "deserialize_stringified_block_number")]
192 pub block_number: BlockNumber,
193 pub time_stamp: String,
194 pub hash: B256,
195 pub from: Address,
196 #[serde(with = "genesis_string")]
197 pub to: GenesisOption<Address>,
198 #[serde(deserialize_with = "deserialize_stringified_numeric")]
199 pub value: U256,
200 #[serde(with = "genesis_string")]
201 pub contract_address: GenesisOption<Address>,
202 #[serde(with = "genesis_string")]
203 pub input: GenesisOption<Bytes>,
204 #[serde(rename = "type")]
205 pub result_type: String,
206 #[serde(deserialize_with = "deserialize_stringified_numeric")]
207 pub gas: U256,
208 #[serde(deserialize_with = "deserialize_stringified_numeric")]
209 pub gas_used: U256,
210 pub trace_id: String,
211 pub is_error: String,
212 pub err_code: String,
213}
214
215#[derive(Clone, Debug, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct ERC20TokenTransferEvent {
219 #[serde(deserialize_with = "deserialize_stringified_block_number")]
220 pub block_number: BlockNumber,
221 pub time_stamp: String,
222 pub hash: B256,
223 #[serde(deserialize_with = "deserialize_stringified_numeric")]
224 pub nonce: U256,
225 pub block_hash: B256,
226 pub from: Address,
227 pub contract_address: Address,
228 pub to: Option<Address>,
229 #[serde(deserialize_with = "deserialize_stringified_numeric")]
230 pub value: U256,
231 pub token_name: String,
232 pub token_symbol: String,
233 pub token_decimal: String,
234 #[serde(deserialize_with = "deserialize_stringified_u64")]
235 pub transaction_index: u64,
236 #[serde(deserialize_with = "deserialize_stringified_numeric")]
237 pub gas: U256,
238 #[serde(deserialize_with = "deserialize_stringified_numeric_opt")]
239 pub gas_price: Option<U256>,
240 #[serde(deserialize_with = "deserialize_stringified_numeric")]
241 pub gas_used: U256,
242 #[serde(deserialize_with = "deserialize_stringified_numeric")]
243 pub cumulative_gas_used: U256,
244 pub input: String,
246 #[serde(deserialize_with = "deserialize_stringified_u64")]
247 pub confirmations: u64,
248}
249
250#[derive(Clone, Debug, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct ERC721TokenTransferEvent {
254 #[serde(deserialize_with = "deserialize_stringified_block_number")]
255 pub block_number: BlockNumber,
256 pub time_stamp: String,
257 pub hash: B256,
258 #[serde(deserialize_with = "deserialize_stringified_numeric")]
259 pub nonce: U256,
260 pub block_hash: B256,
261 pub from: Address,
262 pub contract_address: Address,
263 pub to: Option<Address>,
264 #[serde(rename = "tokenID")]
265 pub token_id: String,
266 pub token_name: String,
267 pub token_symbol: String,
268 pub token_decimal: String,
269 #[serde(deserialize_with = "deserialize_stringified_u64")]
270 pub transaction_index: u64,
271 #[serde(deserialize_with = "deserialize_stringified_numeric")]
272 pub gas: U256,
273 #[serde(deserialize_with = "deserialize_stringified_numeric_opt")]
274 pub gas_price: Option<U256>,
275 #[serde(deserialize_with = "deserialize_stringified_numeric")]
276 pub gas_used: U256,
277 #[serde(deserialize_with = "deserialize_stringified_numeric")]
278 pub cumulative_gas_used: U256,
279 pub input: String,
281 #[serde(deserialize_with = "deserialize_stringified_u64")]
282 pub confirmations: u64,
283}
284
285#[derive(Clone, Debug, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct ERC1155TokenTransferEvent {
289 #[serde(deserialize_with = "deserialize_stringified_block_number")]
290 pub block_number: BlockNumber,
291 pub time_stamp: String,
292 pub hash: B256,
293 #[serde(deserialize_with = "deserialize_stringified_numeric")]
294 pub nonce: U256,
295 pub block_hash: B256,
296 pub from: Address,
297 pub contract_address: Address,
298 pub to: Option<Address>,
299 #[serde(rename = "tokenID")]
300 pub token_id: String,
301 pub token_value: String,
302 pub token_name: String,
303 pub token_symbol: String,
304 #[serde(deserialize_with = "deserialize_stringified_u64")]
305 pub transaction_index: u64,
306 #[serde(deserialize_with = "deserialize_stringified_numeric")]
307 pub gas: U256,
308 #[serde(deserialize_with = "deserialize_stringified_numeric_opt")]
309 pub gas_price: Option<U256>,
310 #[serde(deserialize_with = "deserialize_stringified_numeric")]
311 pub gas_used: U256,
312 #[serde(deserialize_with = "deserialize_stringified_numeric")]
313 pub cumulative_gas_used: U256,
314 pub input: String,
316 #[serde(deserialize_with = "deserialize_stringified_u64")]
317 pub confirmations: u64,
318}
319
320#[derive(Clone, Debug, Serialize, Deserialize)]
322#[serde(rename_all = "camelCase")]
323pub struct MinedBlock {
324 #[serde(deserialize_with = "deserialize_stringified_block_number")]
325 pub block_number: BlockNumber,
326 pub time_stamp: String,
327 pub block_reward: String,
328}
329
330#[derive(Clone, Copy, Debug, Default)]
332pub enum Tag {
333 Earliest,
334 Pending,
335 #[default]
336 Latest,
337}
338
339impl Display for Tag {
340 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
341 match self {
342 Tag::Earliest => write!(f, "earliest"),
343 Tag::Pending => write!(f, "pending"),
344 Tag::Latest => write!(f, "latest"),
345 }
346 }
347}
348
349#[derive(Clone, Copy, Debug)]
351pub enum Sort {
352 Asc,
353 Desc,
354}
355
356impl Display for Sort {
357 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
358 match self {
359 Sort::Asc => write!(f, "asc"),
360 Sort::Desc => write!(f, "desc"),
361 }
362 }
363}
364
365#[derive(Clone, Copy, Debug)]
367pub struct TxListParams {
368 pub start_block: u64,
369 pub end_block: u64,
370 pub page: u64,
371 pub offset: u64,
372 pub sort: Sort,
373}
374
375impl TxListParams {
376 pub fn new(start_block: u64, end_block: u64, page: u64, offset: u64, sort: Sort) -> Self {
377 Self { start_block, end_block, page, offset, sort }
378 }
379}
380
381impl Default for TxListParams {
382 fn default() -> Self {
383 Self { start_block: 0, end_block: 99999999, page: 0, offset: 10000, sort: Sort::Asc }
384 }
385}
386
387impl From<TxListParams> for HashMap<&'static str, String> {
388 fn from(tx_params: TxListParams) -> Self {
389 let mut params = HashMap::new();
390 params.insert("startBlock", tx_params.start_block.to_string());
391 params.insert("endBlock", tx_params.end_block.to_string());
392 params.insert("page", tx_params.page.to_string());
393 params.insert("offset", tx_params.offset.to_string());
394 params.insert("sort", tx_params.sort.to_string());
395 params
396 }
397}
398
399#[derive(Clone, Debug)]
401#[allow(missing_copy_implementations)]
402pub enum InternalTxQueryOption {
403 ByAddress(Address),
404 ByTransactionHash(B256),
405 ByBlockRange,
406}
407
408#[derive(Clone, Debug)]
410#[allow(missing_copy_implementations)]
411pub enum TokenQueryOption {
412 ByAddress(Address),
413 ByContract(Address),
414 ByAddressAndContract(Address, Address),
415}
416
417impl TokenQueryOption {
418 pub fn into_params(self, list_params: TxListParams) -> HashMap<&'static str, String> {
419 let mut params: HashMap<&'static str, String> = list_params.into();
420 match self {
421 TokenQueryOption::ByAddress(address) => {
422 params.insert("address", format!("{address:?}"));
423 params
424 }
425 TokenQueryOption::ByContract(contract) => {
426 params.insert("contractaddress", format!("{contract:?}"));
427 params
428 }
429 TokenQueryOption::ByAddressAndContract(address, contract) => {
430 params.insert("address", format!("{address:?}"));
431 params.insert("contractaddress", format!("{contract:?}"));
432 params
433 }
434 }
435 }
436}
437
438#[derive(Copy, Clone, Debug, Default)]
440pub enum BlockType {
441 #[default]
442 CanonicalBlocks,
443 Uncles,
444}
445
446impl Display for BlockType {
447 fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> {
448 match self {
449 BlockType::CanonicalBlocks => write!(f, "blocks"),
450 BlockType::Uncles => write!(f, "uncles"),
451 }
452 }
453}
454
455impl Client {
456 pub async fn get_ether_balance_single(
467 &self,
468 address: &Address,
469 tag: Option<Tag>,
470 ) -> Result<AccountBalance> {
471 let tag_str = tag.unwrap_or_default().to_string();
472 let addr_str = format!("{address:?}");
473 let query = self.create_query(
474 "account",
475 "balance",
476 HashMap::from([("address", &addr_str), ("tag", &tag_str)]),
477 );
478 let response: Response<String> = self.get_json(&query).await?;
479
480 match response.status.as_str() {
481 "0" => Err(EtherscanError::BalanceFailed),
482 "1" => Ok(AccountBalance { account: *address, balance: response.result }),
483 err => Err(EtherscanError::BadStatusCode(err.to_string())),
484 }
485 }
486
487 pub async fn get_ether_balance_multi(
503 &self,
504 addresses: &[Address],
505 tag: Option<Tag>,
506 ) -> Result<Vec<AccountBalance>> {
507 let tag_str = tag.unwrap_or_default().to_string();
508 let addrs = addresses.iter().map(|x| format!("{x:?}")).collect::<Vec<String>>().join(",");
509 let query: Query<'_, HashMap<&str, &str>> = self.create_query(
510 "account",
511 "balancemulti",
512 HashMap::from([("address", addrs.as_ref()), ("tag", tag_str.as_ref())]),
513 );
514 let response: Response<Vec<AccountBalance>> = self.get_json(&query).await?;
515
516 match response.status.as_str() {
517 "0" => Err(EtherscanError::BalanceFailed),
518 "1" => Ok(response.result),
519 err => Err(EtherscanError::BadStatusCode(err.to_string())),
520 }
521 }
522
523 pub async fn get_transactions(
534 &self,
535 address: &Address,
536 params: Option<TxListParams>,
537 ) -> Result<Vec<NormalTransaction>> {
538 let mut tx_params: HashMap<&str, String> = params.unwrap_or_default().into();
539 tx_params.insert("address", format!("{address:?}"));
540 let query = self.create_query("account", "txlist", tx_params);
541 let response: Response<Vec<NormalTransaction>> = self.get_json(&query).await?;
542
543 Ok(response.result)
544 }
545
546 pub async fn get_internal_transactions(
561 &self,
562 tx_query_option: InternalTxQueryOption,
563 params: Option<TxListParams>,
564 ) -> Result<Vec<InternalTransaction>> {
565 let mut tx_params: HashMap<&str, String> = params.unwrap_or_default().into();
566 match tx_query_option {
567 InternalTxQueryOption::ByAddress(address) => {
568 tx_params.insert("address", format!("{address:?}"));
569 }
570 InternalTxQueryOption::ByTransactionHash(tx_hash) => {
571 tx_params.insert("txhash", format!("{tx_hash:?}"));
572 }
573 _ => {}
574 }
575 let query = self.create_query("account", "txlistinternal", tx_params);
576 let response: Response<Vec<InternalTransaction>> = self.get_json(&query).await?;
577
578 Ok(response.result)
579 }
580
581 pub async fn get_erc20_token_transfer_events(
596 &self,
597 event_query_option: TokenQueryOption,
598 params: Option<TxListParams>,
599 ) -> Result<Vec<ERC20TokenTransferEvent>> {
600 let params = event_query_option.into_params(params.unwrap_or_default());
601 let query = self.create_query("account", "tokentx", params);
602 let response: Response<Vec<ERC20TokenTransferEvent>> = self.get_json(&query).await?;
603
604 Ok(response.result)
605 }
606
607 pub async fn get_erc721_token_transfer_events(
622 &self,
623 event_query_option: TokenQueryOption,
624 params: Option<TxListParams>,
625 ) -> Result<Vec<ERC721TokenTransferEvent>> {
626 let params = event_query_option.into_params(params.unwrap_or_default());
627 let query = self.create_query("account", "tokennfttx", params);
628 let response: Response<Vec<ERC721TokenTransferEvent>> = self.get_json(&query).await?;
629
630 Ok(response.result)
631 }
632
633 pub async fn get_erc1155_token_transfer_events(
649 &self,
650 event_query_option: TokenQueryOption,
651 params: Option<TxListParams>,
652 ) -> Result<Vec<ERC1155TokenTransferEvent>> {
653 let params = event_query_option.into_params(params.unwrap_or_default());
654 let query = self.create_query("account", "token1155tx", params);
655 let response: Response<Vec<ERC1155TokenTransferEvent>> = self.get_json(&query).await?;
656
657 Ok(response.result)
658 }
659
660 pub async fn get_mined_blocks(
671 &self,
672 address: &Address,
673 block_type: Option<BlockType>,
674 page_and_offset: Option<(u64, u64)>,
675 ) -> Result<Vec<MinedBlock>> {
676 let mut params = HashMap::new();
677 params.insert("address", format!("{address:?}"));
678 params.insert("blocktype", block_type.unwrap_or_default().to_string());
679 if let Some((page, offset)) = page_and_offset {
680 params.insert("page", page.to_string());
681 params.insert("offset", offset.to_string());
682 }
683 let query = self.create_query("account", "getminedblocks", params);
684 let response: Response<Vec<MinedBlock>> = self.get_json(&query).await?;
685
686 Ok(response.result)
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use super::*;
693
694 #[test]
696 fn can_parse_response_2612() {
697 let err = r#"{
698 "status": "1",
699 "message": "OK",
700 "result": [
701 {
702 "blockNumber": "18185184",
703 "timeStamp": "1695310607",
704 "hash": "0x95983231acd079498b7628c6b6dd4866f559a23120fbce590c5dd7f10c7628af",
705 "nonce": "1325609",
706 "blockHash": "0x61e106aa2446ba06fe0217eb5bd9dae98a72b56dad2c2197f60a0798ce9f0dc6",
707 "transactionIndex": "45",
708 "from": "0xae2fc483527b8ef99eb5d9b44875f005ba1fae13",
709 "to": "0x6b75d8af000000e20b7a7ddf000ba900b4009a80",
710 "value": "23283064365",
711 "gas": "107142",
712 "gasPrice": "15945612744",
713 "isError": "0",
714 "txreceipt_status": "1",
715 "input": "0xe061",
716 "contractAddress": "",
717 "cumulativeGasUsed": "3013734",
718 "gasUsed": "44879",
719 "confirmations": "28565",
720 "methodId": "0xe061",
721 "functionName": ""
722 }
723 ]
724}"#;
725 let _resp: Response<Vec<NormalTransaction>> = serde_json::from_str(err).unwrap();
726 }
727}