Skip to content

Commit 02c2a06

Browse files
authored
chore: deprecate Etherscan V1 (#101)
Should be considered as part of foundry-rs/foundry#10913 and merged after initial approval of main PR: foundry-rs/foundry#11387
1 parent b89e7bf commit 02c2a06

File tree

4 files changed

+29
-232
lines changed

4 files changed

+29
-232
lines changed

crates/block-explorers/src/lib.rs

Lines changed: 21 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@ use reqwest::{header, IntoUrl, Url};
2424
use serde::{de::DeserializeOwned, Deserialize, Serialize};
2525
use std::{
2626
borrow::Cow,
27-
collections::HashMap,
2827
io::Write,
2928
path::PathBuf,
30-
str::FromStr,
3129
time::{Duration, SystemTime, UNIX_EPOCH},
3230
};
3331

@@ -46,65 +44,19 @@ pub mod verify;
4644

4745
pub(crate) type Result<T, E = EtherscanError> = std::result::Result<T, E>;
4846

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-
9147
/// The Etherscan.io API client.
9248
#[derive(Clone, Debug)]
9349
pub struct Client {
9450
/// Client that executes HTTP requests
9551
client: reqwest::Client,
9652
/// Etherscan API key
9753
api_key: Option<String>,
98-
/// Etherscan API version
99-
etherscan_api_version: EtherscanApiVersion,
10054
/// Etherscan API endpoint like <https://api.etherscan.io/v2/api?chainid=(chain_id)>
10155
etherscan_api_url: Url,
10256
/// Etherscan base endpoint like <https://etherscan.io>
10357
etherscan_url: Url,
10458
/// Path to where ABI files should be cached
10559
cache: Option<Cache>,
106-
/// Chain ID
107-
chain_id: Option<u64>,
10860
}
10961

11062
impl Client {
@@ -145,32 +97,9 @@ impl Client {
14597
Client::builder().with_api_key(api_key).chain(chain)?.build()
14698
}
14799

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-
157100
/// Create a new client with the correct endpoint with the chain
158101
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()
174103
}
175104

176105
/// Create a new client with the correct endpoints based on the chain and API key
@@ -193,11 +122,6 @@ impl Client {
193122
self
194123
}
195124

196-
/// Returns the configured etherscan api version.
197-
pub fn etherscan_api_version(&self) -> &EtherscanApiVersion {
198-
&self.etherscan_api_version
199-
}
200-
201125
pub fn etherscan_api_url(&self) -> &Url {
202126
&self.etherscan_api_url
203127
}
@@ -262,20 +186,10 @@ impl Client {
262186
async fn post<F: Serialize>(&self, form: &F) -> Result<String> {
263187
trace!(target: "etherscan", "POST {}", self.etherscan_api_url);
264188

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-
274189
let response = self
275190
.client
276191
.post(self.etherscan_api_url.clone())
277192
.form(form)
278-
.query(&post_query)
279193
.send()
280194
.await?
281195
.text()
@@ -325,14 +239,9 @@ impl Client {
325239
apikey: self.api_key.as_deref().map(Cow::Borrowed),
326240
module: Cow::Borrowed(module),
327241
action: Cow::Borrowed(action),
328-
chain_id: if self.url_contains_chainid() { None } else { self.chain_id },
329242
other,
330243
}
331244
}
332-
333-
fn url_contains_chainid(&self) -> bool {
334-
self.etherscan_api_url.query_pairs().any(|(key, _)| key.eq_ignore_ascii_case("chainid"))
335-
}
336245
}
337246

338247
#[derive(Clone, Debug, Default)]
@@ -343,57 +252,39 @@ pub struct ClientBuilder {
343252
api_key: Option<String>,
344253
/// Etherscan API endpoint like <https://api.etherscan.io/v2/api?chainid=(chain_id)>
345254
etherscan_api_url: Option<Url>,
346-
/// Etherscan API version (v2 is new verifier version, v1 is the default)
347-
etherscan_api_version: EtherscanApiVersion,
348255
/// Etherscan base endpoint like <https://etherscan.io>
349256
etherscan_url: Option<Url>,
350257
/// Path to where ABI files should be cached
351258
cache: Option<Cache>,
352-
/// Chain ID
353-
chain_id: Option<u64>,
354259
}
355260

356261
// === impl ClientBuilder ===
357262

358263
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
360265
///
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>
362267
///
363268
/// # Errors
364269
///
365-
/// Fails if the chain is not supported by etherscan
270+
/// Fails if the chain is not supported by Etherscan
366271
pub fn chain(self, chain: Chain) -> Result<Self> {
367272
fn urls(
368273
(api, url): (impl IntoUrl, impl IntoUrl),
369274
) -> (reqwest::Result<Url>, reqwest::Result<Url>) {
370275
(api.into_url(), url.into_url())
371276
}
372-
let (default_etherscan_api_url, etherscan_url) = chain
277+
let (etherscan_api_url, etherscan_url) = chain
373278
.named()
374279
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))?
375280
.etherscan_urls()
376281
.map(urls)
377282
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))?;
378283

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?)
394285
}
395286

