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