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 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown / not applicable, default to false for backwards compatibility
|
Polygon | PolygonMumbai => "POLYGONSCAN_API_KEY",
|
||||||
Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan |
|
|
||||||
Sokol | Poa | XDai | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos |
|
Fantom | FantomTestnet => "FTMSCAN_API_KEY",
|
||||||
EvmosTestnet | Chiado | Aurora | AuroraTestnet | Canto | CantoTestnet => false,
|
|
||||||
}
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue