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