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