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