396-
/// Configures the etherscan url
287+
/// Configures the Etherscan url
397288
///
398289
/// # Errors
399290
///
@@ -409,7 +300,7 @@ impl ClientBuilder {
409300
self
410301
}
411302

412-
/// Configures the etherscan api url
303+
/// Configures the Etherscan api url
413304
///
414305
/// # Errors
415306
///
@@ -419,29 +310,18 @@ impl ClientBuilder {
419310
Ok(self)
420311
}
421312

422-
/// Configures the etherscan api key
313+
/// Configures the Etherscan api key
423314
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
424315
self.api_key = Some(api_key.into()).filter(|s| !s.is_empty());
425316
self
426317
}
427318

428-
/// Configures cache for etherscan request
319+
/// Configures cache for Etherscan request
429320
pub fn with_cache(mut self, cache_root: Option<PathBuf>, cache_ttl: Duration) -> Self {
430321
self.cache = cache_root.map(|root| Cache::new(root, cache_ttl));
431322
self
432323
}
433324

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-
445325
/// Returns a Client that uses this ClientBuilder configuration.
446326
///
447327
/// # Errors
@@ -450,28 +330,17 @@ impl ClientBuilder {
450330
/// - `etherscan_api_url`
451331
/// - `etherscan_url`
452332
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;
462334

463335
let client = Client {
464336
client: client.unwrap_or_default(),
465337
api_key,
466338
etherscan_api_url: etherscan_api_url
467339
.clone()
468340
.ok_or_else(|| EtherscanError::Builder("etherscan api url".to_string()))?,
469-
// Set default API version to V1 if missing
470-
etherscan_api_version,
471341
etherscan_url: etherscan_url
472342
.ok_or_else(|| EtherscanError::Builder("etherscan url".to_string()))?,
473343
cache,
474-
chain_id,
475344
};
476345
Ok(client)
477346
}
@@ -599,8 +468,6 @@ struct Query<'a, T: Serialize> {
599468
apikey: Option<Cow<'a, str>>,
600469
module: Cow<'a, str>,
601470
action: Cow<'a, str>,
602-
#[serde(rename = "chainId", skip_serializing_if = "Option::is_none")]
603-
chain_id: Option<u64>,
604471
#[serde(flatten)]
605472
other: T,
606473
}
@@ -612,10 +479,7 @@ fn into_url(url: impl IntoUrl) -> std::result::Result<Url, reqwest::Error> {
612479
url.into_url()
613480
}
614481

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> {
619483
match chain.kind() {
620484
ChainKind::Named(named) => match named {
621485
// Fantom is special and doesn't support etherscan api v2
@@ -641,24 +505,15 @@ fn get_api_key_from_chain(
641505

642506
// Rather than get special ENV vars here, normal case is to pull overall
643507
// 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),
654509
},
655510
ChainKind::Id(_) => Err(EtherscanError::ChainNotSupported(chain)),
656511
}
657512
}
658513

659514
#[cfg(test)]
660515
mod tests {
661-
use crate::{Client, EtherscanApiVersion, EtherscanError, ResponseData};
516+
use crate::{Client, EtherscanError, ResponseData};
662517
use alloy_chains::Chain;
663518
use alloy_primitives::{Address, B256};
664519

@@ -671,21 +526,13 @@ mod tests {
671526
}
672527

673528
#[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");
689536
}
690537

691538
#[test]
@@ -736,19 +583,4 @@ mod tests {
736583
let resp: ResponseData<Address> = serde_json::from_value(err).unwrap();
737584
assert!(matches!(resp, ResponseData::Error { .. }));
738585
}
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-
}
754586
}

0 commit comments

Comments
 (0)