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 chain = self.chain();
let client = match api_key { let client = match api_key {
Some(api_key) => Client::new(chain, 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) 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`, // "main" must be present and will be used in `Display`, `Serialize` and `FromStr`,
// while the aliases will be added only to `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. /// An Ethereum EIP-155 chain.
#[derive( #[derive(
Clone, Clone,
@ -30,11 +35,11 @@ pub type ParseChainError = TryFromPrimitiveError<Chain>;
PartialOrd, PartialOrd,
Ord, Ord,
Hash, Hash,
AsRefStr, // also for fmt::Display and serde::Serialize AsRefStr, // AsRef<str>, fmt::Display and serde::Serialize
EnumVariantNames, // Self::VARIANTS EnumVariantNames, // Chain::VARIANTS
EnumString, // FromStr, TryFrom<&str> EnumString, // FromStr, TryFrom<&str>
EnumIter, EnumIter, // Chain::iter
EnumCount, EnumCount, // Chain::COUNT
TryFromPrimitive, // TryFrom<u64> TryFromPrimitive, // TryFrom<u64>
Deserialize, Deserialize,
)] )]
@ -212,12 +217,25 @@ impl Chain {
/// **Note:** this is not an accurate average, but is rather a sensible default derived from /// **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) /// blocktime charts such as [Etherscan's](https://etherscan.com/chart/blocktime)
/// or [Polygonscan's](https://polygonscan.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> { pub const fn average_blocktime_hint(&self) -> Option<Duration> {
use Chain::*; use Chain::*;
let ms = match self { let ms = match self {
Mainnet => 12_000,
Arbitrum | ArbitrumTestnet | ArbitrumGoerli | ArbitrumNova => 1_300, Arbitrum | ArbitrumTestnet | ArbitrumGoerli | ArbitrumNova => 1_300,
Mainnet | Optimism => 13_000,
Polygon | PolygonMumbai => 2_100, Polygon | PolygonMumbai => 2_100,
Moonbeam | Moonriver => 12_500, Moonbeam | Moonriver => 12_500,
BinanceSmartChain | BinanceSmartChainTestnet => 3_000, BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
@ -230,20 +248,82 @@ impl Chain {
Emerald => 6_000, Emerald => 6_000,
Dev | AnvilHardhat => 200, Dev | AnvilHardhat => 200,
Celo | CeloAlfajores | CeloBaklava => 5_000, 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 | Morden | Ropsten | Rinkeby | Goerli | Kovan | XDai | Chiado | Sepolia | Moonbase |
MoonbeamDev | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk | EmeraldTestnet => { MoonbeamDev | Optimism | OptimismGoerli | OptimismKovan | Poa | Sokol | Rsk |
return None EmeraldTestnet => return None,
}
}; };
Some(Duration::from_millis(ms)) 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 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)> { pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
use Chain::*; use Chain::*;
@ -328,51 +408,83 @@ impl Chain {
"https://testnet-explorer.canto.neobase.one/", "https://testnet-explorer.canto.neobase.one/",
"https://testnet-explorer.canto.neobase.one/api", "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 // Explicitly exhaustive. See NB above.
// new chain AnvilHardhat | Dev | Morden | MoonbeamDev => return None,
return None
}
}; };
Some(urls) Some(urls)
} }
/// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type). /// Returns the chain's blockchain explorer's API key environment variable's default name.
pub const fn is_legacy(&self) -> bool { ///
/// # 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::*; use Chain::*;
match self { let api_key_name = match self {
// Known legacy chains / non EIP-1559 compliant Mainnet |
Morden |
Ropsten |
Kovan |
Rinkeby |
Goerli |
Optimism | Optimism |
OptimismGoerli | OptimismGoerli |
OptimismKovan | OptimismKovan |
Fantom |
FantomTestnet |
BinanceSmartChain | BinanceSmartChain |
BinanceSmartChainTestnet | BinanceSmartChainTestnet |
Arbitrum | Arbitrum |
ArbitrumTestnet | ArbitrumTestnet |
ArbitrumGoerli | ArbitrumGoerli |
ArbitrumNova | ArbitrumNova |
Rsk | Cronos |
Oasis | CronosTestnet |
Emerald | Aurora |
EmeraldTestnet | AuroraTestnet |
Celo | Celo |
CeloAlfajores | CeloAlfajores |
CeloBaklava => true, CeloBaklava => "ETHERSCAN_API_KEY",
// Known EIP-1559 chains Avalanche | AvalancheFuji => "SNOWTRACE_API_KEY",
Mainnet | Goerli | Sepolia | Polygon | PolygonMumbai | Avalanche | AvalancheFuji => {
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)
} }
// Unknown / not applicable, default to false for backwards compatibility /// Returns the chain's blockchain explorer's API key, from the environment variable with the
Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan | /// name specified in [`etherscan_api_key_name`](Chain::etherscan_api_key_name).
Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos | ///
EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false, /// # 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 { impl Client {
/// Creates a `ClientBuilder` to configure a `Client`. /// Creates a `ClientBuilder` to configure a `Client`.
///
/// This is the same as `ClientBuilder::default()`. /// This is the same as `ClientBuilder::default()`.
/// ///
/// # Example /// # Example
@ -77,36 +78,15 @@ impl Client {
} }
/// Create a new client with the correct endpoints based on the chain and API key /// 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> { pub fn new_from_env(chain: Chain) -> Result<Self> {
let api_key = match chain { let api_key = match chain {
Chain::Avalanche | Chain::AvalancheFuji => std::env::var("SNOWTRACE_API_KEY")?, // Extra aliases
Chain::Polygon | Chain::PolygonMumbai => std::env::var("POLYGONSCAN_API_KEY")?, Chain::Fantom | Chain::FantomTestnet => std::env::var("FMTSCAN_API_KEY")
Chain::Mainnet | .or_else(|_| std::env::var("FANTOMSCAN_API_KEY"))
Chain::Morden | .map_err(Into::into),
Chain::Ropsten |
Chain::Kovan | // Backwards compatibility, ideally these should return an error.
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"))?
}
Chain::XDai | Chain::XDai |
Chain::Chiado | Chain::Chiado |
Chain::Sepolia | Chain::Sepolia |
@ -117,15 +97,14 @@ impl Client {
Chain::Emerald | Chain::Emerald |
Chain::EmeraldTestnet | Chain::EmeraldTestnet |
Chain::Evmos | Chain::Evmos |
Chain::EvmosTestnet => String::default(), Chain::EvmosTestnet => Ok(String::new()),
Chain::Moonbeam | Chain::Moonbase | Chain::MoonbeamDev | Chain::Moonriver => { Chain::AnvilHardhat | Chain::Dev => Err(EtherscanError::LocalNetworksNotSupported),
std::env::var("MOONSCAN_API_KEY")?
} _ => chain
Chain::Canto | Chain::CantoTestnet => std::env::var("BLOCKSCOUT_API_KEY")?, .etherscan_api_key_name()
Chain::AnvilHardhat | Chain::Dev => { .ok_or_else(|| EtherscanError::ChainNotSupported(chain))
return Err(EtherscanError::LocalNetworksNotSupported) .and_then(|key_name| std::env::var(key_name).map_err(Into::into)),
} }?;
};
Self::new(chain, api_key) Self::new(chain, api_key)
} }