1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ChainKind {
12 Evm,
14 Solana,
15 Tron,
16 DexOnly,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Chain {
23 pub id: &'static str,
25 pub aliases: &'static [&'static str],
27 pub kind: ChainKind,
28 pub evm_chain_id: Option<&'static str>,
30 pub api_key_name: Option<&'static str>,
32 pub native_symbol: &'static str,
33 pub native_decimals: u8,
34 pub explorer_token_base: Option<&'static str>,
36 pub explorer_address_base: Option<&'static str>,
38 pub dexscreener_id: Option<&'static str>,
40 pub wrapped_native: Option<&'static str>,
42}
43
44const fn evm(
45 id: &'static str,
46 aliases: &'static [&'static str],
47 chain_id: &'static str,
48 key: &'static str,
49 symbol: &'static str,
50 explorer: (&'static str, &'static str),
51 wrapped: &'static str,
52) -> Chain {
53 Chain {
54 id,
55 aliases,
56 kind: ChainKind::Evm,
57 evm_chain_id: Some(chain_id),
58 api_key_name: Some(key),
59 native_symbol: symbol,
60 native_decimals: 18,
61 explorer_token_base: Some(explorer.0),
62 explorer_address_base: Some(explorer.1),
63 dexscreener_id: Some(id),
64 wrapped_native: Some(wrapped),
65 }
66}
67
68pub const CHAINS: &[Chain] = &[
70 evm(
71 "ethereum",
72 &["eth"],
73 "1",
74 "etherscan",
75 "ETH",
76 ("https://etherscan.io/token", "https://etherscan.io/address"),
77 "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
78 ),
79 evm(
80 "polygon",
81 &["matic"],
82 "137",
83 "polygonscan",
84 "MATIC",
85 (
86 "https://polygonscan.com/token",
87 "https://polygonscan.com/address",
88 ),
89 "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
90 ),
91 evm(
92 "arbitrum",
93 &["arb"],
94 "42161",
95 "arbiscan",
96 "ETH",
97 ("https://arbiscan.io/token", "https://arbiscan.io/address"),
98 "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
99 ),
100 evm(
101 "optimism",
102 &["op"],
103 "10",
104 "optimism",
105 "ETH",
106 (
107 "https://optimistic.etherscan.io/token",
108 "https://optimistic.etherscan.io/address",
109 ),
110 "0x4200000000000000000000000000000000000006",
111 ),
112 evm(
113 "base",
114 &[],
115 "8453",
116 "basescan",
117 "ETH",
118 ("https://basescan.org/token", "https://basescan.org/address"),
119 "0x4200000000000000000000000000000000000006",
120 ),
121 evm(
122 "bsc",
123 &["bnb"],
124 "56",
125 "bscscan",
126 "BNB",
127 ("https://bscscan.com/token", "https://bscscan.com/address"),
128 "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
129 ),
130 Chain {
131 id: "aegis",
132 aliases: &[],
133 kind: ChainKind::Evm,
134 evm_chain_id: None,
136 api_key_name: None,
137 native_symbol: "WRAITH",
138 native_decimals: 18,
139 explorer_token_base: None,
140 explorer_address_base: None,
141 dexscreener_id: None,
142 wrapped_native: None,
143 },
144 Chain {
145 id: "solana",
146 aliases: &["sol"],
147 kind: ChainKind::Solana,
148 evm_chain_id: None,
149 api_key_name: None,
150 native_symbol: "SOL",
151 native_decimals: 9,
152 explorer_token_base: Some("https://solscan.io/token"),
153 explorer_address_base: Some("https://solscan.io/account"),
154 dexscreener_id: Some("solana"),
155 wrapped_native: Some("So11111111111111111111111111111111111111112"),
156 },
157 Chain {
158 id: "tron",
159 aliases: &["trx"],
160 kind: ChainKind::Tron,
161 evm_chain_id: None,
162 api_key_name: None,
163 native_symbol: "TRX",
164 native_decimals: 6,
165 explorer_token_base: Some("https://tronscan.org/#/token20"),
166 explorer_address_base: Some("https://tronscan.org/#/address"),
167 dexscreener_id: Some("tron"),
168 wrapped_native: None,
170 },
171 Chain {
172 id: "avalanche",
173 aliases: &["avax"],
174 kind: ChainKind::DexOnly,
175 evm_chain_id: None,
176 api_key_name: None,
177 native_symbol: "AVAX",
178 native_decimals: 18,
179 explorer_token_base: None,
180 explorer_address_base: None,
181 dexscreener_id: Some("avalanche"),
182 wrapped_native: None,
183 },
184];
185
186pub fn chain(name: &str) -> Option<&'static Chain> {
188 let name = name.trim().to_lowercase();
189 CHAINS
190 .iter()
191 .find(|c| c.id == name || c.aliases.contains(&name.as_str()))
192}
193
194pub fn client_chain_ids() -> Vec<&'static str> {
197 CHAINS
198 .iter()
199 .filter(|c| c.kind != ChainKind::DexOnly)
200 .map(|c| c.id)
201 .collect()
202}
203
204pub fn client_chain_list() -> String {
206 client_chain_ids().join(", ")
207}
208
209pub fn explorer_api_chain_list() -> String {
211 CHAINS
212 .iter()
213 .filter(|c| c.evm_chain_id.is_some())
214 .map(|c| c.id)
215 .collect::<Vec<_>>()
216 .join(", ")
217}
218
219pub fn chain_help() -> String {
221 format!("Target blockchain network: {}", client_chain_list())
222}
223
224pub fn has_explorer(name: &str) -> bool {
227 chain(name).is_some_and(|c| c.kind != ChainKind::DexOnly && c.explorer_token_base.is_some())
228}
229
230pub fn is_evm(name: &str) -> bool {
232 chain(name).is_some_and(|c| c.kind == ChainKind::Evm)
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn test_ids_and_aliases_resolve_in_any_case() {
241 assert_eq!(chain("ETH").unwrap().id, "ethereum");
242 assert_eq!(chain("matic").unwrap().id, "polygon");
243 assert_eq!(chain(" Sol ").unwrap().id, "solana");
244 assert!(chain("nosuch").is_none());
245 }
246
247 #[test]
248 fn test_every_alias_names_one_chain_only() {
249 let mut names: Vec<&str> = CHAINS
250 .iter()
251 .flat_map(|c| std::iter::once(c.id).chain(c.aliases.iter().copied()))
252 .collect();
253 let n = names.len();
254 names.sort_unstable();
255 names.dedup();
256 assert_eq!(names.len(), n, "a name is listed twice");
257 }
258
259 #[test]
260 fn test_the_client_list_has_aegis_and_not_the_dex_only_chains() {
261 let ids = client_chain_ids();
262 assert!(ids.contains(&"aegis"));
263 assert!(!ids.contains(&"avalanche"));
264 assert!(is_evm("arb") && !is_evm("solana"));
265 }
266}