ethers_etherscan/
account.rs

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/// The raw response from the balance-related API endpoints
14#[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/// Possible values for some field responses.
103///
104/// Transactions from the Genesis block may contain fields that do not conform to the expected
105/// types.
106#[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/// The raw response from the transaction list API endpoint
136#[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/// The raw response from the internal transaction list API endpoint
178#[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/// The raw response from the ERC20 transfer list API endpoint
206#[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    /// deprecated
235    pub input: String,
236    #[serde(deserialize_with = "deserialize_stringified_u64")]
237    pub confirmations: u64,
238}
239
240/// The raw response from the ERC721 transfer list API endpoint
241#[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    /// deprecated
270    pub input: String,
271    #[serde(deserialize_with = "deserialize_stringified_u64")]
272    pub confirmations: u64,
273}
274
275/// The raw response from the ERC1155 transfer list API endpoint
276#[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    /// deprecated
305    pub input: String,
306    #[serde(deserialize_with = "deserialize_stringified_u64")]
307    pub confirmations: u64,
308}
309
310/// The raw response from the mined blocks API endpoint
311#[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/// The raw response from the beacon wihtdrawal transaction list API endpoint
321#[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/// The pre-defined block parameter for balance API endpoints
336#[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/// The list sorting preference
355#[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/// Common optional arguments for the transaction or event list API endpoints
371#[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/// Options for querying internal transactions
405#[derive(Clone, Debug)]
406pub enum InternalTxQueryOption {
407    ByAddress(Address),
408    ByTransactionHash(H256),
409    ByBlockRange,
410}
411
412/// Options for querying ERC20 or ERC721 token transfers
413#[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/// The pre-defined block type for retrieving mined blocks
442#[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    /// Returns the Ether balance of a given address.
460    ///
461    /// # Examples
462    ///
463    /// ```no_run
464    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
465    /// let address = "0x58eB28A67731c570Ef827C365c89B5751F9E6b0a".parse()?;
466    /// let balance = client.get_ether_balance_single(&address, None).await?;
467    /// # Ok(()) }
468    /// ```
469    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    /// Returns the balance of the accounts from a list of addresses.
491    ///
492    /// # Examples
493    ///
494    /// ```no_run
495    /// # use ethers_core::types::Address;
496    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
497    /// let addresses = [
498    ///     "0x3E3c00494d0b306a0739E480DBB5DB91FFb5d4CB".parse::<Address>()?,
499    ///     "0x7e9996ef050a9Fa7A01248e63271F69086aaFc9D".parse::<Address>()?,
500    /// ];
501    /// let balances = client.get_ether_balance_multi(&addresses, None).await?;
502    /// assert_eq!(addresses.len(), balances.len());
503    /// # Ok(()) }
504    /// ```
505    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    /// Returns the list of transactions performed by an address, with optional pagination.
527    ///
528    /// # Examples
529    ///
530    /// ```no_run
531    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
532    /// let address = "0x1f162cf730564efD2Bb96eb27486A2801d76AFB6".parse()?;
533    /// let transactions = client.get_transactions(&address, None).await?;
534    /// # Ok(()) }
535    /// ```
536    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    /// Returns the list of internal transactions performed by an address or within a transaction,
550    /// with optional pagination.
551    ///
552    /// # Examples
553    ///
554    /// ```no_run
555    /// use ethers_etherscan::account::InternalTxQueryOption;
556    ///
557    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
558    /// let address = "0x2c1ba59d6f58433fb1eaee7d20b26ed83bda51a3".parse()?;
559    /// let query = InternalTxQueryOption::ByAddress(address);
560    /// let internal_transactions = client.get_internal_transactions(query, None).await?;
561    /// # Ok(()) }
562    /// ```
563    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    /// Returns the list of ERC-20 tokens transferred by an address, with optional filtering by
585    /// token contract.
586    ///
587    /// # Examples
588    ///
589    /// ```no_run
590    /// use ethers_etherscan::account::TokenQueryOption;
591    ///
592    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
593    /// let address = "0x4e83362442b8d1bec281594cea3050c8eb01311c".parse()?;
594    /// let query = TokenQueryOption::ByAddress(address);
595    /// let events = client.get_erc20_token_transfer_events(query, None).await?;
596    /// # Ok(()) }
597    /// ```
598    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    /// Returns the list of ERC-721 ( NFT ) tokens transferred by an address, with optional
611    /// filtering by token contract.
612    ///
613    /// # Examples
614    ///
615    /// ```no_run
616    /// use ethers_etherscan::account::TokenQueryOption;
617    ///
618    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
619    /// let contract = "0x06012c8cf97bead5deae237070f9587f8e7a266d".parse()?;
620    /// let query = TokenQueryOption::ByContract(contract);
621    /// let events = client.get_erc721_token_transfer_events(query, None).await?;
622    /// # Ok(()) }
623    /// ```
624    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    /// Returns the list of ERC-1155 ( NFT ) tokens transferred by an address, with optional
637    /// filtering by token contract.
638    ///
639    /// # Examples
640    ///
641    /// ```no_run
642    /// use ethers_etherscan::account::TokenQueryOption;
643    ///
644    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
645    /// let address = "0x216CD350a4044e7016f14936663e2880Dd2A39d7".parse()?;
646    /// let contract = "0x495f947276749ce646f68ac8c248420045cb7b5e".parse()?;
647    /// let query = TokenQueryOption::ByAddressAndContract(address, contract);
648    /// let events = client.get_erc1155_token_transfer_events(query, None).await?;
649    /// # Ok(()) }
650    /// ```
651    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    /// Returns the list of blocks mined by an address.
664    ///
665    /// # Examples
666    ///
667    /// ```no_run
668    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
669    /// let address = "0x9dd134d14d1e65f84b706d6f205cd5b1cd03a46b".parse()?;
670    /// let blocks = client.get_mined_blocks(&address, None, None).await?;
671    /// # Ok(()) }
672    /// ```
673    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    /// Returns the list of beacon withdrawal transactions performed by an address, with optional
693    /// pagination.
694    ///
695    /// # Examples
696    ///
697    /// ```no_run
698    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
699    /// let address = "0xB9D7934878B5FB9610B3fE8A5e441e8fad7E293f".parse()?;
700    /// let beacon_withdrawal_transactions = client.get_beacon_withdrawal_transactions(&address, None).await?;
701    /// # Ok(()) }
702    /// ```
703    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    // <https://github.com/gakonst/ethers-rs/issues/2612>
722    #[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}