use alloy_primitives::{Address, U256};
use serde::{Deserialize, Serialize};
pub type AddressPerChain = Vec<(super::chain::SupportedChainId, Address)>;
pub type ApiBaseUrls = Vec<(super::chain::SupportedChainId, String)>;
use super::chain::SupportedChainId;
pub const RAW_FILES_PATH: &str = "https://files.cow.fi/cow-sdk";
pub const RAW_CHAINS_FILES_PATH: &str = "https://files.cow.fi/cow-sdk/chains";
pub const TOKEN_LIST_IMAGES_PATH: &str = "https://files.cow.fi/token-lists/images";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u64)]
pub enum EvmChains {
Mainnet = 1,
Optimism = 10,
Bnb = 56,
GnosisChain = 100,
Polygon = 137,
Base = 8_453,
Plasma = 9_745,
ArbitrumOne = 42_161,
Avalanche = 43_114,
Ink = 57_073,
Linea = 59_144,
Sepolia = 11_155_111,
}
impl EvmChains {
#[must_use]
pub const fn as_u64(self) -> u64 {
self as u64
}
#[must_use]
pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
match chain_id {
1 => Some(Self::Mainnet),
10 => Some(Self::Optimism),
56 => Some(Self::Bnb),
100 => Some(Self::GnosisChain),
137 => Some(Self::Polygon),
8_453 => Some(Self::Base),
9_745 => Some(Self::Plasma),
42_161 => Some(Self::ArbitrumOne),
43_114 => Some(Self::Avalanche),
57_073 => Some(Self::Ink),
59_144 => Some(Self::Linea),
11_155_111 => Some(Self::Sepolia),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u64)]
pub enum NonEvmChains {
Bitcoin = 1_000_000_000,
Solana = 1_000_000_001,
}
impl NonEvmChains {
#[must_use]
pub const fn as_u64(self) -> u64 {
self as u64
}
#[must_use]
pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
match chain_id {
1_000_000_000 => Some(Self::Bitcoin),
1_000_000_001 => Some(Self::Solana),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u64)]
pub enum AdditionalTargetChainId {
Optimism = 10,
Bitcoin = 1_000_000_000,
Solana = 1_000_000_001,
}
impl AdditionalTargetChainId {
#[must_use]
pub const fn as_u64(self) -> u64 {
self as u64
}
#[must_use]
pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
match chain_id {
10 => Some(Self::Optimism),
1_000_000_000 => Some(Self::Bitcoin),
1_000_000_001 => Some(Self::Solana),
_ => None,
}
}
#[must_use]
pub const fn all() -> &'static [Self] {
&[Self::Optimism, Self::Bitcoin, Self::Solana]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TargetChainId {
Supported(SupportedChainId),
Additional(AdditionalTargetChainId),
}
impl TargetChainId {
#[must_use]
pub const fn as_u64(&self) -> u64 {
match self {
Self::Supported(c) => c.as_u64(),
Self::Additional(c) => c.as_u64(),
}
}
}
#[derive(Debug, Clone)]
pub struct ThemedImage {
pub light: &'static str,
pub dark: &'static str,
}
#[derive(Debug, Clone)]
pub struct WebUrl {
pub name: &'static str,
pub url: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct ChainContract {
pub address: Address,
pub block_created: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct ChainContracts {
pub multicall3: Option<ChainContract>,
pub ens_registry: Option<ChainContract>,
pub ens_universal_resolver: Option<ChainContract>,
}
#[derive(Debug, Clone)]
pub struct ChainRpcUrls {
pub http: &'static [&'static str],
pub web_socket: Option<&'static [&'static str]>,
}
#[derive(Debug, Clone)]
pub struct ChainTokenInfo {
pub chain_id: u64,
pub address: &'static str,
pub decimals: u8,
pub name: &'static str,
pub symbol: &'static str,
pub logo_url: Option<&'static str>,
}
#[derive(Debug, Clone)]
pub struct EvmChainInfo {
pub id: u64,
pub label: &'static str,
pub eip155_label: &'static str,
pub address_prefix: &'static str,
pub native_currency: ChainTokenInfo,
pub is_testnet: bool,
pub color: &'static str,
pub logo: ThemedImage,
pub website: WebUrl,
pub docs: WebUrl,
pub block_explorer: WebUrl,
pub bridges: &'static [WebUrl],
pub contracts: ChainContracts,
pub rpc_urls: ChainRpcUrls,
pub is_zk_sync: bool,
pub is_under_development: bool,
pub is_deprecated: bool,
}
#[derive(Debug, Clone)]
pub struct NonEvmChainInfo {
pub id: u64,
pub label: &'static str,
pub address_prefix: &'static str,
pub native_currency: ChainTokenInfo,
pub is_testnet: bool,
pub color: &'static str,
pub logo: ThemedImage,
pub website: WebUrl,
pub docs: WebUrl,
pub block_explorer: WebUrl,
pub is_under_development: bool,
pub is_deprecated: bool,
}
#[derive(Debug, Clone)]
pub enum ChainInfo {
Evm(EvmChainInfo),
NonEvm(NonEvmChainInfo),
}
impl ChainInfo {
#[must_use]
pub const fn id(&self) -> u64 {
match self {
Self::Evm(info) => info.id,
Self::NonEvm(info) => info.id,
}
}
#[must_use]
pub const fn label(&self) -> &'static str {
match self {
Self::Evm(info) => info.label,
Self::NonEvm(info) => info.label,
}
}
#[must_use]
pub const fn is_under_development(&self) -> bool {
match self {
Self::Evm(info) => info.is_under_development,
Self::NonEvm(info) => info.is_under_development,
}
}
#[must_use]
pub const fn is_deprecated(&self) -> bool {
match self {
Self::Evm(info) => info.is_deprecated,
Self::NonEvm(info) => info.is_deprecated,
}
}
#[must_use]
pub const fn is_evm(&self) -> bool {
matches!(self, Self::Evm(_))
}
#[must_use]
pub const fn is_non_evm(&self) -> bool {
matches!(self, Self::NonEvm(_))
}
#[must_use]
pub const fn as_evm(&self) -> Option<&EvmChainInfo> {
match self {
Self::Evm(info) => Some(info),
Self::NonEvm(_) => None,
}
}
#[must_use]
pub const fn as_non_evm(&self) -> Option<&NonEvmChainInfo> {
match self {
Self::NonEvm(info) => Some(info),
Self::Evm(_) => None,
}
}
#[must_use]
pub const fn native_currency(&self) -> &ChainTokenInfo {
match self {
Self::Evm(info) => &info.native_currency,
Self::NonEvm(info) => &info.native_currency,
}
}
}
#[must_use]
pub const fn is_evm_chain(chain_id: u64) -> bool {
EvmChains::try_from_u64(chain_id).is_some()
}
#[must_use]
pub const fn is_non_evm_chain(chain_id: u64) -> bool {
NonEvmChains::try_from_u64(chain_id).is_some()
}
#[must_use]
pub const fn is_evm_chain_info(chain_info: &ChainInfo) -> bool {
chain_info.is_evm()
}
#[must_use]
pub const fn is_non_evm_chain_info(chain_info: &ChainInfo) -> bool {
chain_info.is_non_evm()
}
#[must_use]
pub const fn is_btc_chain(chain_id: u64) -> bool {
chain_id == NonEvmChains::Bitcoin as u64
}
#[must_use]
pub const fn is_supported_chain(chain_id: u64) -> bool {
SupportedChainId::try_from_u64(chain_id).is_some()
}
#[must_use]
pub const fn is_additional_target_chain(chain_id: u64) -> bool {
AdditionalTargetChainId::try_from_u64(chain_id).is_some()
}
#[must_use]
pub const fn is_target_chain_id(chain_id: u64) -> bool {
is_supported_chain(chain_id) || is_additional_target_chain(chain_id)
}
#[must_use]
pub fn is_zk_sync_chain(chain_id: u64) -> bool {
if !is_evm_chain(chain_id) {
return false;
}
get_chain_info(chain_id)
.and_then(|info| info.as_evm().cloned())
.is_some_and(|evm| evm.is_zk_sync)
}
#[must_use]
pub fn is_chain_under_development(chain_id: u64) -> bool {
get_chain_info(chain_id).is_some_and(|info| info.is_under_development())
}
#[must_use]
pub fn is_chain_deprecated(chain_id: u64) -> bool {
get_chain_info(chain_id).is_some_and(|info| info.is_deprecated())
}
#[must_use]
pub const fn get_chain_info(chain_id: u64) -> Option<ChainInfo> {
if let Some(supported) = SupportedChainId::try_from_u64(chain_id) {
return Some(supported_chain_info(supported));
}
if let Some(additional) = AdditionalTargetChainId::try_from_u64(chain_id) {
return Some(additional_target_chain_info(additional));
}
None
}
#[must_use]
pub fn map_supported_networks<T>(f: impl Fn(SupportedChainId) -> T) -> Vec<(SupportedChainId, T)> {
SupportedChainId::all().iter().map(|&chain| (chain, f(chain))).collect()
}
#[must_use]
pub fn map_all_networks<T>(f: impl Fn(TargetChainId) -> T) -> Vec<(TargetChainId, T)> {
all_chain_ids().into_iter().map(|id| (id, f(id))).collect()
}
#[must_use]
pub fn map_address_to_supported_networks(address: Address) -> Vec<(SupportedChainId, Address)> {
map_supported_networks(|_| address)
}
#[must_use]
pub fn all_supported_chain_ids() -> Vec<SupportedChainId> {
SupportedChainId::all().to_vec()
}
#[must_use]
pub fn all_supported_chains() -> Vec<ChainInfo> {
SupportedChainId::all().iter().map(|&c| supported_chain_info(c)).collect()
}
#[must_use]
pub fn tradable_supported_chain_ids() -> Vec<SupportedChainId> {
SupportedChainId::all()
.iter()
.copied()
.filter(|&c| !supported_chain_info(c).is_deprecated())
.collect()
}
#[must_use]
pub fn tradable_supported_chains() -> Vec<ChainInfo> {
SupportedChainId::all()
.iter()
.copied()
.filter(|&c| !supported_chain_info(c).is_deprecated())
.map(supported_chain_info)
.collect()
}
#[must_use]
pub fn all_additional_target_chains() -> Vec<ChainInfo> {
AdditionalTargetChainId::all().iter().map(|&c| additional_target_chain_info(c)).collect()
}
#[must_use]
pub fn all_additional_target_chain_ids() -> Vec<AdditionalTargetChainId> {
AdditionalTargetChainId::all().to_vec()
}
#[must_use]
pub fn all_chains() -> Vec<ChainInfo> {
let mut chains = all_supported_chains();
chains.extend(all_additional_target_chains());
chains
}
#[must_use]
pub fn all_chain_ids() -> Vec<TargetChainId> {
let mut ids: Vec<TargetChainId> =
SupportedChainId::all().iter().map(|&c| TargetChainId::Supported(c)).collect();
ids.extend(AdditionalTargetChainId::all().iter().map(|&c| TargetChainId::Additional(c)));
ids
}
#[must_use]
pub const fn supported_chain_info(chain: SupportedChainId) -> ChainInfo {
ChainInfo::Evm(evm_chain_detail(chain))
}
#[must_use]
pub const fn additional_target_chain_info(chain: AdditionalTargetChainId) -> ChainInfo {
match chain {
AdditionalTargetChainId::Optimism => ChainInfo::Evm(optimism_chain_info()),
AdditionalTargetChainId::Bitcoin => ChainInfo::NonEvm(bitcoin_chain_info()),
AdditionalTargetChainId::Solana => ChainInfo::NonEvm(solana_chain_info()),
}
}
const fn evm_chain_detail(chain: SupportedChainId) -> EvmChainInfo {
match chain {
SupportedChainId::Mainnet => mainnet_chain_info(),
SupportedChainId::GnosisChain => gnosis_chain_info(),
SupportedChainId::ArbitrumOne => arbitrum_chain_info(),
SupportedChainId::Base => base_chain_info(),
SupportedChainId::Sepolia => sepolia_chain_info(),
SupportedChainId::Polygon => polygon_chain_info(),
SupportedChainId::Avalanche => avalanche_chain_info(),
SupportedChainId::BnbChain => bnb_chain_info(),
SupportedChainId::Linea => linea_chain_info(),
SupportedChainId::Lens => lens_chain_info(),
SupportedChainId::Plasma => plasma_chain_info(),
SupportedChainId::Ink => ink_chain_info(),
}
}
const EVM_NATIVE_ADDR: &str = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const BTC_ADDR: &str = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
const SOL_ADDR: &str = "11111111111111111111111111111111";
const MULTICALL3: Address = Address::new([
0xca, 0x11, 0xbd, 0xe0, 0x59, 0x77, 0xb3, 0x63, 0x11, 0x67, 0x02, 0x88, 0x62, 0xbe, 0x2a, 0x17,
0x39, 0x76, 0xca, 0x11,
]);
const fn default_native_currency(chain_id: u64) -> ChainTokenInfo {
ChainTokenInfo {
chain_id,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "Ether",
symbol: "ETH",
logo_url: Some(
"https://files.cow.fi/token-lists/images/1/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/logo.png",
),
}
}
const fn no_contracts() -> ChainContracts {
ChainContracts { multicall3: None, ens_registry: None, ens_universal_resolver: None }
}
const fn multicall3_only(block_created: u64) -> ChainContracts {
ChainContracts {
multicall3: Some(ChainContract { address: MULTICALL3, block_created: Some(block_created) }),
ens_registry: None,
ens_universal_resolver: None,
}
}
const fn mainnet_chain_info() -> EvmChainInfo {
EvmChainInfo {
id: 1,
label: "Ethereum",
eip155_label: "Ethereum Mainnet",
address_prefix: "eth",
native_currency: default_native_currency(1),
is_testnet: false,
color: "#62688F",
logo: ThemedImage {
light: "https://files.cow.fi/cow-sdk/chains/images/mainnet-logo.svg",
dark: "https://files.cow.fi/cow-sdk/chains/images/mainnet-logo.svg",
},
website: WebUrl { name: "Ethereum", url: "https://ethereum.org" },
docs: WebUrl { name: "Ethereum Docs", url: "https://ethereum.org/en/developers/docs" },
block_explorer: WebUrl { name: "Etherscan", url: "https://etherscan.io" },
bridges: &[],
contracts: ChainContracts {
multicall3: Some(ChainContract {
address: MULTICALL3,
block_created: Some(14_353_601),
}),
ens_registry: Some(ChainContract {
address: Address::new([
0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x07, 0x4e, 0xc6, 0x9a, 0x0d, 0xfb,
0x29, 0x97, 0xba, 0x6c, 0x7d, 0x2e, 0x1e,
]),
block_created: None,
}),
ens_universal_resolver: Some(ChainContract {
address: Address::new([
0xce, 0x01, 0xf8, 0xee, 0xe7, 0xe4, 0x79, 0xc9, 0x28, 0xf8, 0x91, 0x9a, 0xbd,
0x53, 0xe5, 0x53, 0xa3, 0x6c, 0xef, 0x67,
]),
block_created: Some(19_258_213),
}),
},
rpc_urls: ChainRpcUrls { http: &["https://eth.merkle.io"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn gnosis_chain_info() -> EvmChainInfo {
EvmChainInfo {
id: 100,
label: "Gnosis",
eip155_label: "Gnosis",
address_prefix: "gno",
native_currency: ChainTokenInfo {
chain_id: 100,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "xDAI",
symbol: "xDAI",
logo_url: Some(
"https://files.cow.fi/token-lists/images/100/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/logo.png",
),
},
is_testnet: false,
color: "#07795B",
logo: ThemedImage {
light: "https://files.cow.fi/cow-sdk/chains/images/gnosis-logo.svg",
dark: "https://files.cow.fi/cow-sdk/chains/images/gnosis-logo.svg",
},
website: WebUrl { name: "Gnosis Chain", url: "https://www.gnosischain.com" },
docs: WebUrl { name: "Gnosis Chain Docs", url: "https://docs.gnosischain.com" },
block_explorer: WebUrl { name: "Gnosisscan", url: "https://gnosisscan.io" },
bridges: &[WebUrl { name: "Gnosis Chain Bridge", url: "https://bridge.gnosischain.com" }],
contracts: multicall3_only(21_022_491),
rpc_urls: ChainRpcUrls {
http: &["https://rpc.gnosischain.com"],
web_socket: Some(&["wss://rpc.gnosischain.com/wss"]),
},
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn arbitrum_chain_info() -> EvmChainInfo {
EvmChainInfo {
id: 42_161,
label: "Arbitrum",
eip155_label: "Arbitrum One",
address_prefix: "arb1",
native_currency: default_native_currency(42_161),
is_testnet: false,
color: "#1B4ADD",
logo: ThemedImage {
light: "https://files.cow.fi/cow-sdk/chains/images/arbitrum-logo.svg",
dark: "https://files.cow.fi/cow-sdk/chains/images/arbitrum-logo.svg",
},
website: WebUrl { name: "Arbitrum", url: "https://arbitrum.io" },
docs: WebUrl { name: "Arbitrum Docs", url: "https://docs.arbitrum.io" },
block_explorer: WebUrl { name: "Arbiscan", url: "https://arbiscan.io" },
bridges: &[WebUrl { name: "Arbitrum Bridge", url: "https://bridge.arbitrum.io" }],
contracts: multicall3_only(7_654_707),
rpc_urls: ChainRpcUrls { http: &["https://arb1.arbitrum.io/rpc"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn base_chain_info() -> EvmChainInfo {
EvmChainInfo {
id: 8_453,
label: "Base",
eip155_label: "Base",
address_prefix: "base",
native_currency: default_native_currency(8_453),
is_testnet: false,
color: "#0052FF",
logo: ThemedImage {
light: "https://files.cow.fi/cow-sdk/chains/images/base-logo.svg",
dark: "https://files.cow.fi/cow-sdk/chains/images/base-logo.svg",
},
website: WebUrl { name: "Base", url: "https://base.org" },
docs: WebUrl { name: "Base Docs", url: "https://docs.base.org" },
block_explorer: WebUrl { name: "Basescan", url: "https://basescan.org" },
bridges: &[WebUrl { name: "Superchain Bridges", url: "https://bridge.base.org/deposit" }],
contracts: multicall3_only(5022),
rpc_urls: ChainRpcUrls { http: &["https://mainnet.base.org"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn sepolia_chain_info() -> EvmChainInfo {
EvmChainInfo {
id: 11_155_111,
label: "Sepolia",
eip155_label: "Ethereum Sepolia",
address_prefix: "sep",
native_currency: default_native_currency(11_155_111),
is_testnet: true,
color: "#C12FF2",
logo: ThemedImage {
light: "https://files.cow.fi/cow-sdk/chains/images/sepolia-logo.svg",
dark: "https://files.cow.fi/cow-sdk/chains/images/sepolia-logo.svg",
},
website: WebUrl { name: "Ethereum", url: "https://sepolia.dev" },
docs: WebUrl {
name: "Sepolia Docs",
url: "https://ethereum.org/en/developers/docs/networks/#sepolia",
},
block_explorer: WebUrl { name: "Etherscan", url: "https://sepolia.etherscan.io" },
bridges: &[],
contracts: ChainContracts {
multicall3: Some(ChainContract { address: MULTICALL3, block_created: Some(751_532) }),
ens_registry: Some(ChainContract {
address: Address::new([
0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x07, 0x4e, 0xc6, 0x9a, 0x0d, 0xfb,
0x29, 0x97, 0xba, 0x6c, 0x7d, 0x2e, 0x1e,
]),
block_created: None,
}),
ens_universal_resolver: Some(ChainContract {
address: Address::new([
0xc8, 0xaf, 0x99, 0x9e, 0x38, 0x27, 0x3d, 0x65, 0x8b, 0xe1, 0xb9, 0x21, 0xb8,
0x8a, 0x9d, 0xdf, 0x00, 0x57, 0x69, 0xcc,
]),
block_created: Some(5_317_080),
}),
},
rpc_urls: ChainRpcUrls { http: &["https://sepolia.drpc.org"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn polygon_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/polygon-logo.svg";
EvmChainInfo {
id: 137,
label: "Polygon",
eip155_label: "Polygon Mainnet",
address_prefix: "matic",
native_currency: ChainTokenInfo {
chain_id: 137,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "POL",
symbol: "POL",
logo_url: Some(logo_url),
},
is_testnet: false,
color: "#8247e5",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Polygon", url: "https://polygon.technology" },
docs: WebUrl { name: "Polygon Docs", url: "https://docs.polygon.technology" },
block_explorer: WebUrl { name: "Polygonscan", url: "https://polygonscan.com" },
bridges: &[],
contracts: multicall3_only(25_770_160),
rpc_urls: ChainRpcUrls { http: &["https://polygon-rpc.com"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn avalanche_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/avax-logo.svg";
EvmChainInfo {
id: 43_114,
label: "Avalanche",
eip155_label: "Avalanche C-Chain",
address_prefix: "avax",
native_currency: ChainTokenInfo {
chain_id: 43_114,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "Avalanche",
symbol: "AVAX",
logo_url: Some(logo_url),
},
is_testnet: false,
color: "#ff3944",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Avalanche", url: "https://www.avax.network/" },
docs: WebUrl { name: "Avalanche Docs", url: "https://build.avax.network/docs" },
block_explorer: WebUrl { name: "Snowscan", url: "https://snowscan.xyz" },
bridges: &[],
contracts: multicall3_only(11_907_934),
rpc_urls: ChainRpcUrls {
http: &["https://api.avax.network/ext/bc/C/rpc"],
web_socket: None,
},
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn bnb_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/bnb-logo.svg";
EvmChainInfo {
id: 56,
label: "BNB",
eip155_label: "BNB Chain Mainnet",
address_prefix: "bnb",
native_currency: ChainTokenInfo {
chain_id: 56,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "BNB Chain Native Token",
symbol: "BNB",
logo_url: Some(logo_url),
},
is_testnet: false,
color: "#F0B90B",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "BNB Chain", url: "https://www.bnbchain.org" },
docs: WebUrl { name: "BNB Chain Docs", url: "https://docs.bnbchain.org" },
block_explorer: WebUrl { name: "Bscscan", url: "https://bscscan.com" },
bridges: &[WebUrl {
name: "BNB Chain Cross-Chain Bridge",
url: "https://www.bnbchain.org/en/bnb-chain-bridge",
}],
contracts: multicall3_only(15_921_452),
rpc_urls: ChainRpcUrls { http: &["https://bsc-dataseed1.bnbchain.org"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn linea_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/linea-logo.svg";
EvmChainInfo {
id: 59_144,
label: "Linea",
eip155_label: "Linea Mainnet",
address_prefix: "linea",
native_currency: default_native_currency(59_144),
is_testnet: false,
color: "#61dfff",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Linea", url: "https://linea.build" },
docs: WebUrl { name: "Linea Docs", url: "https://docs.linea.build" },
block_explorer: WebUrl { name: "Lineascan", url: "https://lineascan.build" },
bridges: &[WebUrl { name: "Linea Bridge", url: "https://linea.build/hub/bridge" }],
contracts: multicall3_only(42),
rpc_urls: ChainRpcUrls { http: &["https://rpc.linea.build"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn lens_chain_info() -> EvmChainInfo {
EvmChainInfo {
id: 232,
label: "Lens",
eip155_label: "Lens Network",
address_prefix: "lens",
native_currency: ChainTokenInfo {
chain_id: 232,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "GHO",
symbol: "GHO",
logo_url: None,
},
is_testnet: false,
color: "#00501e",
logo: ThemedImage {
light: "https://files.cow.fi/cow-sdk/chains/images/lens-logo.svg",
dark: "https://files.cow.fi/cow-sdk/chains/images/lens-logo.svg",
},
website: WebUrl { name: "Lens", url: "https://lens.xyz" },
docs: WebUrl { name: "Lens Docs", url: "https://docs.lens.xyz" },
block_explorer: WebUrl { name: "Lens Explorer", url: "https://explorer.lens.xyz" },
bridges: &[],
contracts: no_contracts(),
rpc_urls: ChainRpcUrls { http: &["https://rpc.lens.xyz"], web_socket: None },
is_zk_sync: true,
is_under_development: false,
is_deprecated: false,
}
}
const fn plasma_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/plasma-logo.svg";
EvmChainInfo {
id: 9_745,
label: "Plasma",
eip155_label: "Plasma Mainnet",
address_prefix: "plasma",
native_currency: ChainTokenInfo {
chain_id: 9_745,
address: EVM_NATIVE_ADDR,
decimals: 18,
name: "Plasma",
symbol: "XPL",
logo_url: Some(logo_url),
},
is_testnet: false,
color: "#569F8C",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Plasma", url: "https://www.plasma.to" },
docs: WebUrl { name: "Plasma Docs", url: "https://docs.plasma.to" },
block_explorer: WebUrl { name: "Plasmascan", url: "https://plasmascan.to" },
bridges: &[],
contracts: multicall3_only(0),
rpc_urls: ChainRpcUrls { http: &["https://rpc.plasma.to"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn ink_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/ink-logo.svg";
EvmChainInfo {
id: 57_073,
label: "Ink",
eip155_label: "Ink Chain Mainnet",
address_prefix: "ink",
native_currency: default_native_currency(57_073),
is_testnet: false,
color: "#7132f5",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Ink", url: "https://inkonchain.com/" },
docs: WebUrl { name: "Ink Docs", url: "https://docs.inkonchain.com" },
block_explorer: WebUrl { name: "Ink Explorer", url: "https://explorer.inkonchain.com" },
bridges: &[WebUrl { name: "Ink Bridge", url: "https://inkonchain.com/bridge" }],
contracts: multicall3_only(0),
rpc_urls: ChainRpcUrls { http: &["https://rpc-ten.inkonchain.com"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn optimism_chain_info() -> EvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/optimism-logo.svg";
EvmChainInfo {
id: 10,
label: "Optimism",
eip155_label: "OP Mainnet",
address_prefix: "op",
native_currency: default_native_currency(10),
is_testnet: false,
color: "#ff0420",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Optimism", url: "https://optimism.io" },
docs: WebUrl { name: "Optimism Docs", url: "https://docs.optimism.io" },
block_explorer: WebUrl { name: "Etherscan", url: "https://optimistic.etherscan.io" },
bridges: &[],
contracts: multicall3_only(4_286_263),
rpc_urls: ChainRpcUrls { http: &["https://mainnet.optimism.io"], web_socket: None },
is_zk_sync: false,
is_under_development: false,
is_deprecated: false,
}
}
const fn bitcoin_chain_info() -> NonEvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/bitcoin-logo.svg";
NonEvmChainInfo {
id: 1_000_000_000,
label: "Bitcoin",
address_prefix: "btc",
native_currency: ChainTokenInfo {
chain_id: 1_000_000_000,
address: BTC_ADDR,
decimals: 8,
name: "Bitcoin",
symbol: "BTC",
logo_url: Some(logo_url),
},
is_testnet: false,
color: "#f7931a",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Bitcoin", url: "https://bitcoin.org" },
docs: WebUrl {
name: "Bitcoin Docs",
url: "https://bitcoin.org/en/developer-documentation",
},
block_explorer: WebUrl { name: "Blockstream Explorer", url: "https://blockstream.info" },
is_under_development: false,
is_deprecated: false,
}
}
const fn solana_chain_info() -> NonEvmChainInfo {
let logo_url = "https://files.cow.fi/cow-sdk/chains/images/solana-logo.svg";
NonEvmChainInfo {
id: 1_000_000_001,
label: "Solana",
address_prefix: "sol",
native_currency: ChainTokenInfo {
chain_id: 1_000_000_001,
address: SOL_ADDR,
decimals: 9,
name: "Solana",
symbol: "SOL",
logo_url: Some(logo_url),
},
is_testnet: false,
color: "#9945FF",
logo: ThemedImage { light: logo_url, dark: logo_url },
website: WebUrl { name: "Solana", url: "https://solana.com" },
docs: WebUrl { name: "Solana Docs", url: "https://docs.solana.com" },
block_explorer: WebUrl { name: "Solana Explorer", url: "https://explorer.solana.com" },
is_under_development: false,
is_deprecated: false,
}
}
#[derive(Debug, Clone, Default)]
pub struct IpfsConfig {
pub uri: Option<String>,
pub write_uri: Option<String>,
pub read_uri: Option<String>,
pub pinata_api_key: Option<String>,
pub pinata_api_secret: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ApiContext {
pub chain_id: SupportedChainId,
pub env: super::chain::Env,
pub base_urls: Option<ApiBaseUrls>,
pub api_key: Option<String>,
}
impl Default for ApiContext {
fn default() -> Self {
Self {
chain_id: SupportedChainId::Mainnet,
env: super::chain::Env::Prod,
base_urls: None,
api_key: None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ProtocolOptions {
pub env: Option<super::chain::Env>,
pub settlement_contract_override: Option<AddressPerChain>,
pub eth_flow_contract_override: Option<AddressPerChain>,
}
#[derive(Debug, Clone)]
pub struct EvmCall {
pub to: Address,
pub data: Vec<u8>,
pub value: U256,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn evm_chains_roundtrip_u64() {
let chains = [
(EvmChains::Mainnet, 1),
(EvmChains::Optimism, 10),
(EvmChains::Bnb, 56),
(EvmChains::GnosisChain, 100),
(EvmChains::Polygon, 137),
(EvmChains::Base, 8_453),
(EvmChains::Plasma, 9_745),
(EvmChains::ArbitrumOne, 42_161),
(EvmChains::Avalanche, 43_114),
(EvmChains::Ink, 57_073),
(EvmChains::Linea, 59_144),
(EvmChains::Sepolia, 11_155_111),
];
for (chain, id) in chains {
assert_eq!(chain.as_u64(), id);
assert_eq!(EvmChains::try_from_u64(id), Some(chain));
}
}
#[test]
fn evm_chains_unknown_returns_none() {
assert_eq!(EvmChains::try_from_u64(9999), None);
}
#[test]
fn non_evm_chains_roundtrip() {
assert_eq!(NonEvmChains::Bitcoin.as_u64(), 1_000_000_000);
assert_eq!(NonEvmChains::Solana.as_u64(), 1_000_000_001);
assert_eq!(NonEvmChains::try_from_u64(1_000_000_000), Some(NonEvmChains::Bitcoin));
assert_eq!(NonEvmChains::try_from_u64(1_000_000_001), Some(NonEvmChains::Solana));
assert_eq!(NonEvmChains::try_from_u64(999), None);
}
#[test]
fn additional_target_roundtrip() {
for &chain in AdditionalTargetChainId::all() {
let id = chain.as_u64();
assert_eq!(AdditionalTargetChainId::try_from_u64(id), Some(chain));
}
}
#[test]
fn additional_target_all_has_three() {
assert_eq!(AdditionalTargetChainId::all().len(), 3);
}
#[test]
fn target_chain_id_as_u64() {
let supported = TargetChainId::Supported(SupportedChainId::Mainnet);
assert_eq!(supported.as_u64(), 1);
let additional = TargetChainId::Additional(AdditionalTargetChainId::Bitcoin);
assert_eq!(additional.as_u64(), 1_000_000_000);
}
#[test]
fn is_evm_chain_correct() {
assert!(is_evm_chain(1));
assert!(is_evm_chain(10));
assert!(!is_evm_chain(1_000_000_000));
assert!(!is_evm_chain(9999));
}
#[test]
fn is_non_evm_chain_correct() {
assert!(is_non_evm_chain(1_000_000_000));
assert!(is_non_evm_chain(1_000_000_001));
assert!(!is_non_evm_chain(1));
}
#[test]
fn is_btc_chain_correct() {
assert!(is_btc_chain(1_000_000_000));
assert!(!is_btc_chain(1));
}
#[test]
fn is_supported_chain_correct() {
assert!(is_supported_chain(1));
assert!(is_supported_chain(100));
assert!(!is_supported_chain(10));
assert!(!is_supported_chain(9999));
}
#[test]
fn is_additional_target_chain_correct() {
assert!(is_additional_target_chain(10));
assert!(is_additional_target_chain(1_000_000_000));
assert!(!is_additional_target_chain(1));
}
#[test]
fn is_target_chain_id_correct() {
assert!(is_target_chain_id(1));
assert!(is_target_chain_id(10));
assert!(is_target_chain_id(1_000_000_000));
assert!(!is_target_chain_id(9999));
}
#[test]
fn is_zk_sync_chain_is_false_for_all() {
assert!(!is_zk_sync_chain(1));
assert!(!is_zk_sync_chain(100));
}
#[test]
fn get_chain_info_all_supported() {
for &chain in SupportedChainId::all() {
let chain_info = get_chain_info(chain.as_u64());
assert!(chain_info.is_some(), "no chain info for {chain:?}");
let info = chain_info.unwrap_or_else(|| supported_chain_info(chain));
assert_eq!(info.id(), chain.as_u64());
assert!(!info.label().is_empty());
}
}
#[test]
fn get_chain_info_additional_targets() {
for &chain in AdditionalTargetChainId::all() {
let info = get_chain_info(chain.as_u64());
assert!(info.is_some(), "no chain info for {chain:?}");
}
}
#[test]
fn get_chain_info_unknown_returns_none() {
assert!(get_chain_info(9999).is_none());
}
#[test]
fn chain_info_evm_predicates() {
let info = supported_chain_info(SupportedChainId::Mainnet);
assert!(info.is_evm());
assert!(!info.is_non_evm());
assert!(info.as_evm().is_some());
assert!(info.as_non_evm().is_none());
}
#[test]
fn chain_info_non_evm_predicates() {
let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
assert!(!info.is_evm());
assert!(info.is_non_evm());
assert!(info.as_non_evm().is_some());
}
#[test]
fn chain_info_native_currency() {
let info = supported_chain_info(SupportedChainId::Mainnet);
let currency = info.native_currency();
assert_eq!(currency.decimals, 18);
}
#[test]
fn all_supported_chain_ids_matches_all() {
let ids = all_supported_chain_ids();
assert_eq!(ids.len(), SupportedChainId::all().len());
}
#[test]
fn all_supported_chains_matches_all() {
let chains = all_supported_chains();
assert_eq!(chains.len(), SupportedChainId::all().len());
}
#[test]
fn tradable_chains_excludes_deprecated_and_dev() {
let tradable = tradable_supported_chain_ids();
assert!(!tradable.is_empty());
assert!(tradable.len() <= SupportedChainId::all().len());
}
#[test]
fn all_additional_target_chain_ids_has_three() {
assert_eq!(all_additional_target_chain_ids().len(), 3);
}
#[test]
fn all_chains_includes_supported_and_additional() {
let all = all_chains();
assert!(all.len() >= SupportedChainId::all().len());
}
#[test]
fn all_chain_ids_includes_both() {
let ids = all_chain_ids();
assert!(ids.len() >= SupportedChainId::all().len() + AdditionalTargetChainId::all().len());
}
#[test]
fn map_supported_networks_maps_all() {
let mapped = map_supported_networks(|c| c.as_u64());
assert_eq!(mapped.len(), SupportedChainId::all().len());
}
#[test]
fn map_address_to_supported_networks_produces_correct_count() {
let mapped = map_address_to_supported_networks(Address::ZERO);
assert_eq!(mapped.len(), SupportedChainId::all().len());
}
#[test]
fn is_zk_sync_chain_returns_false_for_lens() {
assert!(!is_zk_sync_chain(232));
}
#[test]
fn is_zk_sync_chain_returns_false_for_non_evm() {
assert!(!is_zk_sync_chain(1_000_000_000));
}
#[test]
fn is_chain_under_development_unknown_chain() {
assert!(!is_chain_under_development(9999));
}
#[test]
fn is_chain_deprecated_unknown_chain() {
assert!(!is_chain_deprecated(9999));
}
#[test]
fn chain_info_is_under_development_non_evm() {
let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
assert!(!info.is_under_development());
}
#[test]
fn chain_info_is_deprecated_non_evm() {
let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
assert!(!info.is_deprecated());
}
#[test]
fn evm_chain_info_type_guards() {
let info = supported_chain_info(SupportedChainId::Mainnet);
assert!(is_evm_chain_info(&info));
assert!(!is_non_evm_chain_info(&info));
}
#[test]
fn non_evm_chain_info_type_guards() {
let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
assert!(is_non_evm_chain_info(&info));
assert!(!is_evm_chain_info(&info));
}
#[test]
fn tradable_supported_chains_returns_chain_infos() {
let chains = tradable_supported_chains();
assert!(!chains.is_empty());
for c in &chains {
assert!(!c.is_deprecated());
}
}
#[test]
fn all_additional_target_chains_returns_infos() {
let chains = all_additional_target_chains();
assert_eq!(chains.len(), 3);
}
#[test]
fn map_all_networks_covers_everything() {
let mapped = map_all_networks(|t| t.as_u64());
assert!(
mapped.len() >= SupportedChainId::all().len() + AdditionalTargetChainId::all().len()
);
}
#[test]
fn chain_info_label_non_evm() {
let info = additional_target_chain_info(AdditionalTargetChainId::Solana);
assert_eq!(info.label(), "Solana");
assert_eq!(info.id(), 1_000_000_001);
}
#[test]
fn additional_target_optimism_is_evm() {
let info = additional_target_chain_info(AdditionalTargetChainId::Optimism);
assert!(info.is_evm());
assert_eq!(info.label(), "Optimism");
}
#[test]
fn api_context_default() {
let ctx = ApiContext::default();
assert_eq!(ctx.chain_id, SupportedChainId::Mainnet);
assert!(ctx.base_urls.is_none());
assert!(ctx.api_key.is_none());
}
#[test]
fn additional_target_try_from_u64_unknown() {
assert!(AdditionalTargetChainId::try_from_u64(999).is_none());
}
}