chore: move etherscan api key env var matching to Chain enum (#2204)

* fix: mainnet, optimism blocktimes

* chore: move etherscan api key env var matching to Chain enum

* docs: missing exhaustiveness comment
This commit is contained in:
DaniPopes 2023-02-27 21:14:36 +01:00 committed by GitHub
parent cc3156db7a
commit 1861b98896
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 166 additions and 74 deletions

View File

@ -67,7 +67,8 @@ impl Explorer {
let chain = self.chain();
let client = match api_key {
Some(api_key) => Client::new(chain, api_key),
None => Client::new_from_env(chain),
None => Client::new_from_env(chain)
.or_else(|_| Client::builder().chain(chain).and_then(|b| b.build())),
}?;
Ok(client)
}

View File

@ -20,6 +20,11 @@ pub type ParseChainError = TryFromPrimitiveError<Chain>;
// "main" must be present and will be used in `Display`, `Serialize` and `FromStr`,
// while the aliases will be added only to `FromStr`.
// We don't derive Serialize because it is manually implemented using AsRef<str> and it would
// break a lot of things since Serialize is `kebab-case` vs Deserialize `snake_case`.
// This means that the Chain type is not "round-trippable", because the Serialize and Deserialize
// implementations do not use the same case style.
/// An Ethereum EIP-155 chain.
#[derive(
Clone,
@ -30,11 +35,11 @@ pub type ParseChainError = TryFromPrimitiveError<Chain>;
PartialOrd,
Ord,
Hash,
AsRefStr, // also for fmt::Display and serde::Serialize
EnumVariantNames, // Self::VARIANTS
AsRefStr, // AsRef<str>, fmt::Display and serde::Serialize
EnumVariantNames, // Chain::VARIANTS
EnumString, // FromStr, TryFrom<&str>
EnumIter,
EnumCount,
EnumIter, // Chain::iter
EnumCount, // Chain::COUNT
TryFromPrimitive, // TryFrom<u64>
Deserialize,
)]
@ -212,12 +217,25 @@ impl Chain {
/// **Note:** this is not an accurate average, but is rather a sensible default derived from
/// blocktime charts such as [Etherscan's](https://etherscan.com/chart/blocktime)
/// or [Polygonscan's](https://polygonscan.com/chart/blocktime).
///
/// # Examples
///
/// ```
/// use ethers_core::types::Chain;
/// use std::time::Duration;
///
/// assert_eq!(
/// Chain::Mainnet.average_blocktime_hint(),
/// Some(Duration::from_millis(12_000)),
/// );
/// assert_eq!(Chain::Optimism.average_blocktime_hint(), None);
/// ```
pub const fn average_blocktime_hint(&self) -> Option<Duration> {
use Chain::*;
let ms = match self {
Mainnet => 12_000,
Arbitrum | ArbitrumTestnet | ArbitrumGoerli | ArbitrumNova => 1_300,
Mainnet | Optimism => 13_000,
Polygon | PolygonMumbai => 2_100,
Moonbeam | Moonriver => 12_500,
BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
@ -230,20 +248,82 @@ impl Chain {
Emerald => 6_000,
Dev | AnvilHardhat => 200,
Celo | CeloAlfajores | CeloBaklava => 5_000,
// Explictly handle all network to make it easier not to forget this match when new
// networks are added.
// Explicitly exhaustive. See NB above.
Morden | Ropsten | Rinkeby | Goerli | Kovan | XDai | Chiado | Sepolia | Moonbase |
MoonbeamDev | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk | EmeraldTestnet => {
return None
}
MoonbeamDev | Optimism | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk |
EmeraldTestnet => return None,
};
Some(Duration::from_millis(ms))
}
/// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
///
/// # Examples
///
/// ```
/// use ethers_core::types::Chain;
///
/// assert!(!Chain::Mainnet.is_legacy());
/// assert!(Chain::Celo.is_legacy());
/// ```
#[allow(clippy::match_like_matches_macro)]
pub const fn is_legacy(&self) -> bool {
use Chain::*;
match self {
// Known legacy chains / non EIP-1559 compliant
Optimism |
OptimismGoerli |
OptimismKovan |
Fantom |
FantomTestnet |
BinanceSmartChain |
BinanceSmartChainTestnet |
Arbitrum |
ArbitrumTestnet |
ArbitrumGoerli |
ArbitrumNova |
Rsk |
Oasis |
Emerald |
EmeraldTestnet |
Celo |
CeloAlfajores |
CeloBaklava => true,
// Known EIP-1559 chains
Mainnet | Goerli | Sepolia | Polygon | PolygonMumbai | Avalanche | AvalancheFuji => {
false
}
// Unknown / not applicable, default to false for backwards compatibility
Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
}
}
/// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
///
/// Returns `(API URL, BASE_URL)`, like `("https://api(-chain).etherscan.io/api", "https://etherscan.io")`
/// Returns `(API_URL, BASE_URL)`
///
/// # Examples
///
/// ```
/// use ethers_core::types::Chain;
///
/// assert_eq!(
/// Chain::Mainnet.etherscan_urls(),
/// Some(("https://api.etherscan.io/api", "https://etherscan.io"))
/// );
/// assert_eq!(
/// Chain::Avalanche.etherscan_urls(),
/// Some(("https://api.snowtrace.io/api", "https://snowtrace.io"))
/// );
/// assert_eq!(Chain::AnvilHardhat.etherscan_urls(), None);
/// ```
pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
use Chain::*;
@ -328,51 +408,83 @@ impl Chain {
"https://testnet-explorer.canto.neobase.one/",
"https://testnet-explorer.canto.neobase.one/api",
),
AnvilHardhat | Dev | Morden | MoonbeamDev => {
// this is explicitly exhaustive so we don't forget to add new urls when adding a
// new chain
return None
}
// Explicitly exhaustive. See NB above.
AnvilHardhat | Dev | Morden | MoonbeamDev => return None,
};
Some(urls)
}
/// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
pub const fn is_legacy(&self) -> bool {
/// Returns the chain's blockchain explorer's API key environment variable's default name.
///
/// # Examples
///
/// ```
/// use ethers_core::types::Chain;
///
/// assert_eq!(Chain::Mainnet.etherscan_api_key_name(), Some("ETHERSCAN_API_KEY"));
/// assert_eq!(Chain::AnvilHardhat.etherscan_api_key_name(), None);
/// ```
pub const fn etherscan_api_key_name(&self) -> Option<&'static str> {
use Chain::*;
match self {
// Known legacy chains / non EIP-1559 compliant
let api_key_name = match self {
Mainnet |
Morden |
Ropsten |
Kovan |
Rinkeby |
Goerli |
Optimism |
OptimismGoerli |
OptimismKovan |
Fantom |
FantomTestnet |
BinanceSmartChain |
BinanceSmartChainTestnet |
Arbitrum |
ArbitrumTestnet |
ArbitrumGoerli |
ArbitrumNova |
Rsk |
Oasis |
Emerald |
EmeraldTestnet |
Cronos |
CronosTestnet |
Aurora |
AuroraTestnet |
Celo |
CeloAlfajores |
CeloBaklava => true,
CeloBaklava => "ETHERSCAN_API_KEY",
// Known EIP-1559 chains
Mainnet | Goerli | Sepolia | Polygon | PolygonMumbai | Avalanche | AvalancheFuji => {
false
}
Avalanche | AvalancheFuji => "SNOWTRACE_API_KEY",
// Unknown / not applicable, default to false for backwards compatibility
Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
}
Polygon | PolygonMumbai => "POLYGONSCAN_API_KEY",
Fantom | FantomTestnet => "FTMSCAN_API_KEY",
Moonbeam | Moonbase | MoonbeamDev | Moonriver => "MOONSCAN_API_KEY",
Canto | CantoTestnet => "BLOCKSCOUT_API_KEY",
// Explicitly exhaustive. See NB above.
XDai | Chiado | Sepolia | Rsk | Sokol | Poa | Oasis | Emerald | EmeraldTestnet |
Evmos | EvmosTestnet | AnvilHardhat | Dev => return None,
};
Some(api_key_name)
}
/// Returns the chain's blockchain explorer's API key, from the environment variable with the
/// name specified in [`etherscan_api_key_name`](Chain::etherscan_api_key_name).
///
/// # Examples
///
/// ```
/// use ethers_core::types::Chain;
///
/// let chain = Chain::Mainnet;
/// std::env::set_var(chain.etherscan_api_key_name().unwrap(), "KEY");
/// assert_eq!(chain.etherscan_api_key().as_deref(), Some("KEY"));
/// ```
pub fn etherscan_api_key(&self) -> Option<String> {
self.etherscan_api_key_name().and_then(|name| std::env::var(name).ok())
}
}

