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:
parent
cc3156db7a
commit
1861b98896
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue