@@ -24,10 +24,8 @@ use reqwest::{header, IntoUrl, Url};
24
24
use serde:: { de:: DeserializeOwned , Deserialize , Serialize } ;
25
25
use std:: {
26
26
borrow:: Cow ,
27
- collections:: HashMap ,
28
27
io:: Write ,
29
28
path:: PathBuf ,
30
- str:: FromStr ,
31
29
time:: { Duration , SystemTime , UNIX_EPOCH } ,
32
30
} ;
33
31
@@ -46,65 +44,19 @@ pub mod verify;
46
44
47
45
pub ( crate ) type Result < T , E = EtherscanError > = std:: result:: Result < T , E > ;
48
46
49
- /// The URL for the etherscan V2 API without the chainid param set.
50
- pub const ETHERSCAN_V2_API_BASE_URL : & str = "https://api.etherscan.io/v2/api" ;
51
-
52
- /// The Etherscan.io API version 1 - classic verifier, one API per chain, 2 - new multichain
53
- /// verifier
54
- #[ derive( Clone , Default , Debug , PartialEq , Copy , Eq , Deserialize , Serialize ) ]
55
- #[ serde( rename_all = "lowercase" ) ]
56
- pub enum EtherscanApiVersion {
57
- V1 ,
58
- #[ default]
59
- V2 ,
60
- }
61
-
62
- impl std:: fmt:: Display for EtherscanApiVersion {
63
- fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
64
- match self {
65
- EtherscanApiVersion :: V1 => write ! ( f, "v1" ) ,
66
- EtherscanApiVersion :: V2 => write ! ( f, "v2" ) ,
67
- }
68
- }
69
- }
70
-
71
- impl TryFrom < String > for EtherscanApiVersion {
72
- type Error = EtherscanError ;
73
-
74
- fn try_from ( value : String ) -> Result < Self , Self :: Error > {
75
- Self :: from_str ( value. as_str ( ) )
76
- }
77
- }
78
-
79
- impl FromStr for EtherscanApiVersion {
80
- type Err = EtherscanError ;
81
-
82
- fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
83
- match value {
84
- "v1" | "V1" => Ok ( EtherscanApiVersion :: V1 ) ,
85
- "v2" | "V2" => Ok ( EtherscanApiVersion :: V2 ) ,
86
- _ => Err ( EtherscanError :: InvalidApiVersion ) ,
87
- }
88
- }
89
- }
90
-
91
47
/// The Etherscan.io API client.
92
48
#[ derive( Clone , Debug ) ]
93
49
pub struct Client {
94
50
/// Client that executes HTTP requests
95
51
client : reqwest:: Client ,
96
52
/// Etherscan API key
97
53
api_key : Option < String > ,
98
- /// Etherscan API version
99
- etherscan_api_version : EtherscanApiVersion ,
100
54
/// Etherscan API endpoint like <https://api.etherscan.io/v2/api?chainid=(chain_id)>
101
55
etherscan_api_url : Url ,
102
56
/// Etherscan base endpoint like <https://etherscan.io>
103
57
etherscan_url : Url ,
104
58
/// Path to where ABI files should be cached
105
59
cache : Option < Cache > ,
106
- /// Chain ID
107
- chain_id : Option < u64 > ,
108
60
}
109
61
110
62
impl Client {
@@ -145,32 +97,9 @@ impl Client {
145
97
Client :: builder ( ) . with_api_key ( api_key) . chain ( chain) ?. build ( )
146
98
}
147
99
148
- /// Create a new client for the given [`EtherscanApiVersion`].
149
- pub fn new_with_api_version (
150
- chain : Chain ,
151
- api_key : impl Into < String > ,
152
- api_version : EtherscanApiVersion ,
153
- ) -> Result < Self > {
154
- Client :: builder ( ) . with_api_key ( api_key) . with_api_version ( api_version) . chain ( chain) ?. build ( )
155
- }
156
-
157
100
/// Create a new client with the correct endpoint with the chain
158
101
pub fn new_from_env ( chain : Chain ) -> Result < Self > {
159
- Self :: new_with_api_version (
160
- chain,
161
- get_api_key_from_chain ( chain, EtherscanApiVersion :: V2 ) ?,
162
- EtherscanApiVersion :: V2 ,
163
- )
164
- }
165
-
166
- /// Create a new client with the correct endpoints based on the chain and API key
167
- /// from the default environment variable defined in [`Chain`].
168
- pub fn new_v1_from_env ( chain : Chain ) -> Result < Self > {
169
- Self :: new_with_api_version (
170
- chain,
171
- get_api_key_from_chain ( chain, EtherscanApiVersion :: V1 ) ?,
172
- EtherscanApiVersion :: V1 ,
173
- )
102
+ Client :: builder ( ) . with_api_key ( get_api_key_from_chain ( chain) ?) . chain ( chain) ?. build ( )
174
103
}
175
104
176
105
/// Create a new client with the correct endpoints based on the chain and API key
@@ -193,11 +122,6 @@ impl Client {
193
122
self
194
123
}
195
124
196
- /// Returns the configured etherscan api version.
197
- pub fn etherscan_api_version ( & self ) -> & EtherscanApiVersion {
198
- & self . etherscan_api_version
199
- }
200
-
201
125
pub fn etherscan_api_url ( & self ) -> & Url {
202
126
& self . etherscan_api_url
203
127
}
@@ -262,20 +186,10 @@ impl Client {
262
186
async fn post < F : Serialize > ( & self , form : & F ) -> Result < String > {
263
187
trace ! ( target: "etherscan" , "POST {}" , self . etherscan_api_url) ;
264
188
265
- let mut post_query = HashMap :: new ( ) ;
266
-
267
- if self . etherscan_api_version == EtherscanApiVersion :: V2
268
- && self . chain_id . is_some ( )
269
- && !self . url_contains_chainid ( )
270
- {
271
- post_query. insert ( "chainid" , self . chain_id . unwrap ( ) ) ;
272
- }
273
-
274
189
let response = self
275
190
. client
276
191
. post ( self . etherscan_api_url . clone ( ) )
277
192
. form ( form)
278
- . query ( & post_query)
279
193
. send ( )
280
194
. await ?
281
195
. text ( )
@@ -325,14 +239,9 @@ impl Client {
325
239
apikey : self . api_key . as_deref ( ) . map ( Cow :: Borrowed ) ,
326
240
module : Cow :: Borrowed ( module) ,
327
241
action : Cow :: Borrowed ( action) ,
328
- chain_id : if self . url_contains_chainid ( ) { None } else { self . chain_id } ,
329
242
other,
330
243
}
331
244
}
332
-
333
- fn url_contains_chainid ( & self ) -> bool {
334
- self . etherscan_api_url . query_pairs ( ) . any ( |( key, _) | key. eq_ignore_ascii_case ( "chainid" ) )
335
- }
336
245
}
337
246
338
247
#[ derive( Clone , Debug , Default ) ]
@@ -343,57 +252,39 @@ pub struct ClientBuilder {
343
252
api_key : Option < String > ,
344
253
/// Etherscan API endpoint like <https://api.etherscan.io/v2/api?chainid=(chain_id)>
345
254
etherscan_api_url : Option < Url > ,
346
- /// Etherscan API version (v2 is new verifier version, v1 is the default)
347
- etherscan_api_version : EtherscanApiVersion ,
348
255
/// Etherscan base endpoint like <https://etherscan.io>
349
256
etherscan_url : Option < Url > ,
350
257
/// Path to where ABI files should be cached
351
258
cache : Option < Cache > ,
352
- /// Chain ID
353
- chain_id : Option < u64 > ,
354
259
}
355
260
356
261
// === impl ClientBuilder ===
357
262
358
263
impl ClientBuilder {
359
- /// Configures the etherscan url and api url for the given chain
264
+ /// Configures the Etherscan url and api url for the given chain
360
265
///
361
- /// Note: This method also sets the chain_id for etherscan multichain verification: <https://docs.etherscan.io/contract-verification/multichain-verification>
266
+ /// Note: This method also sets the chain_id for Etherscan multichain verification: <https://docs.etherscan.io/contract-verification/multichain-verification>
362
267
///
363
268
/// # Errors
364
269
///
365
- /// Fails if the chain is not supported by etherscan
270
+ /// Fails if the chain is not supported by Etherscan
366
271
pub fn chain ( self , chain : Chain ) -> Result < Self > {
367
272
fn urls (
368
273
( api, url) : ( impl IntoUrl , impl IntoUrl ) ,
369
274
) -> ( reqwest:: Result < Url > , reqwest:: Result < Url > ) {
370
275
( api. into_url ( ) , url. into_url ( ) )
371
276
}
372
- let ( default_etherscan_api_url , etherscan_url) = chain
277
+ let ( etherscan_api_url , etherscan_url) = chain
373
278
. named ( )
374
279
. ok_or_else ( || EtherscanError :: ChainNotSupported ( chain) ) ?
375
280
. etherscan_urls ( )
376
281
. map ( urls)
377
282
. ok_or_else ( || EtherscanError :: ChainNotSupported ( chain) ) ?;
378
283
379
- // V2 etherscan default API urls are different – this handles that case.
380
- let etherscan_api_url = if self . etherscan_api_version == EtherscanApiVersion :: V2 {
381
- Url :: parse ( ETHERSCAN_V2_API_BASE_URL )
382
- . map_err ( |_| EtherscanError :: Builder ( "Bad URL Parse" . into ( ) ) ) ?
383
- } else {
384
- default_etherscan_api_url?
385
- } ;
386
-
387
- self . with_chain_id ( chain) . with_api_url ( etherscan_api_url) ?. with_url ( etherscan_url?)
388
- }
389
-
390
- /// Configures the etherscan api version
391
- pub fn with_api_version ( mut self , api_version : EtherscanApiVersion ) -> Self {
392
- self . etherscan_api_version = api_version;
393
- self
284
+ self . with_api_url ( etherscan_api_url?) ?. with_url ( etherscan_url?)
394
285
}
395
286
396
- /// Configures the etherscan url
287
+ /// Configures the Etherscan url
397
288
///
398
289
/// # Errors
399
290
///
@@ -409,7 +300,7 @@ impl ClientBuilder {
409
300
self
410
301
}
411
302
412
- /// Configures the etherscan api url
303
+ /// Configures the Etherscan api url
413
304
///
414
305
/// # Errors
415
306
///
@@ -419,29 +310,18 @@ impl ClientBuilder {
419
310
Ok ( self )
420
311
}
421
312
422
- /// Configures the etherscan api key
313
+ /// Configures the Etherscan api key
423
314
pub fn with_api_key ( mut self , api_key : impl Into < String > ) -> Self {
424
315
self . api_key = Some ( api_key. into ( ) ) . filter ( |s| !s. is_empty ( ) ) ;
425
316
self
426
317
}
427
318
428
- /// Configures cache for etherscan request
319
+ /// Configures cache for Etherscan request
429
320
pub fn with_cache ( mut self , cache_root : Option < PathBuf > , cache_ttl : Duration ) -> Self {
430
321
self . cache = cache_root. map ( |root| Cache :: new ( root, cache_ttl) ) ;
431
322
self
432
323
}
433
324
434
- /// Configures the chain id for etherscan verification: <https://docs.etherscan.io/contract-verification/multichain-verification>
435
- pub fn with_chain_id ( mut self , chain : Chain ) -> Self {
436
- self . chain_id = Some ( chain. id ( ) ) ;
437
- self
438
- }
439
-
440
- /// Returns the chain the client is built on.
441
- pub fn get_chain ( & self ) -> Option < Chain > {
442
- self . chain_id . map ( Chain :: from_id)
443
- }
444
-
445
325
/// Returns a Client that uses this ClientBuilder configuration.
446
326
///
447
327
/// # Errors
@@ -450,28 +330,17 @@ impl ClientBuilder {
450
330
/// - `etherscan_api_url`
451
331
/// - `etherscan_url`
452
332
pub fn build ( self ) -> Result < Client > {
453
- let ClientBuilder {
454
- client,
455
- api_key,
456
- etherscan_api_version,
457
- etherscan_api_url,
458
- etherscan_url,
459
- cache,
460
- chain_id,
461
- } = self ;
333
+ let ClientBuilder { client, api_key, etherscan_api_url, etherscan_url, cache } = self ;
462
334
463
335
let client = Client {
464
336
client : client. unwrap_or_default ( ) ,
465
337
api_key,
466
338
etherscan_api_url : etherscan_api_url
467
339
. clone ( )
468
340
. ok_or_else ( || EtherscanError :: Builder ( "etherscan api url" . to_string ( ) ) ) ?,
469
- // Set default API version to V1 if missing
470
- etherscan_api_version,
471
341
etherscan_url : etherscan_url
472
342
. ok_or_else ( || EtherscanError :: Builder ( "etherscan url" . to_string ( ) ) ) ?,
473
343
cache,
474
- chain_id,
475
344
} ;
476
345
Ok ( client)
477
346
}
@@ -599,8 +468,6 @@ struct Query<'a, T: Serialize> {
599
468
apikey : Option < Cow < ' a , str > > ,
600
469
module : Cow < ' a , str > ,
601
470
action : Cow < ' a , str > ,
602
- #[ serde( rename = "chainId" , skip_serializing_if = "Option::is_none" ) ]
603
- chain_id : Option < u64 > ,
604
471
#[ serde( flatten) ]
605
472
other : T ,
606
473
}
@@ -612,10 +479,7 @@ fn into_url(url: impl IntoUrl) -> std::result::Result<Url, reqwest::Error> {
612
479
url. into_url ( )
613
480
}
614
481
615
- fn get_api_key_from_chain (
616
- chain : Chain ,
617
- api_version : EtherscanApiVersion ,
618
- ) -> Result < String , EtherscanError > {
482
+ fn get_api_key_from_chain ( chain : Chain ) -> Result < String , EtherscanError > {
619
483
match chain. kind ( ) {
620
484
ChainKind :: Named ( named) => match named {
621
485
// Fantom is special and doesn't support etherscan api v2
@@ -641,24 +505,15 @@ fn get_api_key_from_chain(
641
505
642
506
// Rather than get special ENV vars here, normal case is to pull overall
643
507
// ETHERSCAN_API_KEY
644
- _ => {
645
- if api_version == EtherscanApiVersion :: V1 {
646
- named
647
- . etherscan_api_key_name ( )
648
- . ok_or_else ( || EtherscanError :: ChainNotSupported ( chain) )
649
- . and_then ( |key_name| std:: env:: var ( key_name) . map_err ( Into :: into) )
650
- } else {
651
- std:: env:: var ( "ETHERSCAN_API_KEY" ) . map_err ( Into :: into)
652
- }
653
- }
508
+ _ => std:: env:: var ( "ETHERSCAN_API_KEY" ) . map_err ( Into :: into) ,
654
509
} ,
655
510
ChainKind :: Id ( _) => Err ( EtherscanError :: ChainNotSupported ( chain) ) ,
656
511
}
657
512
}
658
513
659
514
#[ cfg( test) ]
660
515
mod tests {
661
- use crate :: { Client , EtherscanApiVersion , EtherscanError , ResponseData } ;
516
+ use crate :: { Client , EtherscanError , ResponseData } ;
662
517
use alloy_chains:: Chain ;
663
518
use alloy_primitives:: { Address , B256 } ;
664
519
@@ -671,21 +526,13 @@ mod tests {
671
526
}
672
527
673
528
#[ test]
674
- fn test_api_paths_v1 ( ) {
675
- let client =
676
- Client :: new_with_api_version ( Chain :: goerli ( ) , "" , EtherscanApiVersion :: V1 ) . unwrap ( ) ;
677
- assert_eq ! ( client. etherscan_api_url. as_str( ) , "https://api-goerli.etherscan.io/api" ) ;
678
-
679
- assert_eq ! ( client. block_url( 100 ) , "https://goerli.etherscan.io/block/100" ) ;
680
- }
681
-
682
- #[ test]
683
- fn test_api_paths_v2 ( ) {
684
- let client =
685
- Client :: new_with_api_version ( Chain :: goerli ( ) , "" , EtherscanApiVersion :: V2 ) . unwrap ( ) ;
686
- assert_eq ! ( client. etherscan_api_url. as_str( ) , "https://api.etherscan.io/v2/api" ) ;
687
-
688
- assert_eq ! ( client. block_url( 100 ) , "https://goerli.etherscan.io/block/100" ) ;
529
+ fn test_api_paths ( ) {
530
+ let client = Client :: new ( Chain :: sepolia ( ) , "" ) . unwrap ( ) ;
531
+ assert_eq ! (
532
+ client. etherscan_api_url. as_str( ) ,
533
+ "https://api.etherscan.io/v2/api?chainid=11155111"
534
+ ) ;
535
+ assert_eq ! ( client. block_url( 100 ) , "https://sepolia.etherscan.io/block/100" ) ;
689
536
}
690
537
691
538
#[ test]
@@ -736,19 +583,4 @@ mod tests {
736
583
let resp: ResponseData < Address > = serde_json:: from_value ( err) . unwrap ( ) ;
737
584
assert ! ( matches!( resp, ResponseData :: Error { .. } ) ) ;
738
585
}
739
-
740
- #[ test]
741
- fn can_parse_api_version ( ) {
742
- assert_eq ! (
743
- EtherscanApiVersion :: try_from( "v1" . to_string( ) ) . unwrap( ) ,
744
- EtherscanApiVersion :: V1
745
- ) ;
746
- assert_eq ! (
747
- EtherscanApiVersion :: try_from( "v2" . to_string( ) ) . unwrap( ) ,
748
- EtherscanApiVersion :: V2
749
- ) ;
750
-
751
- let parse_err = EtherscanApiVersion :: try_from ( "fail" . to_string ( ) ) . unwrap_err ( ) ;
752
- assert ! ( matches!( parse_err, EtherscanError :: InvalidApiVersion ) ) ;
753
- }
754
586
}
0 commit comments