View File

@ -46,6 +46,7 @@ pub struct Client {
impl Client {
/// Creates a `ClientBuilder` to configure a `Client`.
///
/// This is the same as `ClientBuilder::default()`.
///
/// # Example
@ -77,36 +78,15 @@ impl Client {
}
/// Create a new client with the correct endpoints based on the chain and API key
/// from ETHERSCAN_API_KEY environment variable
/// from the default environment variable defined in [`Chain`].
pub fn new_from_env(chain: Chain) -> Result<Self> {
let api_key = match chain {
Chain::Avalanche | Chain::AvalancheFuji => std::env::var("SNOWTRACE_API_KEY")?,
Chain::Polygon | Chain::PolygonMumbai => std::env::var("POLYGONSCAN_API_KEY")?,
Chain::Mainnet |
Chain::Morden |
Chain::Ropsten |
Chain::Kovan |
Chain::Rinkeby |
Chain::Goerli |
Chain::Optimism |
Chain::OptimismGoerli |
Chain::OptimismKovan |
Chain::BinanceSmartChain |
Chain::BinanceSmartChainTestnet |
Chain::Arbitrum |
Chain::ArbitrumTestnet |
Chain::ArbitrumGoerli |
Chain::ArbitrumNova |
Chain::Cronos |
Chain::CronosTestnet |
Chain::Aurora |
Chain::AuroraTestnet |
Chain::Celo |
Chain::CeloAlfajores |
Chain::CeloBaklava => std::env::var("ETHERSCAN_API_KEY")?,
Chain::Fantom | Chain::FantomTestnet => {
std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY"))?
}
// Extra aliases
Chain::Fantom | Chain::FantomTestnet => std::env::var("FMTSCAN_API_KEY")
.or_else(|_| std::env::var("FANTOMSCAN_API_KEY"))
.map_err(Into::into),
// Backwards compatibility, ideally these should return an error.
Chain::XDai |
Chain::Chiado |
Chain::Sepolia |
@ -117,15 +97,14 @@ impl Client {
Chain::Emerald |
Chain::EmeraldTestnet |
Chain::Evmos |
Chain::EvmosTestnet => String::default(),
Chain::Moonbeam | Chain::Moonbase | Chain::MoonbeamDev | Chain::Moonriver => {
std::env::var("MOONSCAN_API_KEY")?
}
Chain::Canto | Chain::CantoTestnet => std::env::var("BLOCKSCOUT_API_KEY")?,
Chain::AnvilHardhat | Chain::Dev => {
return Err(EtherscanError::LocalNetworksNotSupported)
}
};
Chain::EvmosTestnet => Ok(String::new()),
Chain::AnvilHardhat | Chain::Dev => Err(EtherscanError::LocalNetworksNotSupported),
_ => chain
.etherscan_api_key_name()
.ok_or_else(|| EtherscanError::ChainNotSupported(chain))
.and_then(|key_name| std::env::var(key_name).map_err(Into::into)),
}?;
Self::new(chain, api_key)
}