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