-
Notifications
You must be signed in to change notification settings - Fork 2k
Adding support to etherscan-v2 #10298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
97e23a6
a98d6d0
395712a
5866805
735212e
e2f4f11
3d60de4
5580f96
08f6305
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ use figment::{ | |
value::{Dict, Map}, | ||
Error, Metadata, Profile, Provider, | ||
}; | ||
use foundry_block_explorers::EtherscanApiVersion; | ||
use heck::ToKebabCase; | ||
use serde::{Deserialize, Deserializer, Serialize, Serializer}; | ||
use std::{ | ||
|
@@ -173,6 +174,9 @@ pub struct EtherscanConfig { | |
/// Etherscan API URL | ||
#[serde(default, skip_serializing_if = "Option::is_none")] | ||
pub url: Option<String>, | ||
/// Etherscan API Version. Defaults to v2 | ||
#[serde(default, skip_serializing_if = "Option::is_none")] | ||
pub api_version: Option<EtherscanApiVersion>, | ||
mattsse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// The etherscan API KEY that's required to make requests | ||
pub key: EtherscanApiKey, | ||
} | ||
|
@@ -188,7 +192,9 @@ impl EtherscanConfig { | |
self, | ||
alias: Option<&str>, | ||
) -> Result<ResolvedEtherscanConfig, EtherscanConfigError> { | ||
let Self { chain, mut url, key } = self; | ||
let Self { chain, mut url, key, api_version } = self; | ||
|
||
let api_version_string = api_version.map(|v| v.to_string()); | ||
|
||
if let Some(url) = &mut url { | ||
*url = interpolate(url)?; | ||
|
@@ -219,17 +225,23 @@ impl EtherscanConfig { | |
match (chain, url) { | ||
(Some(chain), Some(api_url)) => Ok(ResolvedEtherscanConfig { | ||
api_url, | ||
api_version: api_version_string, | ||
browser_url: chain.etherscan_urls().map(|(_, url)| url.to_string()), | ||
key, | ||
chain: Some(chain), | ||
}), | ||
(Some(chain), None) => ResolvedEtherscanConfig::create(key, chain).ok_or_else(|| { | ||
let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); | ||
EtherscanConfigError::UnknownChain(msg, chain) | ||
(Some(chain), None) => ResolvedEtherscanConfig::create(key, chain, api_version_string) | ||
.ok_or_else(|| { | ||
let msg = alias.map(|a| format!(" `{a}`")).unwrap_or_default(); | ||
EtherscanConfigError::UnknownChain(msg, chain) | ||
}), | ||
(None, Some(api_url)) => Ok(ResolvedEtherscanConfig { | ||
api_url, | ||
browser_url: None, | ||
key, | ||
chain: None, | ||
api_version: api_version_string, | ||
}), | ||
(None, Some(api_url)) => { | ||
Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None }) | ||
} | ||
(None, None) => { | ||
let msg = alias | ||
.map(|a| format!(" for Etherscan config with unknown alias `{a}`")) | ||
|
@@ -251,18 +263,26 @@ pub struct ResolvedEtherscanConfig { | |
pub browser_url: Option<String>, | ||
/// The resolved API key. | ||
pub key: String, | ||
/// Etherscan API Version. | ||
#[serde(default, skip_serializing_if = "Option::is_none")] | ||
pub api_version: Option<String>, | ||
Comment on lines
+266
to
+268
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this also be just etherscanapiversion here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn’t too sure what the pattern was for the __Resolved struct if it was recommended to be a string since the other fields were and it’d be parsed later. If this is the case I can switch it :). Went back and forth. |
||
/// The chain name or EIP-155 chain ID. | ||
#[serde(default, skip_serializing_if = "Option::is_none")] | ||
pub chain: Option<Chain>, | ||
} | ||
|
||
impl ResolvedEtherscanConfig { | ||
/// Creates a new instance using the api key and chain | ||
pub fn create(api_key: impl Into<String>, chain: impl Into<Chain>) -> Option<Self> { | ||
pub fn create( | ||
api_key: impl Into<String>, | ||
chain: impl Into<Chain>, | ||
api_version: Option<impl Into<String>>, | ||
) -> Option<Self> { | ||
let chain = chain.into(); | ||
let (api_url, browser_url) = chain.etherscan_urls()?; | ||
Some(Self { | ||
api_url: api_url.to_string(), | ||
api_version: api_version.map(|v| v.into()), | ||
browser_url: Some(browser_url.to_string()), | ||
key: api_key.into(), | ||
chain: Some(chain), | ||
|
@@ -294,7 +314,7 @@ impl ResolvedEtherscanConfig { | |
self, | ||
) -> Result<foundry_block_explorers::Client, foundry_block_explorers::errors::EtherscanError> | ||
{ | ||
let Self { api_url, browser_url, key: api_key, chain } = self; | ||
let Self { api_url, browser_url, key: api_key, chain, api_version } = self; | ||
let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed"); | ||
|
||
let cache = chain | ||
|
@@ -310,12 +330,14 @@ impl ResolvedEtherscanConfig { | |
} | ||
|
||
let api_url = into_url(&api_url)?; | ||
let parsed_api_version = api_version.map(EtherscanApiVersion::try_from).transpose()?; | ||
let client = reqwest::Client::builder() | ||
.user_agent(ETHERSCAN_USER_AGENT) | ||
.tls_built_in_root_certs(api_url.scheme() == "https") | ||
.build()?; | ||
foundry_block_explorers::Client::builder() | ||
.with_client(client) | ||
.with_api_version(parsed_api_version.unwrap_or_default()) | ||
.with_api_key(api_key) | ||
.with_api_url(api_url)? | ||
// the browser url is not used/required by the client so we can simply set the | ||
|
@@ -423,12 +445,36 @@ mod tests { | |
chain: Some(Mainnet.into()), | ||
url: None, | ||
key: EtherscanApiKey::Key("ABCDEFG".to_string()), | ||
api_version: None, | ||
}, | ||
); | ||
|
||
let mut resolved = configs.resolved(); | ||
let config = resolved.remove("mainnet").unwrap().unwrap(); | ||
let _ = config.into_client().unwrap(); | ||
// None version = None | ||
assert_eq!(config.api_version, None); | ||
let client = config.into_client().unwrap(); | ||
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); | ||
} | ||
|
||
#[test] | ||
fn can_create_v1_client_via_chain() { | ||
let mut configs = EtherscanConfigs::default(); | ||
configs.insert( | ||
"mainnet".to_string(), | ||
EtherscanConfig { | ||
chain: Some(Mainnet.into()), | ||
url: None, | ||
api_version: Some(EtherscanApiVersion::V1), | ||
key: EtherscanApiKey::Key("ABCDEG".to_string()), | ||
}, | ||
); | ||
|
||
let mut resolved = configs.resolved(); | ||
let config = resolved.remove("mainnet").unwrap().unwrap(); | ||
assert_eq!(config.api_version, Some("v1".to_string())); | ||
let client = config.into_client().unwrap(); | ||
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V1); | ||
} | ||
|
||
#[test] | ||
|
@@ -440,6 +486,7 @@ mod tests { | |
chain: Some(Mainnet.into()), | ||
url: Some("https://api.etherscan.io/api".to_string()), | ||
key: EtherscanApiKey::Key("ABCDEFG".to_string()), | ||
api_version: None, | ||
}, | ||
); | ||
|
||
|
@@ -457,6 +504,7 @@ mod tests { | |
EtherscanConfig { | ||
chain: Some(Mainnet.into()), | ||
url: Some("https://api.etherscan.io/api".to_string()), | ||
api_version: None, | ||
key: EtherscanApiKey::Env(format!("${{{env}}}")), | ||
}, | ||
); | ||
|
@@ -470,7 +518,8 @@ mod tests { | |
let mut resolved = configs.resolved(); | ||
let config = resolved.remove("mainnet").unwrap().unwrap(); | ||
assert_eq!(config.key, "ABCDEFG"); | ||
let _ = config.into_client().unwrap(); | ||
let client = config.into_client().unwrap(); | ||
assert_eq!(*client.etherscan_api_version(), EtherscanApiVersion::V2); | ||
|
||
std::env::remove_var(env); | ||
} | ||
|
@@ -484,6 +533,7 @@ mod tests { | |
chain: None, | ||
url: Some("https://api.etherscan.io/api".to_string()), | ||
key: EtherscanApiKey::Key("ABCDEFG".to_string()), | ||
api_version: None, | ||
}, | ||
); | ||
|
||
|
@@ -498,6 +548,7 @@ mod tests { | |
chain: None, | ||
url: Some("https://api.etherscan.io/api".to_string()), | ||
key: EtherscanApiKey::Key("ABCDEFG".to_string()), | ||
api_version: None, | ||
}; | ||
let resolved = config.clone().resolve(Some("base_sepolia")).unwrap(); | ||
assert_eq!(resolved.chain, Some(Chain::base_sepolia())); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once this package is released, the tag should be removed.