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