@@ -9,6 +9,7 @@ use figment::{
9
9
value:: { Dict , Map } ,
10
10
Error , Metadata , Profile , Provider ,
11
11
} ;
12
+ use foundry_block_explorers:: EtherscanApiVersion ;
12
13
use inflector:: Inflector ;
13
14
use serde:: { Deserialize , Deserializer , Serialize , Serializer } ;
14
15
use std:: {
@@ -173,6 +174,9 @@ pub struct EtherscanConfig {
173
174
/// Etherscan API URL
174
175
#[ serde( default , skip_serializing_if = "Option::is_none" ) ]
175
176
pub url : Option < String > ,
177
+ /// Etherscan API Version. Defaults to v2
178
+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
179
+ pub api_version : Option < EtherscanApiVersion > ,
176
180
/// The etherscan API KEY that's required to make requests
177
181
pub key : EtherscanApiKey ,
178
182
}
@@ -188,7 +192,7 @@ impl EtherscanConfig {
188
192
self ,
189
193
alias : Option < & str > ,
190
194
) -> Result < ResolvedEtherscanConfig , EtherscanConfigError > {
191
- let Self { chain, mut url, key } = self ;
195
+ let Self { chain, mut url, key, api_version } = self ;
192
196
193
197
if let Some ( url) = & mut url {
194
198
* url = interpolate ( url) ?;
@@ -219,17 +223,27 @@ impl EtherscanConfig {
219
223
match ( chain, url) {
220
224
( Some ( chain) , Some ( api_url) ) => Ok ( ResolvedEtherscanConfig {
221
225
api_url,
226
+ api_version : api_version. map ( |v| v. to_string ( ) ) ,
222
227
browser_url : chain. etherscan_urls ( ) . map ( |( _, url) | url. to_string ( ) ) ,
223
228
key,
224
229
chain : Some ( chain) ,
225
230
} ) ,
226
- ( Some ( chain) , None ) => ResolvedEtherscanConfig :: create ( key, chain) . ok_or_else ( || {
231
+ ( Some ( chain) , None ) => ResolvedEtherscanConfig :: create (
232
+ key,
233
+ chain,
234
+ api_version. map ( |v| v. to_string ( ) ) ,
235
+ )
236
+ . ok_or_else ( || {
227
237
let msg = alias. map ( |a| format ! ( " `{a}`" ) ) . unwrap_or_default ( ) ;
228
238
EtherscanConfigError :: UnknownChain ( msg, chain)
229
239
} ) ,
230
- ( None , Some ( api_url) ) => {
231
- Ok ( ResolvedEtherscanConfig { api_url, browser_url : None , key, chain : None } )
232
- }
240
+ ( None , Some ( api_url) ) => Ok ( ResolvedEtherscanConfig {
241
+ api_url,
242
+ browser_url : None ,
243
+ key,
244
+ chain : None ,
245
+ api_version : api_version. map ( |v| v. to_string ( ) ) ,
246
+ } ) ,
233
247
( None , None ) => {
234
248
let msg = alias
235
249
. map ( |a| format ! ( " for Etherscan config with unknown alias `{a}`" ) )
@@ -251,18 +265,26 @@ pub struct ResolvedEtherscanConfig {
251
265
pub browser_url : Option < String > ,
252
266
/// The resolved API key.
253
267
pub key : String ,
268
+ /// Etherscan API Version.
269
+ #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
270
+ pub api_version : Option < String > ,
254
271
/// The chain name or EIP-155 chain ID.
255
272
#[ serde( default , skip_serializing_if = "Option::is_none" ) ]
256
273
pub chain : Option < Chain > ,
257
274
}
258
275
259
276
impl ResolvedEtherscanConfig {
260
277
/// Creates a new instance using the api key and chain
261
- pub fn create ( api_key : impl Into < String > , chain : impl Into < Chain > ) -> Option < Self > {
278
+ pub fn create (
279
+ api_key : impl Into < String > ,
280
+ chain : impl Into < Chain > ,
281
+ api_version : Option < impl Into < String > > ,
282
+ ) -> Option < Self > {
262
283
let chain = chain. into ( ) ;
263
284
let ( api_url, browser_url) = chain. etherscan_urls ( ) ?;
264
285
Some ( Self {
265
286
api_url : api_url. to_string ( ) ,
287
+ api_version : api_version. map ( |v| v. into ( ) ) ,
266
288
browser_url : Some ( browser_url. to_string ( ) ) ,
267
289
key : api_key. into ( ) ,
268
290
chain : Some ( chain) ,
@@ -294,7 +316,7 @@ impl ResolvedEtherscanConfig {
294
316
self ,
295
317
) -> Result < foundry_block_explorers:: Client , foundry_block_explorers:: errors:: EtherscanError >
296
318
{
297
- let Self { api_url, browser_url, key : api_key, chain } = self ;
319
+ let Self { api_url, browser_url, key : api_key, chain, api_version } = self ;
298
320
let ( mainnet_api, mainnet_url) = NamedChain :: Mainnet . etherscan_urls ( ) . expect ( "exist; qed" ) ;
299
321
300
322
let cache = chain
@@ -310,12 +332,14 @@ impl ResolvedEtherscanConfig {
310
332
}
311
333
312
334
let api_url = into_url ( & api_url) ?;
335
+ let parsed_api_version = EtherscanApiVersion :: try_from ( api_version. unwrap_or_default ( ) ) ?;
313
336
let client = reqwest:: Client :: builder ( )
314
337
. user_agent ( ETHERSCAN_USER_AGENT )
315
338
. tls_built_in_root_certs ( api_url. scheme ( ) == "https" )
316
339
. build ( ) ?;
317
340
foundry_block_explorers:: Client :: builder ( )
318
341
. with_client ( client)
342
+ . with_api_version ( parsed_api_version)
319
343
. with_api_key ( api_key)
320
344
. with_api_url ( api_url) ?
321
345
// the browser url is not used/required by the client so we can simply set the
@@ -423,12 +447,36 @@ mod tests {
423
447
chain : Some ( Mainnet . into ( ) ) ,
424
448
url : None ,
425
449
key : EtherscanApiKey :: Key ( "ABCDEFG" . to_string ( ) ) ,
450
+ api_version : None ,
426
451
} ,
427
452
) ;
428
453
429
454
let mut resolved = configs. resolved ( ) ;
430
455
let config = resolved. remove ( "mainnet" ) . unwrap ( ) . unwrap ( ) ;
431
- let _ = config. into_client ( ) . unwrap ( ) ;
456
+ // None version = None
457
+ assert_eq ! ( config. api_version, None ) ;
458
+ let client = config. into_client ( ) . unwrap ( ) ;
459
+ assert_eq ! ( * client. etherscan_api_version( ) , EtherscanApiVersion :: V2 ) ;
460
+ }
461
+
462
+ #[ test]
463
+ fn can_create_v1_client_via_chain ( ) {
464
+ let mut configs = EtherscanConfigs :: default ( ) ;
465
+ configs. insert (
466
+ "mainnet" . to_string ( ) ,
467
+ EtherscanConfig {
468
+ chain : Some ( Mainnet . into ( ) ) ,
469
+ url : None ,
470
+ api_version : Some ( EtherscanApiVersion :: V1 ) ,
471
+ key : EtherscanApiKey :: Key ( "ABCDEG" . to_string ( ) ) ,
472
+ } ,
473
+ ) ;
474
+
475
+ let mut resolved = configs. resolved ( ) ;
476
+ let config = resolved. remove ( "mainnet" ) . unwrap ( ) . unwrap ( ) ;
477
+ assert_eq ! ( config. api_version, Some ( "v1" . to_string( ) ) ) ;
478
+ let client = config. into_client ( ) . unwrap ( ) ;
479
+ assert_eq ! ( * client. etherscan_api_version( ) , EtherscanApiVersion :: V1 ) ;
432
480
}
433
481
434
482
#[ test]
@@ -440,6 +488,7 @@ mod tests {
440
488
chain : Some ( Mainnet . into ( ) ) ,
441
489
url : Some ( "https://api.etherscan.io/api" . to_string ( ) ) ,
442
490
key : EtherscanApiKey :: Key ( "ABCDEFG" . to_string ( ) ) ,
491
+ api_version : None ,
443
492
} ,
444
493
) ;
445
494
@@ -457,6 +506,7 @@ mod tests {
457
506
EtherscanConfig {
458
507
chain : Some ( Mainnet . into ( ) ) ,
459
508
url : Some ( "https://api.etherscan.io/api" . to_string ( ) ) ,
509
+ api_version : None ,
460
510
key : EtherscanApiKey :: Env ( format ! ( "${{{env}}}" ) ) ,
461
511
} ,
462
512
) ;
@@ -470,7 +520,8 @@ mod tests {
470
520
let mut resolved = configs. resolved ( ) ;
471
521
let config = resolved. remove ( "mainnet" ) . unwrap ( ) . unwrap ( ) ;
472
522
assert_eq ! ( config. key, "ABCDEFG" ) ;
473
- let _ = config. into_client ( ) . unwrap ( ) ;
523
+ let client = config. into_client ( ) . unwrap ( ) ;
524
+ assert_eq ! ( * client. etherscan_api_version( ) , EtherscanApiVersion :: V2 ) ;
474
525
475
526
std:: env:: remove_var ( env) ;
476
527
}
@@ -484,6 +535,7 @@ mod tests {
484
535
chain : None ,
485
536
url : Some ( "https://api.etherscan.io/api" . to_string ( ) ) ,
486
537
key : EtherscanApiKey :: Key ( "ABCDEFG" . to_string ( ) ) ,
538
+ api_version : None ,
487
539
} ,
488
540
) ;
489
541
@@ -498,6 +550,7 @@ mod tests {
498
550
chain : None ,
499
551
url : Some ( "https://api.etherscan.io/api" . to_string ( ) ) ,
500
552
key : EtherscanApiKey :: Key ( "ABCDEFG" . to_string ( ) ) ,
553
+ api_version : None ,
501
554
} ;
502
555
let resolved = config. clone ( ) . resolve ( Some ( "base_sepolia" ) ) . unwrap ( ) ;
503
556
assert_eq ! ( resolved. chain, Some ( Chain :: base_sepolia( ) ) ) ;
0 commit comments