1use alloy_primitives::{Address, U256};
21use serde::{Deserialize, Serialize};
22
23pub type AddressPerChain = Vec<(super::chain::SupportedChainId, Address)>;
25
26pub type ApiBaseUrls = Vec<(super::chain::SupportedChainId, String)>;
28
29use super::chain::SupportedChainId;
30
31pub const RAW_FILES_PATH: &str = "https://files.cow.fi/cow-sdk";
35
36pub const RAW_CHAINS_FILES_PATH: &str = "https://files.cow.fi/cow-sdk/chains";
38
39pub const TOKEN_LIST_IMAGES_PATH: &str = "https://files.cow.fi/token-lists/images";
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
49#[repr(u64)]
50pub enum EvmChains {
51 Mainnet = 1,
53 Optimism = 10,
55 Bnb = 56,
57 GnosisChain = 100,
59 Polygon = 137,
61 Base = 8_453,
63 Plasma = 9_745,
65 ArbitrumOne = 42_161,
67 Avalanche = 43_114,
69 Ink = 57_073,
71 Linea = 59_144,
73 Sepolia = 11_155_111,
75}
76
77impl EvmChains {
78 #[must_use]
84 pub const fn as_u64(self) -> u64 {
85 self as u64
86 }
87
88 #[must_use]
98 pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
99 match chain_id {
100 1 => Some(Self::Mainnet),
101 10 => Some(Self::Optimism),
102 56 => Some(Self::Bnb),
103 100 => Some(Self::GnosisChain),
104 137 => Some(Self::Polygon),
105 8_453 => Some(Self::Base),
106 9_745 => Some(Self::Plasma),
107 42_161 => Some(Self::ArbitrumOne),
108 43_114 => Some(Self::Avalanche),
109 57_073 => Some(Self::Ink),
110 59_144 => Some(Self::Linea),
111 11_155_111 => Some(Self::Sepolia),
112 _ => None,
113 }
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
119#[repr(u64)]
120pub enum NonEvmChains {
121 Bitcoin = 1_000_000_000,
123 Solana = 1_000_000_001,
125}
126
127impl NonEvmChains {
128 #[must_use]
134 pub const fn as_u64(self) -> u64 {
135 self as u64
136 }
137
138 #[must_use]
148 pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
149 match chain_id {
150 1_000_000_000 => Some(Self::Bitcoin),
151 1_000_000_001 => Some(Self::Solana),
152 _ => None,
153 }
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
160#[repr(u64)]
161pub enum AdditionalTargetChainId {
162 Optimism = 10,
164 Bitcoin = 1_000_000_000,
166 Solana = 1_000_000_001,
168}
169
170impl AdditionalTargetChainId {
171 #[must_use]
177 pub const fn as_u64(self) -> u64 {
178 self as u64
179 }
180
181 #[must_use]
191 pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
192 match chain_id {
193 10 => Some(Self::Optimism),
194 1_000_000_000 => Some(Self::Bitcoin),
195 1_000_000_001 => Some(Self::Solana),
196 _ => None,
197 }
198 }
199
200 #[must_use]
206 pub const fn all() -> &'static [Self] {
207 &[Self::Optimism, Self::Bitcoin, Self::Solana]
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217pub enum TargetChainId {
218 Supported(SupportedChainId),
220 Additional(AdditionalTargetChainId),
222}
223
224impl TargetChainId {
225 #[must_use]
231 pub const fn as_u64(&self) -> u64 {
232 match self {
233 Self::Supported(c) => c.as_u64(),
234 Self::Additional(c) => c.as_u64(),
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
243pub struct ThemedImage {
244 pub light: &'static str,
246 pub dark: &'static str,
248}
249
250#[derive(Debug, Clone)]
252pub struct WebUrl {
253 pub name: &'static str,
255 pub url: &'static str,
257}
258
259#[derive(Debug, Clone, Copy)]
261pub struct ChainContract {
262 pub address: Address,
264 pub block_created: Option<u64>,
266}
267
268#[derive(Debug, Clone)]
270pub struct ChainContracts {
271 pub multicall3: Option<ChainContract>,
273 pub ens_registry: Option<ChainContract>,
275 pub ens_universal_resolver: Option<ChainContract>,
277}
278
279#[derive(Debug, Clone)]
281pub struct ChainRpcUrls {
282 pub http: &'static [&'static str],
284 pub web_socket: Option<&'static [&'static str]>,
286}
287
288#[derive(Debug, Clone)]
293pub struct ChainTokenInfo {
294 pub chain_id: u64,
296 pub address: &'static str,
298 pub decimals: u8,
300 pub name: &'static str,
302 pub symbol: &'static str,
304 pub logo_url: Option<&'static str>,
306}
307
308#[derive(Debug, Clone)]
310pub struct EvmChainInfo {
311 pub id: u64,
313 pub label: &'static str,
315 pub eip155_label: &'static str,
317 pub address_prefix: &'static str,
319 pub native_currency: ChainTokenInfo,
321 pub is_testnet: bool,
323 pub color: &'static str,
325 pub logo: ThemedImage,
327 pub website: WebUrl,
329 pub docs: WebUrl,
331 pub block_explorer: WebUrl,
333 pub bridges: &'static [WebUrl],
335 pub contracts: ChainContracts,
337 pub rpc_urls: ChainRpcUrls,
339 pub is_zk_sync: bool,
341 pub is_under_development: bool,
343 pub is_deprecated: bool,
345}
346
347#[derive(Debug, Clone)]
349pub struct NonEvmChainInfo {
350 pub id: u64,
352 pub label: &'static str,
354 pub address_prefix: &'static str,
356 pub native_currency: ChainTokenInfo,
358 pub is_testnet: bool,
360 pub color: &'static str,
362 pub logo: ThemedImage,
364 pub website: WebUrl,
366 pub docs: WebUrl,
368 pub block_explorer: WebUrl,
370 pub is_under_development: bool,
372 pub is_deprecated: bool,
374}
375
376#[derive(Debug, Clone)]
378pub enum ChainInfo {
379 Evm(EvmChainInfo),
381 NonEvm(NonEvmChainInfo),
383}
384
385impl ChainInfo {
386 #[must_use]
392 pub const fn id(&self) -> u64 {
393 match self {
394 Self::Evm(info) => info.id,
395 Self::NonEvm(info) => info.id,
396 }
397 }
398
399 #[must_use]
405 pub const fn label(&self) -> &'static str {
406 match self {
407 Self::Evm(info) => info.label,
408 Self::NonEvm(info) => info.label,
409 }
410 }
411
412 #[must_use]
418 pub const fn is_under_development(&self) -> bool {
419 match self {
420 Self::Evm(info) => info.is_under_development,
421 Self::NonEvm(info) => info.is_under_development,
422 }
423 }
424
425 #[must_use]
431 pub const fn is_deprecated(&self) -> bool {
432 match self {
433 Self::Evm(info) => info.is_deprecated,
434 Self::NonEvm(info) => info.is_deprecated,
435 }
436 }
437
438 #[must_use]
444 pub const fn is_evm(&self) -> bool {
445 matches!(self, Self::Evm(_))
446 }
447
448 #[must_use]
454 pub const fn is_non_evm(&self) -> bool {
455 matches!(self, Self::NonEvm(_))
456 }
457
458 #[must_use]
464 pub const fn as_evm(&self) -> Option<&EvmChainInfo> {
465 match self {
466 Self::Evm(info) => Some(info),
467 Self::NonEvm(_) => None,
468 }
469 }
470
471 #[must_use]
477 pub const fn as_non_evm(&self) -> Option<&NonEvmChainInfo> {
478 match self {
479 Self::NonEvm(info) => Some(info),
480 Self::Evm(_) => None,
481 }
482 }
483
484 #[must_use]
490 pub const fn native_currency(&self) -> &ChainTokenInfo {
491 match self {
492 Self::Evm(info) => &info.native_currency,
493 Self::NonEvm(info) => &info.native_currency,
494 }
495 }
496}
497
498#[must_use]
519pub const fn is_evm_chain(chain_id: u64) -> bool {
520 EvmChains::try_from_u64(chain_id).is_some()
521}
522
523#[must_use]
541pub const fn is_non_evm_chain(chain_id: u64) -> bool {
542 NonEvmChains::try_from_u64(chain_id).is_some()
543}
544
545#[must_use]
567pub const fn is_evm_chain_info(chain_info: &ChainInfo) -> bool {
568 chain_info.is_evm()
569}
570
571#[must_use]
593pub const fn is_non_evm_chain_info(chain_info: &ChainInfo) -> bool {
594 chain_info.is_non_evm()
595}
596
597#[must_use]
614pub const fn is_btc_chain(chain_id: u64) -> bool {
615 chain_id == NonEvmChains::Bitcoin as u64
616}
617
618#[must_use]
635pub const fn is_solana_chain(chain_id: u64) -> bool {
636 chain_id == NonEvmChains::Solana as u64
637}
638
639#[must_use]
656pub const fn is_supported_chain(chain_id: u64) -> bool {
657 SupportedChainId::try_from_u64(chain_id).is_some()
658}
659
660#[must_use]
678pub const fn is_additional_target_chain(chain_id: u64) -> bool {
679 AdditionalTargetChainId::try_from_u64(chain_id).is_some()
680}
681
682#[must_use]
701pub const fn is_target_chain_id(chain_id: u64) -> bool {
702 is_supported_chain(chain_id) || is_additional_target_chain(chain_id)
703}
704
705#[must_use]
721pub fn is_zk_sync_chain(chain_id: u64) -> bool {
722 if !is_evm_chain(chain_id) {
723 return false;
724 }
725 get_chain_info(chain_id)
726 .and_then(|info| info.as_evm().cloned())
727 .is_some_and(|evm| evm.is_zk_sync)
728}
729
730#[must_use]
746pub fn is_chain_under_development(chain_id: u64) -> bool {
747 get_chain_info(chain_id).is_some_and(|info| info.is_under_development())
748}
749
750#[must_use]
767pub fn is_chain_deprecated(chain_id: u64) -> bool {
768 get_chain_info(chain_id).is_some_and(|info| info.is_deprecated())
769}
770
771#[must_use]
796pub const fn get_chain_info(chain_id: u64) -> Option<ChainInfo> {
797 if let Some(supported) = SupportedChainId::try_from_u64(chain_id) {
798 return Some(supported_chain_info(supported));
799 }
800
801 if let Some(additional) = AdditionalTargetChainId::try_from_u64(chain_id) {
802 return Some(additional_target_chain_info(additional));
803 }
804
805 None
806}
807
808#[must_use]
828pub fn map_supported_networks<T>(f: impl Fn(SupportedChainId) -> T) -> Vec<(SupportedChainId, T)> {
829 SupportedChainId::all().iter().map(|&chain| (chain, f(chain))).collect()
830}
831
832#[must_use]
843pub fn map_all_networks<T>(f: impl Fn(TargetChainId) -> T) -> Vec<(TargetChainId, T)> {
844 all_chain_ids().into_iter().map(|id| (id, f(id))).collect()
845}
846
847#[must_use]
859pub fn map_address_to_supported_networks(address: Address) -> Vec<(SupportedChainId, Address)> {
860 map_supported_networks(|_| address)
861}
862
863#[must_use]
869pub fn all_supported_chain_ids() -> Vec<SupportedChainId> {
870 SupportedChainId::all().to_vec()
871}
872
873#[must_use]
879pub fn all_supported_chains() -> Vec<ChainInfo> {
880 SupportedChainId::all().iter().map(|&c| supported_chain_info(c)).collect()
881}
882
883#[must_use]
889pub fn tradable_supported_chain_ids() -> Vec<SupportedChainId> {
890 SupportedChainId::all()
891 .iter()
892 .copied()
893 .filter(|&c| !supported_chain_info(c).is_deprecated())
894 .collect()
895}
896
897#[must_use]
903pub fn tradable_supported_chains() -> Vec<ChainInfo> {
904 SupportedChainId::all()
905 .iter()
906 .copied()
907 .filter(|&c| !supported_chain_info(c).is_deprecated())
908 .map(supported_chain_info)
909 .collect()
910}
911
912#[must_use]
918pub fn all_additional_target_chains() -> Vec<ChainInfo> {
919 AdditionalTargetChainId::all().iter().map(|&c| additional_target_chain_info(c)).collect()
920}
921
922#[must_use]
928pub fn all_additional_target_chain_ids() -> Vec<AdditionalTargetChainId> {
929 AdditionalTargetChainId::all().to_vec()
930}
931
932#[must_use]
938pub fn all_chains() -> Vec<ChainInfo> {
939 let mut chains = all_supported_chains();
940 chains.extend(all_additional_target_chains());
941 chains
942}
943
944#[must_use]
950pub fn all_chain_ids() -> Vec<TargetChainId> {
951 let mut ids: Vec<TargetChainId> =
952 SupportedChainId::all().iter().map(|&c| TargetChainId::Supported(c)).collect();
953 ids.extend(AdditionalTargetChainId::all().iter().map(|&c| TargetChainId::Additional(c)));
954 ids
955}
956
957#[must_use]
968pub fn all_chains_map() -> foldhash::HashMap<u64, ChainInfo> {
969 all_chains().into_iter().map(|info| (info.id(), info)).collect()
970}
971
972#[must_use]
984pub const fn supported_chain_info(chain: SupportedChainId) -> ChainInfo {
985 ChainInfo::Evm(evm_chain_detail(chain))
986}
987
988#[must_use]
998pub const fn additional_target_chain_info(chain: AdditionalTargetChainId) -> ChainInfo {
999 match chain {
1000 AdditionalTargetChainId::Optimism => ChainInfo::Evm(optimism_chain_info()),
1001 AdditionalTargetChainId::Bitcoin => ChainInfo::NonEvm(bitcoin_chain_info()),
1002 AdditionalTargetChainId::Solana => ChainInfo::NonEvm(solana_chain_info()),
1003 }
1004}
1005
1006const fn evm_chain_detail(chain: SupportedChainId) -> EvmChainInfo {
1007 match chain {
1008 SupportedChainId::Mainnet => mainnet_chain_info(),
1009 SupportedChainId::GnosisChain => gnosis_chain_info(),
1010 SupportedChainId::ArbitrumOne => arbitrum_chain_info(),
1011 SupportedChainId::Base => base_chain_info(),
1012 SupportedChainId::Sepolia => sepolia_chain_info(),
1013 SupportedChainId::Polygon => polygon_chain_info(),
1014 SupportedChainId::Avalanche => avalanche_chain_info(),
1015 SupportedChainId::BnbChain => bnb_chain_info(),
1016 SupportedChainId::Linea => linea_chain_info(),
1017 SupportedChainId::Plasma => plasma_chain_info(),
1018 SupportedChainId::Ink => ink_chain_info(),
1019 }
1020}
1021
1022const EVM_NATIVE_ADDR: &str = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
1024const BTC_ADDR: &str = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
1026const SOL_ADDR: &str = "11111111111111111111111111111111";
1028
1029const MULTICALL3: Address = Address::new([
1031 0xca, 0x11, 0xbd, 0xe0, 0x59, 0x77, 0xb3, 0x63, 0x11, 0x67, 0x02, 0x88, 0x62, 0xbe, 0x2a, 0x17,
1032 0x39, 0x76, 0xca, 0x11,
1033]);
1034
1035const fn default_native_currency(chain_id: u64) -> ChainTokenInfo {
1036 ChainTokenInfo {
1037 chain_id,
1038 address: EVM_NATIVE_ADDR,
1039 decimals: 18,
1040 name: "Ether",
1041 symbol: "ETH",
1042 logo_url: Some(
1043 "https://files.cow.fi/token-lists/images/1/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/logo.png",
1044 ),
1045 }
1046}
1047
1048const fn multicall3_only(block_created: u64) -> ChainContracts {
1049 ChainContracts {
1050 multicall3: Some(ChainContract { address: MULTICALL3, block_created: Some(block_created) }),
1051 ens_registry: None,
1052 ens_universal_resolver: None,
1053 }
1054}
1055
1056const fn mainnet_chain_info() -> EvmChainInfo {
1057 EvmChainInfo {
1058 id: 1,
1059 label: "Ethereum",
1060 eip155_label: "Ethereum Mainnet",
1061 address_prefix: "eth",
1062 native_currency: default_native_currency(1),
1063 is_testnet: false,
1064 color: "#62688F",
1065 logo: ThemedImage {
1066 light: "https://files.cow.fi/cow-sdk/chains/images/mainnet-logo.svg",
1067 dark: "https://files.cow.fi/cow-sdk/chains/images/mainnet-logo.svg",
1068 },
1069 website: WebUrl { name: "Ethereum", url: "https://ethereum.org" },
1070 docs: WebUrl { name: "Ethereum Docs", url: "https://ethereum.org/en/developers/docs" },
1071 block_explorer: WebUrl { name: "Etherscan", url: "https://etherscan.io" },
1072 bridges: &[],
1073 contracts: ChainContracts {
1074 multicall3: Some(ChainContract {
1075 address: MULTICALL3,
1076 block_created: Some(14_353_601),
1077 }),
1078 ens_registry: Some(ChainContract {
1079 address: Address::new([
1080 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x07, 0x4e, 0xc6, 0x9a, 0x0d, 0xfb,
1081 0x29, 0x97, 0xba, 0x6c, 0x7d, 0x2e, 0x1e,
1082 ]),
1083 block_created: None,
1084 }),
1085 ens_universal_resolver: Some(ChainContract {
1086 address: Address::new([
1087 0xce, 0x01, 0xf8, 0xee, 0xe7, 0xe4, 0x79, 0xc9, 0x28, 0xf8, 0x91, 0x9a, 0xbd,
1088 0x53, 0xe5, 0x53, 0xa3, 0x6c, 0xef, 0x67,
1089 ]),
1090 block_created: Some(19_258_213),
1091 }),
1092 },
1093 rpc_urls: ChainRpcUrls { http: &["https://eth.merkle.io"], web_socket: None },
1094 is_zk_sync: false,
1095 is_under_development: false,
1096 is_deprecated: false,
1097 }
1098}
1099
1100const fn gnosis_chain_info() -> EvmChainInfo {
1101 EvmChainInfo {
1102 id: 100,
1103 label: "Gnosis",
1104 eip155_label: "Gnosis",
1105 address_prefix: "gno",
1106 native_currency: ChainTokenInfo {
1107 chain_id: 100,
1108 address: EVM_NATIVE_ADDR,
1109 decimals: 18,
1110 name: "xDAI",
1111 symbol: "xDAI",
1112 logo_url: Some(
1113 "https://files.cow.fi/token-lists/images/100/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/logo.png",
1114 ),
1115 },
1116 is_testnet: false,
1117 color: "#07795B",
1118 logo: ThemedImage {
1119 light: "https://files.cow.fi/cow-sdk/chains/images/gnosis-logo.svg",
1120 dark: "https://files.cow.fi/cow-sdk/chains/images/gnosis-logo.svg",
1121 },
1122 website: WebUrl { name: "Gnosis Chain", url: "https://www.gnosischain.com" },
1123 docs: WebUrl { name: "Gnosis Chain Docs", url: "https://docs.gnosischain.com" },
1124 block_explorer: WebUrl { name: "Gnosisscan", url: "https://gnosisscan.io" },
1125 bridges: &[WebUrl { name: "Gnosis Chain Bridge", url: "https://bridge.gnosischain.com" }],
1126 contracts: multicall3_only(21_022_491),
1127 rpc_urls: ChainRpcUrls {
1128 http: &["https://rpc.gnosischain.com"],
1129 web_socket: Some(&["wss://rpc.gnosischain.com/wss"]),
1130 },
1131 is_zk_sync: false,
1132 is_under_development: false,
1133 is_deprecated: false,
1134 }
1135}
1136
1137const fn arbitrum_chain_info() -> EvmChainInfo {
1138 EvmChainInfo {
1139 id: 42_161,
1140 label: "Arbitrum",
1141 eip155_label: "Arbitrum One",
1142 address_prefix: "arb1",
1143 native_currency: default_native_currency(42_161),
1144 is_testnet: false,
1145 color: "#1B4ADD",
1146 logo: ThemedImage {
1147 light: "https://files.cow.fi/cow-sdk/chains/images/arbitrum-logo.svg",
1148 dark: "https://files.cow.fi/cow-sdk/chains/images/arbitrum-logo.svg",
1149 },
1150 website: WebUrl { name: "Arbitrum", url: "https://arbitrum.io" },
1151 docs: WebUrl { name: "Arbitrum Docs", url: "https://docs.arbitrum.io" },
1152 block_explorer: WebUrl { name: "Arbiscan", url: "https://arbiscan.io" },
1153 bridges: &[WebUrl { name: "Arbitrum Bridge", url: "https://bridge.arbitrum.io" }],
1154 contracts: multicall3_only(7_654_707),
1155 rpc_urls: ChainRpcUrls { http: &["https://arb1.arbitrum.io/rpc"], web_socket: None },
1156 is_zk_sync: false,
1157 is_under_development: false,
1158 is_deprecated: false,
1159 }
1160}
1161
1162const fn base_chain_info() -> EvmChainInfo {
1163 EvmChainInfo {
1164 id: 8_453,
1165 label: "Base",
1166 eip155_label: "Base",
1167 address_prefix: "base",
1168 native_currency: default_native_currency(8_453),
1169 is_testnet: false,
1170 color: "#0052FF",
1171 logo: ThemedImage {
1172 light: "https://files.cow.fi/cow-sdk/chains/images/base-logo.svg",
1173 dark: "https://files.cow.fi/cow-sdk/chains/images/base-logo.svg",
1174 },
1175 website: WebUrl { name: "Base", url: "https://base.org" },
1176 docs: WebUrl { name: "Base Docs", url: "https://docs.base.org" },
1177 block_explorer: WebUrl { name: "Basescan", url: "https://basescan.org" },
1178 bridges: &[WebUrl { name: "Superchain Bridges", url: "https://bridge.base.org/deposit" }],
1179 contracts: multicall3_only(5022),
1180 rpc_urls: ChainRpcUrls { http: &["https://mainnet.base.org"], web_socket: None },
1181 is_zk_sync: false,
1182 is_under_development: false,
1183 is_deprecated: false,
1184 }
1185}
1186
1187const fn sepolia_chain_info() -> EvmChainInfo {
1188 EvmChainInfo {
1189 id: 11_155_111,
1190 label: "Sepolia",
1191 eip155_label: "Ethereum Sepolia",
1192 address_prefix: "sep",
1193 native_currency: default_native_currency(11_155_111),
1194 is_testnet: true,
1195 color: "#C12FF2",
1196 logo: ThemedImage {
1197 light: "https://files.cow.fi/cow-sdk/chains/images/sepolia-logo.svg",
1198 dark: "https://files.cow.fi/cow-sdk/chains/images/sepolia-logo.svg",
1199 },
1200 website: WebUrl { name: "Ethereum", url: "https://sepolia.dev" },
1201 docs: WebUrl {
1202 name: "Sepolia Docs",
1203 url: "https://ethereum.org/en/developers/docs/networks/#sepolia",
1204 },
1205 block_explorer: WebUrl { name: "Etherscan", url: "https://sepolia.etherscan.io" },
1206 bridges: &[],
1207 contracts: ChainContracts {
1208 multicall3: Some(ChainContract { address: MULTICALL3, block_created: Some(751_532) }),
1209 ens_registry: Some(ChainContract {
1210 address: Address::new([
1211 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x07, 0x4e, 0xc6, 0x9a, 0x0d, 0xfb,
1212 0x29, 0x97, 0xba, 0x6c, 0x7d, 0x2e, 0x1e,
1213 ]),
1214 block_created: None,
1215 }),
1216 ens_universal_resolver: Some(ChainContract {
1217 address: Address::new([
1218 0xc8, 0xaf, 0x99, 0x9e, 0x38, 0x27, 0x3d, 0x65, 0x8b, 0xe1, 0xb9, 0x21, 0xb8,
1219 0x8a, 0x9d, 0xdf, 0x00, 0x57, 0x69, 0xcc,
1220 ]),
1221 block_created: Some(5_317_080),
1222 }),
1223 },
1224 rpc_urls: ChainRpcUrls { http: &["https://sepolia.drpc.org"], web_socket: None },
1225 is_zk_sync: false,
1226 is_under_development: false,
1227 is_deprecated: false,
1228 }
1229}
1230
1231const fn polygon_chain_info() -> EvmChainInfo {
1232 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/polygon-logo.svg";
1233 EvmChainInfo {
1234 id: 137,
1235 label: "Polygon",
1236 eip155_label: "Polygon Mainnet",
1237 address_prefix: "matic",
1238 native_currency: ChainTokenInfo {
1239 chain_id: 137,
1240 address: EVM_NATIVE_ADDR,
1241 decimals: 18,
1242 name: "POL",
1243 symbol: "POL",
1244 logo_url: Some(logo_url),
1245 },
1246 is_testnet: false,
1247 color: "#8247e5",
1248 logo: ThemedImage { light: logo_url, dark: logo_url },
1249 website: WebUrl { name: "Polygon", url: "https://polygon.technology" },
1250 docs: WebUrl { name: "Polygon Docs", url: "https://docs.polygon.technology" },
1251 block_explorer: WebUrl { name: "Polygonscan", url: "https://polygonscan.com" },
1252 bridges: &[],
1253 contracts: multicall3_only(25_770_160),
1254 rpc_urls: ChainRpcUrls { http: &["https://polygon-rpc.com"], web_socket: None },
1255 is_zk_sync: false,
1256 is_under_development: false,
1257 is_deprecated: false,
1258 }
1259}
1260
1261const fn avalanche_chain_info() -> EvmChainInfo {
1262 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/avax-logo.svg";
1263 EvmChainInfo {
1264 id: 43_114,
1265 label: "Avalanche",
1266 eip155_label: "Avalanche C-Chain",
1267 address_prefix: "avax",
1268 native_currency: ChainTokenInfo {
1269 chain_id: 43_114,
1270 address: EVM_NATIVE_ADDR,
1271 decimals: 18,
1272 name: "Avalanche",
1273 symbol: "AVAX",
1274 logo_url: Some(logo_url),
1275 },
1276 is_testnet: false,
1277 color: "#ff3944",
1278 logo: ThemedImage { light: logo_url, dark: logo_url },
1279 website: WebUrl { name: "Avalanche", url: "https://www.avax.network/" },
1280 docs: WebUrl { name: "Avalanche Docs", url: "https://build.avax.network/docs" },
1281 block_explorer: WebUrl { name: "Snowscan", url: "https://snowscan.xyz" },
1282 bridges: &[],
1283 contracts: multicall3_only(11_907_934),
1284 rpc_urls: ChainRpcUrls {
1285 http: &["https://api.avax.network/ext/bc/C/rpc"],
1286 web_socket: None,
1287 },
1288 is_zk_sync: false,
1289 is_under_development: false,
1290 is_deprecated: false,
1291 }
1292}
1293
1294const fn bnb_chain_info() -> EvmChainInfo {
1295 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/bnb-logo.svg";
1296 EvmChainInfo {
1297 id: 56,
1298 label: "BNB",
1299 eip155_label: "BNB Chain Mainnet",
1300 address_prefix: "bnb",
1301 native_currency: ChainTokenInfo {
1302 chain_id: 56,
1303 address: EVM_NATIVE_ADDR,
1304 decimals: 18,
1305 name: "BNB Chain Native Token",
1306 symbol: "BNB",
1307 logo_url: Some(logo_url),
1308 },
1309 is_testnet: false,
1310 color: "#F0B90B",
1311 logo: ThemedImage { light: logo_url, dark: logo_url },
1312 website: WebUrl { name: "BNB Chain", url: "https://www.bnbchain.org" },
1313 docs: WebUrl { name: "BNB Chain Docs", url: "https://docs.bnbchain.org" },
1314 block_explorer: WebUrl { name: "Bscscan", url: "https://bscscan.com" },
1315 bridges: &[WebUrl {
1316 name: "BNB Chain Cross-Chain Bridge",
1317 url: "https://www.bnbchain.org/en/bnb-chain-bridge",
1318 }],
1319 contracts: multicall3_only(15_921_452),
1320 rpc_urls: ChainRpcUrls { http: &["https://bsc-dataseed1.bnbchain.org"], web_socket: None },
1321 is_zk_sync: false,
1322 is_under_development: false,
1323 is_deprecated: false,
1324 }
1325}
1326
1327const fn linea_chain_info() -> EvmChainInfo {
1328 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/linea-logo.svg";
1329 EvmChainInfo {
1330 id: 59_144,
1331 label: "Linea",
1332 eip155_label: "Linea Mainnet",
1333 address_prefix: "linea",
1334 native_currency: default_native_currency(59_144),
1335 is_testnet: false,
1336 color: "#61dfff",
1337 logo: ThemedImage { light: logo_url, dark: logo_url },
1338 website: WebUrl { name: "Linea", url: "https://linea.build" },
1339 docs: WebUrl { name: "Linea Docs", url: "https://docs.linea.build" },
1340 block_explorer: WebUrl { name: "Lineascan", url: "https://lineascan.build" },
1341 bridges: &[WebUrl { name: "Linea Bridge", url: "https://linea.build/hub/bridge" }],
1342 contracts: multicall3_only(42),
1343 rpc_urls: ChainRpcUrls { http: &["https://rpc.linea.build"], web_socket: None },
1344 is_zk_sync: false,
1345 is_under_development: false,
1346 is_deprecated: false,
1347 }
1348}
1349
1350const fn plasma_chain_info() -> EvmChainInfo {
1351 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/plasma-logo.svg";
1352 EvmChainInfo {
1353 id: 9_745,
1354 label: "Plasma",
1355 eip155_label: "Plasma Mainnet",
1356 address_prefix: "plasma",
1357 native_currency: ChainTokenInfo {
1358 chain_id: 9_745,
1359 address: EVM_NATIVE_ADDR,
1360 decimals: 18,
1361 name: "Plasma",
1362 symbol: "XPL",
1363 logo_url: Some(logo_url),
1364 },
1365 is_testnet: false,
1366 color: "#569F8C",
1367 logo: ThemedImage { light: logo_url, dark: logo_url },
1368 website: WebUrl { name: "Plasma", url: "https://www.plasma.to" },
1369 docs: WebUrl { name: "Plasma Docs", url: "https://docs.plasma.to" },
1370 block_explorer: WebUrl { name: "Plasmascan", url: "https://plasmascan.to" },
1371 bridges: &[],
1372 contracts: multicall3_only(0),
1373 rpc_urls: ChainRpcUrls { http: &["https://rpc.plasma.to"], web_socket: None },
1374 is_zk_sync: false,
1375 is_under_development: false,
1376 is_deprecated: false,
1377 }
1378}
1379
1380const fn ink_chain_info() -> EvmChainInfo {
1381 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/ink-logo.svg";
1382 EvmChainInfo {
1383 id: 57_073,
1384 label: "Ink",
1385 eip155_label: "Ink Chain Mainnet",
1386 address_prefix: "ink",
1387 native_currency: default_native_currency(57_073),
1388 is_testnet: false,
1389 color: "#7132f5",
1390 logo: ThemedImage { light: logo_url, dark: logo_url },
1391 website: WebUrl { name: "Ink", url: "https://inkonchain.com/" },
1392 docs: WebUrl { name: "Ink Docs", url: "https://docs.inkonchain.com" },
1393 block_explorer: WebUrl { name: "Ink Explorer", url: "https://explorer.inkonchain.com" },
1394 bridges: &[WebUrl { name: "Ink Bridge", url: "https://inkonchain.com/bridge" }],
1395 contracts: multicall3_only(0),
1396 rpc_urls: ChainRpcUrls { http: &["https://rpc-ten.inkonchain.com"], web_socket: None },
1397 is_zk_sync: false,
1398 is_under_development: false,
1399 is_deprecated: false,
1400 }
1401}
1402
1403const fn optimism_chain_info() -> EvmChainInfo {
1404 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/optimism-logo.svg";
1405 EvmChainInfo {
1406 id: 10,
1407 label: "Optimism",
1408 eip155_label: "OP Mainnet",
1409 address_prefix: "op",
1410 native_currency: default_native_currency(10),
1411 is_testnet: false,
1412 color: "#ff0420",
1413 logo: ThemedImage { light: logo_url, dark: logo_url },
1414 website: WebUrl { name: "Optimism", url: "https://optimism.io" },
1415 docs: WebUrl { name: "Optimism Docs", url: "https://docs.optimism.io" },
1416 block_explorer: WebUrl { name: "Etherscan", url: "https://optimistic.etherscan.io" },
1417 bridges: &[],
1418 contracts: multicall3_only(4_286_263),
1419 rpc_urls: ChainRpcUrls { http: &["https://mainnet.optimism.io"], web_socket: None },
1420 is_zk_sync: false,
1421 is_under_development: false,
1422 is_deprecated: false,
1423 }
1424}
1425
1426const fn bitcoin_chain_info() -> NonEvmChainInfo {
1427 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/bitcoin-logo.svg";
1428 NonEvmChainInfo {
1429 id: 1_000_000_000,
1430 label: "Bitcoin",
1431 address_prefix: "btc",
1432 native_currency: ChainTokenInfo {
1433 chain_id: 1_000_000_000,
1434 address: BTC_ADDR,
1435 decimals: 8,
1436 name: "Bitcoin",
1437 symbol: "BTC",
1438 logo_url: Some(logo_url),
1439 },
1440 is_testnet: false,
1441 color: "#f7931a",
1442 logo: ThemedImage { light: logo_url, dark: logo_url },
1443 website: WebUrl { name: "Bitcoin", url: "https://bitcoin.org" },
1444 docs: WebUrl {
1445 name: "Bitcoin Docs",
1446 url: "https://bitcoin.org/en/developer-documentation",
1447 },
1448 block_explorer: WebUrl { name: "Blockstream Explorer", url: "https://blockstream.info" },
1449 is_under_development: false,
1450 is_deprecated: false,
1451 }
1452}
1453
1454const fn solana_chain_info() -> NonEvmChainInfo {
1455 let logo_url = "https://files.cow.fi/cow-sdk/chains/images/solana-logo.svg";
1456 NonEvmChainInfo {
1457 id: 1_000_000_001,
1458 label: "Solana",
1459 address_prefix: "sol",
1460 native_currency: ChainTokenInfo {
1461 chain_id: 1_000_000_001,
1462 address: SOL_ADDR,
1463 decimals: 9,
1464 name: "Solana",
1465 symbol: "SOL",
1466 logo_url: Some(logo_url),
1467 },
1468 is_testnet: false,
1469 color: "#9945FF",
1470 logo: ThemedImage { light: logo_url, dark: logo_url },
1471 website: WebUrl { name: "Solana", url: "https://solana.com" },
1472 docs: WebUrl { name: "Solana Docs", url: "https://docs.solana.com" },
1473 block_explorer: WebUrl { name: "Solana Explorer", url: "https://explorer.solana.com" },
1474 is_under_development: false,
1475 is_deprecated: false,
1476 }
1477}
1478
1479#[derive(Debug, Clone, Default)]
1483pub struct IpfsConfig {
1484 pub uri: Option<String>,
1486 pub write_uri: Option<String>,
1488 pub read_uri: Option<String>,
1490 pub pinata_api_key: Option<String>,
1492 pub pinata_api_secret: Option<String>,
1494}
1495
1496#[derive(Debug, Clone)]
1501pub struct ApiContext {
1502 pub chain_id: SupportedChainId,
1504 pub env: super::chain::Env,
1506 pub base_urls: Option<ApiBaseUrls>,
1508 pub api_key: Option<String>,
1510}
1511
1512impl Default for ApiContext {
1513 fn default() -> Self {
1515 Self {
1516 chain_id: SupportedChainId::Mainnet,
1517 env: super::chain::Env::Prod,
1518 base_urls: None,
1519 api_key: None,
1520 }
1521 }
1522}
1523
1524#[derive(Debug, Clone, Default)]
1527pub struct ProtocolOptions {
1528 pub env: Option<super::chain::Env>,
1530 pub settlement_contract_override: Option<AddressPerChain>,
1532 pub eth_flow_contract_override: Option<AddressPerChain>,
1534}
1535
1536#[derive(Debug, Clone)]
1538pub struct EvmCall {
1539 pub to: Address,
1541 pub data: Vec<u8>,
1543 pub value: U256,
1545}
1546
1547#[cfg(test)]
1548mod tests {
1549 use super::*;
1550
1551 #[test]
1554 fn evm_chains_roundtrip_u64() {
1555 let chains = [
1556 (EvmChains::Mainnet, 1),
1557 (EvmChains::Optimism, 10),
1558 (EvmChains::Bnb, 56),
1559 (EvmChains::GnosisChain, 100),
1560 (EvmChains::Polygon, 137),
1561 (EvmChains::Base, 8_453),
1562 (EvmChains::Plasma, 9_745),
1563 (EvmChains::ArbitrumOne, 42_161),
1564 (EvmChains::Avalanche, 43_114),
1565 (EvmChains::Ink, 57_073),
1566 (EvmChains::Linea, 59_144),
1567 (EvmChains::Sepolia, 11_155_111),
1568 ];
1569 for (chain, id) in chains {
1570 assert_eq!(chain.as_u64(), id);
1571 assert_eq!(EvmChains::try_from_u64(id), Some(chain));
1572 }
1573 }
1574
1575 #[test]
1576 fn evm_chains_unknown_returns_none() {
1577 assert_eq!(EvmChains::try_from_u64(9999), None);
1578 }
1579
1580 #[test]
1583 fn non_evm_chains_roundtrip() {
1584 assert_eq!(NonEvmChains::Bitcoin.as_u64(), 1_000_000_000);
1585 assert_eq!(NonEvmChains::Solana.as_u64(), 1_000_000_001);
1586 assert_eq!(NonEvmChains::try_from_u64(1_000_000_000), Some(NonEvmChains::Bitcoin));
1587 assert_eq!(NonEvmChains::try_from_u64(1_000_000_001), Some(NonEvmChains::Solana));
1588 assert_eq!(NonEvmChains::try_from_u64(999), None);
1589 }
1590
1591 #[test]
1594 fn additional_target_roundtrip() {
1595 for &chain in AdditionalTargetChainId::all() {
1596 let id = chain.as_u64();
1597 assert_eq!(AdditionalTargetChainId::try_from_u64(id), Some(chain));
1598 }
1599 }
1600
1601 #[test]
1602 fn additional_target_all_has_three() {
1603 assert_eq!(AdditionalTargetChainId::all().len(), 3);
1604 }
1605
1606 #[test]
1609 fn target_chain_id_as_u64() {
1610 let supported = TargetChainId::Supported(SupportedChainId::Mainnet);
1611 assert_eq!(supported.as_u64(), 1);
1612 let additional = TargetChainId::Additional(AdditionalTargetChainId::Bitcoin);
1613 assert_eq!(additional.as_u64(), 1_000_000_000);
1614 }
1615
1616 #[test]
1619 fn is_evm_chain_correct() {
1620 assert!(is_evm_chain(1));
1621 assert!(is_evm_chain(10));
1622 assert!(!is_evm_chain(1_000_000_000));
1623 assert!(!is_evm_chain(9999));
1624 }
1625
1626 #[test]
1627 fn is_non_evm_chain_correct() {
1628 assert!(is_non_evm_chain(1_000_000_000));
1629 assert!(is_non_evm_chain(1_000_000_001));
1630 assert!(!is_non_evm_chain(1));
1631 }
1632
1633 #[test]
1634 fn is_btc_chain_correct() {
1635 assert!(is_btc_chain(1_000_000_000));
1636 assert!(!is_btc_chain(1));
1637 }
1638
1639 #[test]
1640 fn is_solana_chain_correct() {
1641 assert!(is_solana_chain(1_000_000_001));
1642 assert!(!is_solana_chain(1_000_000_000));
1643 assert!(!is_solana_chain(1));
1644 }
1645
1646 #[test]
1647 fn all_chains_map_covers_every_chain() {
1648 let map = all_chains_map();
1649 assert_eq!(map.len(), all_chains().len());
1650 for id in all_chain_ids() {
1651 assert!(map.contains_key(&id.as_u64()), "missing chain id {}", id.as_u64());
1652 }
1653 }
1654
1655 #[test]
1656 fn is_supported_chain_correct() {
1657 assert!(is_supported_chain(1));
1658 assert!(is_supported_chain(100));
1659 assert!(!is_supported_chain(10));
1660 assert!(!is_supported_chain(9999));
1661 }
1662
1663 #[test]
1664 fn is_additional_target_chain_correct() {
1665 assert!(is_additional_target_chain(10));
1666 assert!(is_additional_target_chain(1_000_000_000));
1667 assert!(!is_additional_target_chain(1));
1668 }
1669
1670 #[test]
1671 fn is_target_chain_id_correct() {
1672 assert!(is_target_chain_id(1));
1673 assert!(is_target_chain_id(10));
1674 assert!(is_target_chain_id(1_000_000_000));
1675 assert!(!is_target_chain_id(9999));
1676 }
1677
1678 #[test]
1679 fn is_zk_sync_chain_is_false_for_all() {
1680 assert!(!is_zk_sync_chain(1));
1681 assert!(!is_zk_sync_chain(100));
1682 }
1683
1684 #[test]
1687 fn get_chain_info_all_supported() {
1688 for &chain in SupportedChainId::all() {
1689 let chain_info = get_chain_info(chain.as_u64());
1690 assert!(chain_info.is_some(), "no chain info for {chain:?}");
1691 let info = chain_info.unwrap_or_else(|| supported_chain_info(chain));
1692 assert_eq!(info.id(), chain.as_u64());
1693 assert!(!info.label().is_empty());
1694 }
1695 }
1696
1697 #[test]
1698 fn get_chain_info_additional_targets() {
1699 for &chain in AdditionalTargetChainId::all() {
1700 let info = get_chain_info(chain.as_u64());
1701 assert!(info.is_some(), "no chain info for {chain:?}");
1702 }
1703 }
1704
1705 #[test]
1706 fn get_chain_info_unknown_returns_none() {
1707 assert!(get_chain_info(9999).is_none());
1708 }
1709
1710 #[test]
1711 fn chain_info_evm_predicates() {
1712 let info = supported_chain_info(SupportedChainId::Mainnet);
1713 assert!(info.is_evm());
1714 assert!(!info.is_non_evm());
1715 assert!(info.as_evm().is_some());
1716 assert!(info.as_non_evm().is_none());
1717 }
1718
1719 #[test]
1720 fn chain_info_non_evm_predicates() {
1721 let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1722 assert!(!info.is_evm());
1723 assert!(info.is_non_evm());
1724 assert!(info.as_non_evm().is_some());
1725 }
1726
1727 #[test]
1728 fn chain_info_native_currency() {
1729 let info = supported_chain_info(SupportedChainId::Mainnet);
1730 let currency = info.native_currency();
1731 assert_eq!(currency.decimals, 18);
1732 }
1733
1734 #[test]
1737 fn all_supported_chain_ids_matches_all() {
1738 let ids = all_supported_chain_ids();
1739 assert_eq!(ids.len(), SupportedChainId::all().len());
1740 }
1741
1742 #[test]
1743 fn all_supported_chains_matches_all() {
1744 let chains = all_supported_chains();
1745 assert_eq!(chains.len(), SupportedChainId::all().len());
1746 }
1747
1748 #[test]
1749 fn tradable_chains_excludes_deprecated_and_dev() {
1750 let tradable = tradable_supported_chain_ids();
1751 assert!(!tradable.is_empty());
1752 assert!(tradable.len() <= SupportedChainId::all().len());
1753 }
1754
1755 #[test]
1756 fn all_additional_target_chain_ids_has_three() {
1757 assert_eq!(all_additional_target_chain_ids().len(), 3);
1758 }
1759
1760 #[test]
1761 fn all_chains_includes_supported_and_additional() {
1762 let all = all_chains();
1763 assert!(all.len() >= SupportedChainId::all().len());
1764 }
1765
1766 #[test]
1767 fn all_chain_ids_includes_both() {
1768 let ids = all_chain_ids();
1769 assert!(ids.len() >= SupportedChainId::all().len() + AdditionalTargetChainId::all().len());
1770 }
1771
1772 #[test]
1773 fn map_supported_networks_maps_all() {
1774 let mapped = map_supported_networks(|c| c.as_u64());
1775 assert_eq!(mapped.len(), SupportedChainId::all().len());
1776 }
1777
1778 #[test]
1779 fn map_address_to_supported_networks_produces_correct_count() {
1780 let mapped = map_address_to_supported_networks(Address::ZERO);
1781 assert_eq!(mapped.len(), SupportedChainId::all().len());
1782 }
1783
1784 #[test]
1787 fn is_zk_sync_chain_returns_false_for_non_evm() {
1788 assert!(!is_zk_sync_chain(1_000_000_000));
1789 }
1790
1791 #[test]
1792 fn is_chain_under_development_unknown_chain() {
1793 assert!(!is_chain_under_development(9999));
1794 }
1795
1796 #[test]
1797 fn is_chain_deprecated_unknown_chain() {
1798 assert!(!is_chain_deprecated(9999));
1799 }
1800
1801 #[test]
1802 fn chain_info_is_under_development_non_evm() {
1803 let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1804 assert!(!info.is_under_development());
1805 }
1806
1807 #[test]
1808 fn chain_info_is_deprecated_non_evm() {
1809 let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1810 assert!(!info.is_deprecated());
1811 }
1812
1813 #[test]
1814 fn evm_chain_info_type_guards() {
1815 let info = supported_chain_info(SupportedChainId::Mainnet);
1816 assert!(is_evm_chain_info(&info));
1817 assert!(!is_non_evm_chain_info(&info));
1818 }
1819
1820 #[test]
1821 fn non_evm_chain_info_type_guards() {
1822 let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1823 assert!(is_non_evm_chain_info(&info));
1824 assert!(!is_evm_chain_info(&info));
1825 }
1826
1827 #[test]
1828 fn tradable_supported_chains_returns_chain_infos() {
1829 let chains = tradable_supported_chains();
1830 assert!(!chains.is_empty());
1831 for c in &chains {
1832 assert!(!c.is_deprecated());
1833 }
1834 }
1835
1836 #[test]
1837 fn all_additional_target_chains_returns_infos() {
1838 let chains = all_additional_target_chains();
1839 assert_eq!(chains.len(), 3);
1840 }
1841
1842 #[test]
1843 fn map_all_networks_covers_everything() {
1844 let mapped = map_all_networks(|t| t.as_u64());
1845 assert!(
1846 mapped.len() >= SupportedChainId::all().len() + AdditionalTargetChainId::all().len()
1847 );
1848 }
1849
1850 #[test]
1851 fn chain_info_label_non_evm() {
1852 let info = additional_target_chain_info(AdditionalTargetChainId::Solana);
1853 assert_eq!(info.label(), "Solana");
1854 assert_eq!(info.id(), 1_000_000_001);
1855 }
1856
1857 #[test]
1858 fn additional_target_optimism_is_evm() {
1859 let info = additional_target_chain_info(AdditionalTargetChainId::Optimism);
1860 assert!(info.is_evm());
1861 assert_eq!(info.label(), "Optimism");
1862 }
1863
1864 #[test]
1865 fn api_context_default() {
1866 let ctx = ApiContext::default();
1867 assert_eq!(ctx.chain_id, SupportedChainId::Mainnet);
1868 assert!(ctx.base_urls.is_none());
1869 assert!(ctx.api_key.is_none());
1870 }
1871
1872 #[test]
1873 fn additional_target_try_from_u64_unknown() {
1874 assert!(AdditionalTargetChainId::try_from_u64(999).is_none());
1875 }
1876}