Skip to main content

nautilus_model/defi/
chain.rs

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