nautilus_model/defi/
chain.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Basic structures for representing on-chain blocks and transactions in DeFi integrations.
17
18use std::{
19    fmt::{Display, Formatter},
20    str::FromStr,
21    sync::Arc,
22};
23
24use serde::{Deserialize, Serialize};
25use strum::{Display, EnumIter, EnumString};
26
27/// Represents different blockchain networks.
28#[derive(
29    Debug,
30    Clone,
31    Copy,
32    Hash,
33    PartialOrd,
34    PartialEq,
35    Ord,
36    Eq,
37    Display,
38    EnumIter,
39    EnumString,
40    Serialize,
41    Deserialize,
42)]
43#[non_exhaustive]
44#[strum(ascii_case_insensitive)]
45#[cfg_attr(
46    feature = "python",
47    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
48)]
49pub enum Blockchain {
50    Abstract,
51    Arbitrum,
52    ArbitrumNova,
53    ArbitrumSepolia,
54    Aurora,
55    Avalanche,
56    Base,
57    BaseSepolia,
58    Berachain,
59    BerachainBartio,
60    Blast,
61    BlastSepolia,
62    Boba,
63    Bsc,
64    BscTestnet,
65    Celo,
66    Chiliz,
67    CitreaTestnet,
68    Curtis,
69    Cyber,
70    Darwinia,
71    Ethereum,
72    Fantom,
73    Flare,
74    Fraxtal,
75    Fuji,
76    GaladrielDevnet,
77    Gnosis,
78    GnosisChiado,
79    GnosisTraces,
80    HarmonyShard0,
81    Holesky,
82    HoleskyTokenTest,
83    Hyperliquid,
84    HyperliquidTemp,
85    Ink,
86    InternalTestChain,
87    Kroma,
88    Linea,
89    Lisk,
90    Lukso,
91    LuksoTestnet,
92    Manta,
93    Mantle,
94    MegaethTestnet,
95    Merlin,
96    Metall2,
97    Metis,
98    MevCommit,
99    Mode,
100    MonadTestnet,
101    MonadTestnetBackup,
102    MoonbaseAlpha,
103    Moonbeam,
104    Morph,
105    MorphHolesky,
106    Opbnb,
107    Optimism,
108    OptimismSepolia,
109    PharosDevnet,
110    Polygon,
111    PolygonAmoy,
112    PolygonZkEvm,
113    Rootstock,
114    Saakuru,
115    Scroll,
116    Sepolia,
117    ShimmerEvm,
118    Soneium,
119    Sophon,
120    SophonTestnet,
121    Superseed,
122    Unichain,
123    UnichainSepolia,
124    Xdc,
125    XdcTestnet,
126    Zeta,
127    Zircuit,
128    ZKsync,
129    Zora,
130}
131
132/// Defines a blockchain with its unique identifiers and connection details for network interaction.
133#[cfg_attr(feature = "python", pyo3::pyclass(module = "nautilus_pyo3.model"))]
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct Chain {
136    /// The blockchain network type.
137    pub name: Blockchain,
138    /// The unique identifier for this blockchain.
139    pub chain_id: u32,
140    /// URL endpoint for HyperSync connection.
141    pub hypersync_url: String,
142    /// URL endpoint for the default RPC connection.
143    pub rpc_url: Option<String>,
144    /// The number of decimals for the native currency.
145    pub native_currency_decimals: u8,
146}
147
148/// A thread-safe shared pointer to a `Chain`, enabling efficient reuse across multiple components.
149pub type SharedChain = Arc<Chain>;
150
151impl Chain {
152    /// Creates a new [`Chain`] instance with the specified blockchain and chain ID.
153    pub fn new(name: Blockchain, chain_id: u32) -> Self {
154        Self {
155            chain_id,
156            name,
157            hypersync_url: format!("https://{chain_id}.hypersync.xyz"),
158            rpc_url: None,
159            native_currency_decimals: 18, // Default to 18 for EVM chains
160        }
161    }
162
163    /// Sets the RPC URL endpoint.
164    pub fn set_rpc_url(&mut self, rpc: String) {
165        self.rpc_url = Some(rpc);
166    }
167
168    /// Returns a reference to the `Chain` corresponding to the given `chain_id`, or `None` if it is not found.
169    pub fn from_chain_id(chain_id: u32) -> Option<&'static Chain> {
170        match chain_id {
171            2741 => Some(&chains::ABSTRACT),
172            42161 => Some(&chains::ARBITRUM),
173            42170 => Some(&chains::ARBITRUM_NOVA),
174            421614 => Some(&chains::ARBITRUM_SEPOLIA),
175            1313161554 => Some(&chains::AURORA),
176            43114 => Some(&chains::AVALANCHE),
177            8453 => Some(&chains::BASE),
178            84532 => Some(&chains::BASE_SEPOLIA),
179            80094 => Some(&chains::BERACHAIN),
180            80085 => Some(&chains::BERACHAIN_BARTIO),
181            81457 => Some(&chains::BLAST),
182            168587773 => Some(&chains::BLAST_SEPOLIA),
183            288 => Some(&chains::BOBA),
184            56 => Some(&chains::BSC),
185            97 => Some(&chains::BSC_TESTNET),
186            42220 => Some(&chains::CELO),
187            8888 => Some(&chains::CHILIZ),
188            3333 => Some(&chains::CITREA_TESTNET),
189            33111 => Some(&chains::CURTIS),
190            7560 => Some(&chains::CYBER),
191            46 => Some(&chains::DARWINIA),
192            1 => Some(&chains::ETHEREUM),
193            250 => Some(&chains::FANTOM),
194            14 => Some(&chains::FLARE),
195            252 => Some(&chains::FRAXTAL),
196            43113 => Some(&chains::FUJI),
197            696969 => Some(&chains::GALADRIEL_DEVNET),
198            100 => Some(&chains::GNOSIS),
199            10200 => Some(&chains::GNOSIS_CHIADO),
200            10300 => Some(&chains::GNOSIS_TRACES),
201            1666600000 => Some(&chains::HARMONY_SHARD_0),
202            17000 => Some(&chains::HOLESKY),
203            17001 => Some(&chains::HOLESKY_TOKEN_TEST),
204            7979 => Some(&chains::HYPERLIQUID),
205            7978 => Some(&chains::HYPERLIQUID_TEMP),
206            222 => Some(&chains::INK),
207            13337 => Some(&chains::INTERNAL_TEST_CHAIN),
208            255 => Some(&chains::KROMA),
209            59144 => Some(&chains::LINEA),
210            501 => Some(&chains::LISK),
211            42 => Some(&chains::LUKSO),
212            4201 => Some(&chains::LUKSO_TESTNET),
213            169 => Some(&chains::MANTA),
214            5000 => Some(&chains::MANTLE),
215            777 => Some(&chains::MEGAETH_TESTNET),
216            4200 => Some(&chains::MERLIN),
217            90 => Some(&chains::METALL2),
218            1088 => Some(&chains::METIS),
219            11 => Some(&chains::MEV_COMMIT),
220            34443 => Some(&chains::MODE),
221            2323 => Some(&chains::MONAD_TESTNET),
222            2358 => Some(&chains::MONAD_TESTNET_BACKUP),
223            1287 => Some(&chains::MOONBASE_ALPHA),
224            1284 => Some(&chains::MOONBEAM),
225            2710 => Some(&chains::MORPH),
226            2710111 => Some(&chains::MORPH_HOLESKY),
227            204 => Some(&chains::OPBNB),
228            10 => Some(&chains::OPTIMISM),
229            11155420 => Some(&chains::OPTIMISM_SEPOLIA),
230            1337 => Some(&chains::PHAROS_DEVNET),
231            137 => Some(&chains::POLYGON),
232            80002 => Some(&chains::POLYGON_AMOY),
233            1101 => Some(&chains::POLYGON_ZKEVM),
234            30 => Some(&chains::ROOTSTOCK),
235            1204 => Some(&chains::SAAKURU),
236            534352 => Some(&chains::SCROLL),
237            11155111 => Some(&chains::SEPOLIA),
238            148 => Some(&chains::SHIMMER_EVM),
239            109 => Some(&chains::SONEIUM),
240            138 => Some(&chains::SOPHON),
241            139 => Some(&chains::SOPHON_TESTNET),
242            10001 => Some(&chains::SUPERSEED),
243            9999 => Some(&chains::UNICHAIN),
244            9997 => Some(&chains::UNICHAIN_SEPOLIA),
245            50 => Some(&chains::XDC),
246            51 => Some(&chains::XDC_TESTNET),
247            7000 => Some(&chains::ZETA),
248            78600 => Some(&chains::ZIRCUIT),
249            324 => Some(&chains::ZKSYNC),
250            7777777 => Some(&chains::ZORA),
251            _ => None,
252        }
253    }
254
255    /// Returns a reference to the `Chain` corresponding to the given chain name, or `None` if it is not found.
256    ///
257    /// String matching is case-insensitive.
258    pub fn from_chain_name(chain_name: &str) -> Option<&'static Chain> {
259        let blockchain = Blockchain::from_str(chain_name).ok()?;
260
261        match blockchain {
262            Blockchain::Abstract => Some(&chains::ABSTRACT),
263            Blockchain::Arbitrum => Some(&chains::ARBITRUM),
264            Blockchain::ArbitrumNova => Some(&chains::ARBITRUM_NOVA),
265            Blockchain::ArbitrumSepolia => Some(&chains::ARBITRUM_SEPOLIA),
266            Blockchain::Aurora => Some(&chains::AURORA),
267            Blockchain::Avalanche => Some(&chains::AVALANCHE),
268            Blockchain::Base => Some(&chains::BASE),
269            Blockchain::BaseSepolia => Some(&chains::BASE_SEPOLIA),
270            Blockchain::Berachain => Some(&chains::BERACHAIN),
271            Blockchain::BerachainBartio => Some(&chains::BERACHAIN_BARTIO),
272            Blockchain::Blast => Some(&chains::BLAST),
273            Blockchain::BlastSepolia => Some(&chains::BLAST_SEPOLIA),
274            Blockchain::Boba => Some(&chains::BOBA),
275            Blockchain::Bsc => Some(&chains::BSC),
276            Blockchain::BscTestnet => Some(&chains::BSC_TESTNET),
277            Blockchain::Celo => Some(&chains::CELO),
278            Blockchain::Chiliz => Some(&chains::CHILIZ),
279            Blockchain::CitreaTestnet => Some(&chains::CITREA_TESTNET),
280            Blockchain::Curtis => Some(&chains::CURTIS),
281            Blockchain::Cyber => Some(&chains::CYBER),
282            Blockchain::Darwinia => Some(&chains::DARWINIA),
283            Blockchain::Ethereum => Some(&chains::ETHEREUM),
284            Blockchain::Fantom => Some(&chains::FANTOM),
285            Blockchain::Flare => Some(&chains::FLARE),
286            Blockchain::Fraxtal => Some(&chains::FRAXTAL),
287            Blockchain::Fuji => Some(&chains::FUJI),
288            Blockchain::GaladrielDevnet => Some(&chains::GALADRIEL_DEVNET),
289            Blockchain::Gnosis => Some(&chains::GNOSIS),
290            Blockchain::GnosisChiado => Some(&chains::GNOSIS_CHIADO),
291            Blockchain::GnosisTraces => Some(&chains::GNOSIS_TRACES),
292            Blockchain::HarmonyShard0 => Some(&chains::HARMONY_SHARD_0),
293            Blockchain::Holesky => Some(&chains::HOLESKY),
294            Blockchain::HoleskyTokenTest => Some(&chains::HOLESKY_TOKEN_TEST),
295            Blockchain::Hyperliquid => Some(&chains::HYPERLIQUID),
296            Blockchain::HyperliquidTemp => Some(&chains::HYPERLIQUID_TEMP),
297            Blockchain::Ink => Some(&chains::INK),
298            Blockchain::InternalTestChain => Some(&chains::INTERNAL_TEST_CHAIN),
299            Blockchain::Kroma => Some(&chains::KROMA),
300            Blockchain::Linea => Some(&chains::LINEA),
301            Blockchain::Lisk => Some(&chains::LISK),
302            Blockchain::Lukso => Some(&chains::LUKSO),
303            Blockchain::LuksoTestnet => Some(&chains::LUKSO_TESTNET),
304            Blockchain::Manta => Some(&chains::MANTA),
305            Blockchain::Mantle => Some(&chains::MANTLE),
306            Blockchain::MegaethTestnet => Some(&chains::MEGAETH_TESTNET),
307            Blockchain::Merlin => Some(&chains::MERLIN),
308            Blockchain::Metall2 => Some(&chains::METALL2),
309            Blockchain::Metis => Some(&chains::METIS),
310            Blockchain::MevCommit => Some(&chains::MEV_COMMIT),
311            Blockchain::Mode => Some(&chains::MODE),
312            Blockchain::MonadTestnet => Some(&chains::MONAD_TESTNET),
313            Blockchain::MonadTestnetBackup => Some(&chains::MONAD_TESTNET_BACKUP),
314            Blockchain::MoonbaseAlpha => Some(&chains::MOONBASE_ALPHA),
315            Blockchain::Moonbeam => Some(&chains::MOONBEAM),
316            Blockchain::Morph => Some(&chains::MORPH),
317            Blockchain::MorphHolesky => Some(&chains::MORPH_HOLESKY),
318            Blockchain::Opbnb => Some(&chains::OPBNB),
319            Blockchain::Optimism => Some(&chains::OPTIMISM),
320            Blockchain::OptimismSepolia => Some(&chains::OPTIMISM_SEPOLIA),
321            Blockchain::PharosDevnet => Some(&chains::PHAROS_DEVNET),
322            Blockchain::Polygon => Some(&chains::POLYGON),
323            Blockchain::PolygonAmoy => Some(&chains::POLYGON_AMOY),
324            Blockchain::PolygonZkEvm => Some(&chains::POLYGON_ZKEVM),
325            Blockchain::Rootstock => Some(&chains::ROOTSTOCK),
326            Blockchain::Saakuru => Some(&chains::SAAKURU),
327            Blockchain::Scroll => Some(&chains::SCROLL),
328            Blockchain::Sepolia => Some(&chains::SEPOLIA),
329            Blockchain::ShimmerEvm => Some(&chains::SHIMMER_EVM),
330            Blockchain::Soneium => Some(&chains::SONEIUM),
331            Blockchain::Sophon => Some(&chains::SOPHON),
332            Blockchain::SophonTestnet => Some(&chains::SOPHON_TESTNET),
333            Blockchain::Superseed => Some(&chains::SUPERSEED),
334            Blockchain::Unichain => Some(&chains::UNICHAIN),
335            Blockchain::UnichainSepolia => Some(&chains::UNICHAIN_SEPOLIA),
336            Blockchain::Xdc => Some(&chains::XDC),
337            Blockchain::XdcTestnet => Some(&chains::XDC_TESTNET),
338            Blockchain::Zeta => Some(&chains::ZETA),
339            Blockchain::Zircuit => Some(&chains::ZIRCUIT),
340            Blockchain::ZKsync => Some(&chains::ZKSYNC),
341            Blockchain::Zora => Some(&chains::ZORA),
342        }
343    }
344}
345
346impl Display for Chain {
347    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
348        write!(f, "Chain(name={}, id={})", self.name, self.chain_id)
349    }
350}
351
352// Define a module to contain all the chain definitions.
353pub mod chains {
354    use std::sync::LazyLock;
355
356    use crate::defi::chain::{Blockchain, Chain};
357
358    pub static ABSTRACT: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Abstract, 2741));
359    pub static ARBITRUM: LazyLock<Chain> =
360        LazyLock::new(|| Chain::new(Blockchain::Arbitrum, 42161));
361    pub static ARBITRUM_NOVA: LazyLock<Chain> =
362        LazyLock::new(|| Chain::new(Blockchain::ArbitrumNova, 42170));
363    pub static ARBITRUM_SEPOLIA: LazyLock<Chain> =
364        LazyLock::new(|| Chain::new(Blockchain::ArbitrumSepolia, 421614));
365    pub static AURORA: LazyLock<Chain> =
366        LazyLock::new(|| Chain::new(Blockchain::Aurora, 1313161554));
367    pub static AVALANCHE: LazyLock<Chain> =
368        LazyLock::new(|| Chain::new(Blockchain::Avalanche, 43114));
369    pub static BASE: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Base, 8453));
370    pub static BASE_SEPOLIA: LazyLock<Chain> =
371        LazyLock::new(|| Chain::new(Blockchain::BaseSepolia, 84532));
372    pub static BERACHAIN: LazyLock<Chain> =
373        LazyLock::new(|| Chain::new(Blockchain::Berachain, 80094));
374    pub static BERACHAIN_BARTIO: LazyLock<Chain> =
375        LazyLock::new(|| Chain::new(Blockchain::BerachainBartio, 80085));
376    pub static BLAST: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Blast, 81457));
377    pub static BLAST_SEPOLIA: LazyLock<Chain> =
378        LazyLock::new(|| Chain::new(Blockchain::BlastSepolia, 168587773));
379    pub static BOBA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Boba, 288));
380    pub static BSC: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Bsc, 56));
381    pub static BSC_TESTNET: LazyLock<Chain> =
382        LazyLock::new(|| Chain::new(Blockchain::BscTestnet, 97));
383    pub static CELO: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Celo, 42220));
384    pub static CHILIZ: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Chiliz, 8888));
385    pub static CITREA_TESTNET: LazyLock<Chain> =
386        LazyLock::new(|| Chain::new(Blockchain::CitreaTestnet, 3333));
387    pub static CURTIS: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Curtis, 33111));
388    pub static CYBER: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Cyber, 7560));
389    pub static DARWINIA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Darwinia, 46));
390    pub static ETHEREUM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Ethereum, 1));
391    pub static FANTOM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Fantom, 250));
392    pub static FLARE: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Flare, 14));
393    pub static FRAXTAL: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Fraxtal, 252));
394    pub static FUJI: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Fuji, 43113));
395    pub static GALADRIEL_DEVNET: LazyLock<Chain> =
396        LazyLock::new(|| Chain::new(Blockchain::GaladrielDevnet, 696969));
397    pub static GNOSIS: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Gnosis, 100));
398    pub static GNOSIS_CHIADO: LazyLock<Chain> =
399        LazyLock::new(|| Chain::new(Blockchain::GnosisChiado, 10200));
400    // Chain ID 10300 is reserved for the public *Gnosis Traces* test-network. The value was
401    // previously set to 100 (Mainnet) which caused `Chain::from_chain_id(10300)` to return a
402    // `Chain` whose `chain_id` field did not match the requested ID. This led to confusing log
403    // output and could break caching keyed by the numeric identifier. We therefore align the
404    // static definition with the mapping used in `from_chain_id` (10300).
405    pub static GNOSIS_TRACES: LazyLock<Chain> =
406        LazyLock::new(|| Chain::new(Blockchain::GnosisTraces, 10300));
407    pub static HARMONY_SHARD_0: LazyLock<Chain> =
408        LazyLock::new(|| Chain::new(Blockchain::HarmonyShard0, 1666600000));
409    pub static HOLESKY: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Holesky, 17000));
410    // The Holesky *token test* network uses a dedicated chain-ID (17001) distinct from the main
411    // Holesky devnet (17000). Align this constant with the value returned from `from_chain_id`.
412    pub static HOLESKY_TOKEN_TEST: LazyLock<Chain> =
413        LazyLock::new(|| Chain::new(Blockchain::HoleskyTokenTest, 17001));
414    // Hyperliquid main & temp test networks live on low numeric identifiers (7979 / 7978).
415    // Using the correct small IDs avoids overflow issues in certain front-ends that assume
416    // EVM-style 32-bit chain IDs.
417    pub static HYPERLIQUID: LazyLock<Chain> =
418        LazyLock::new(|| Chain::new(Blockchain::Hyperliquid, 7979));
419    pub static HYPERLIQUID_TEMP: LazyLock<Chain> =
420        LazyLock::new(|| Chain::new(Blockchain::HyperliquidTemp, 7978));
421    // Align with mapping – 222 is the well–known chain-ID for the `Ink` network.
422    pub static INK: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Ink, 222));
423    // Use the `foundry`-style development chain-ID 13337 to match the lookup table above.
424    pub static INTERNAL_TEST_CHAIN: LazyLock<Chain> =
425        LazyLock::new(|| Chain::new(Blockchain::InternalTestChain, 13337));
426    pub static KROMA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Kroma, 255));
427    pub static LINEA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Linea, 59144));
428    pub static LISK: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Lisk, 501));
429    pub static LUKSO: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Lukso, 42));
430    pub static LUKSO_TESTNET: LazyLock<Chain> =
431        LazyLock::new(|| Chain::new(Blockchain::LuksoTestnet, 4201));
432    pub static MANTA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Manta, 169));
433    pub static MANTLE: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Mantle, 5000));
434    pub static MEGAETH_TESTNET: LazyLock<Chain> =
435        LazyLock::new(|| Chain::new(Blockchain::MegaethTestnet, 777));
436    pub static MERLIN: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Merlin, 4200));
437    pub static METALL2: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Metall2, 90));
438    pub static METIS: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Metis, 1088));
439    pub static MEV_COMMIT: LazyLock<Chain> =
440        LazyLock::new(|| Chain::new(Blockchain::MevCommit, 11));
441    pub static MODE: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Mode, 34443));
442    pub static MONAD_TESTNET: LazyLock<Chain> =
443        LazyLock::new(|| Chain::new(Blockchain::MonadTestnet, 2323));
444    pub static MONAD_TESTNET_BACKUP: LazyLock<Chain> =
445        LazyLock::new(|| Chain::new(Blockchain::MonadTestnetBackup, 2358));
446    pub static MOONBASE_ALPHA: LazyLock<Chain> =
447        LazyLock::new(|| Chain::new(Blockchain::MoonbaseAlpha, 1287));
448    pub static MOONBEAM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Moonbeam, 1284));
449    pub static MORPH: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Morph, 2710));
450    pub static MORPH_HOLESKY: LazyLock<Chain> =
451        LazyLock::new(|| Chain::new(Blockchain::MorphHolesky, 2710111));
452    pub static OPBNB: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Opbnb, 204));
453    pub static OPTIMISM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Optimism, 10));
454    pub static OPTIMISM_SEPOLIA: LazyLock<Chain> =
455        LazyLock::new(|| Chain::new(Blockchain::OptimismSepolia, 11155420));
456    pub static PHAROS_DEVNET: LazyLock<Chain> =
457        LazyLock::new(|| Chain::new(Blockchain::PharosDevnet, 1337));
458    pub static POLYGON: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Polygon, 137));
459    pub static POLYGON_AMOY: LazyLock<Chain> =
460        LazyLock::new(|| Chain::new(Blockchain::PolygonAmoy, 80002));
461    pub static POLYGON_ZKEVM: LazyLock<Chain> =
462        LazyLock::new(|| Chain::new(Blockchain::PolygonZkEvm, 1101));
463    pub static ROOTSTOCK: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Rootstock, 30));
464    pub static SAAKURU: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Saakuru, 1204));
465    pub static SCROLL: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Scroll, 534352));
466    pub static SEPOLIA: LazyLock<Chain> =
467        LazyLock::new(|| Chain::new(Blockchain::Sepolia, 11155111));
468    pub static SHIMMER_EVM: LazyLock<Chain> =
469        LazyLock::new(|| Chain::new(Blockchain::ShimmerEvm, 148));
470    pub static SONEIUM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Soneium, 109));
471    pub static SOPHON: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Sophon, 138));
472    pub static SOPHON_TESTNET: LazyLock<Chain> =
473        LazyLock::new(|| Chain::new(Blockchain::SophonTestnet, 139));
474    pub static SUPERSEED: LazyLock<Chain> =
475        LazyLock::new(|| Chain::new(Blockchain::Superseed, 10001));
476    pub static UNICHAIN: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Unichain, 9999));
477    pub static UNICHAIN_SEPOLIA: LazyLock<Chain> =
478        LazyLock::new(|| Chain::new(Blockchain::UnichainSepolia, 9997));
479    pub static XDC: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Xdc, 50));
480    pub static XDC_TESTNET: LazyLock<Chain> =
481        LazyLock::new(|| Chain::new(Blockchain::XdcTestnet, 51));
482    pub static ZETA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Zeta, 7000));
483    pub static ZIRCUIT: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Zircuit, 78600));
484    pub static ZKSYNC: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::ZKsync, 324));
485    pub static ZORA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Zora, 7777777));
486}
487
488#[cfg(test)]
489mod tests {
490    use rstest::rstest;
491
492    use super::*;
493
494    #[rstest]
495    fn test_ethereum_chain() {
496        let eth_chain = chains::ETHEREUM.clone();
497        assert_eq!(eth_chain.to_string(), "Chain(name=Ethereum, id=1)");
498        assert_eq!(eth_chain.name, Blockchain::Ethereum);
499        assert_eq!(eth_chain.chain_id, 1);
500        assert_eq!(eth_chain.hypersync_url.as_str(), "https://1.hypersync.xyz")
501    }
502
503    #[rstest]
504    fn test_arbitrum_chain() {
505        let arbitrum_chain = chains::ARBITRUM.clone();
506        assert_eq!(arbitrum_chain.to_string(), "Chain(name=Arbitrum, id=42161)");
507        assert_eq!(arbitrum_chain.name, Blockchain::Arbitrum);
508        assert_eq!(arbitrum_chain.chain_id, 42161);
509        assert_eq!(
510            arbitrum_chain.hypersync_url.as_str(),
511            "https://42161.hypersync.xyz"
512        );
513    }
514
515    #[rstest]
516    fn test_chain_constructor() {
517        let chain = Chain::new(Blockchain::Polygon, 137);
518
519        assert_eq!(chain.name, Blockchain::Polygon);
520        assert_eq!(chain.chain_id, 137);
521        assert_eq!(chain.hypersync_url, "https://137.hypersync.xyz");
522        assert!(chain.rpc_url.is_none());
523        assert_eq!(chain.native_currency_decimals, 18);
524    }
525
526    #[rstest]
527    fn test_chain_set_rpc_url() {
528        let mut chain = Chain::new(Blockchain::Ethereum, 1);
529        assert!(chain.rpc_url.is_none());
530
531        let rpc_url = "https://mainnet.infura.io/v3/YOUR-PROJECT-ID".to_string();
532        chain.set_rpc_url(rpc_url.clone());
533
534        assert_eq!(chain.rpc_url, Some(rpc_url));
535    }
536
537    #[rstest]
538    fn test_chain_from_chain_id_valid() {
539        // Test some known chain IDs
540        assert!(Chain::from_chain_id(1).is_some()); // Ethereum
541        assert!(Chain::from_chain_id(137).is_some()); // Polygon
542        assert!(Chain::from_chain_id(42161).is_some()); // Arbitrum
543        assert!(Chain::from_chain_id(8453).is_some()); // Base
544
545        // Verify specific chain
546        let eth_chain = Chain::from_chain_id(1).unwrap();
547        assert_eq!(eth_chain.name, Blockchain::Ethereum);
548        assert_eq!(eth_chain.chain_id, 1);
549    }
550
551    #[rstest]
552    fn test_chain_from_chain_id_invalid() {
553        // Test unknown chain ID
554        assert!(Chain::from_chain_id(999999).is_none());
555        assert!(Chain::from_chain_id(0).is_none());
556    }
557
558    #[rstest]
559    fn test_chain_from_chain_name_valid() {
560        // Test some known chain names
561        assert!(Chain::from_chain_name("Ethereum").is_some());
562        assert!(Chain::from_chain_name("Polygon").is_some());
563        assert!(Chain::from_chain_name("Arbitrum").is_some());
564        assert!(Chain::from_chain_name("Base").is_some());
565
566        // Verify specific chain
567        let eth_chain = Chain::from_chain_name("Ethereum").unwrap();
568        assert_eq!(eth_chain.name, Blockchain::Ethereum);
569        assert_eq!(eth_chain.chain_id, 1);
570
571        // Verify ArbitrumNova (compound name)
572        let arbitrum_nova_chain = Chain::from_chain_name("ArbitrumNova").unwrap();
573        assert_eq!(arbitrum_nova_chain.name, Blockchain::ArbitrumNova);
574        assert_eq!(arbitrum_nova_chain.chain_id, 42170);
575
576        // Verify BSC (abbreviated name)
577        let bsc_chain = Chain::from_chain_name("Bsc").unwrap();
578        assert_eq!(bsc_chain.name, Blockchain::Bsc);
579        assert_eq!(bsc_chain.chain_id, 56);
580    }
581
582    #[rstest]
583    fn test_chain_from_chain_name_invalid() {
584        // Test unknown chain names
585        assert!(Chain::from_chain_name("InvalidChain").is_none());
586        assert!(Chain::from_chain_name("").is_none());
587        assert!(Chain::from_chain_name("NonExistentNetwork").is_none());
588    }
589
590    #[rstest]
591    fn test_chain_from_chain_name_case_sensitive() {
592        // Test case sensitivity - should be case insensitive
593        assert!(Chain::from_chain_name("Ethereum").is_some());
594        assert!(Chain::from_chain_name("ethereum").is_some()); // lowercase
595        assert!(Chain::from_chain_name("ETHEREUM").is_some()); // uppercase
596        assert!(Chain::from_chain_name("EtHeReUm").is_some()); // mixed case
597
598        assert!(Chain::from_chain_name("Arbitrum").is_some());
599        assert!(Chain::from_chain_name("arbitrum").is_some()); // lowercase
600    }
601
602    #[rstest]
603    fn test_chain_from_chain_name_consistency_with_id() {
604        // Test that from_chain_name and from_chain_id return the same chain instances
605        let chains_to_test = [
606            ("Ethereum", 1),
607            ("Polygon", 137),
608            ("Arbitrum", 42161),
609            ("Base", 8453),
610            ("Optimism", 10),
611            ("Avalanche", 43114),
612            ("Fantom", 250),
613            ("Bsc", 56),
614        ];
615
616        for (name, id) in chains_to_test {
617            let chain_by_name =
618                Chain::from_chain_name(name).unwrap_or_else(|| panic!("Chain {name} should exist"));
619            let chain_by_id =
620                Chain::from_chain_id(id).unwrap_or_else(|| panic!("Chain {name} should exist"));
621
622            // Should return the same chain instance
623            assert_eq!(chain_by_name.name, chain_by_id.name);
624            assert_eq!(chain_by_name.chain_id, chain_by_id.chain_id);
625            assert_eq!(chain_by_name.hypersync_url, chain_by_id.hypersync_url);
626        }
627    }
628}