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