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            88888 => Some(&chains::CHILIZ),
242            5115 => 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            999 => Some(&chains::HYPERLIQUID),
259            7978 => Some(&chains::HYPERLIQUID_TEMP),
260            57073 => Some(&chains::INK),
261            13337 => Some(&chains::INTERNAL_TEST_CHAIN),
262            255 => Some(&chains::KROMA),
263            59144 => Some(&chains::LINEA),
264            1135 => 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            1750 => Some(&chains::METALL2),
272            1088 => Some(&chains::METIS),
273            11 => Some(&chains::MEV_COMMIT),
274            34443 => Some(&chains::MODE),
275            10143 => Some(&chains::MONAD_TESTNET),
276            2358 => Some(&chains::MONAD_TESTNET_BACKUP),
277            1287 => Some(&chains::MOONBASE_ALPHA),
278            1284 => Some(&chains::MOONBEAM),
279            2818 => 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            1868 => Some(&chains::SONEIUM),
294            50104 => Some(&chains::SOPHON),
295            139 => Some(&chains::SOPHON_TESTNET),
296            5330 => Some(&chains::SUPERSEDE),
297            130 => 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, 88888));
440    pub static CITREA_TESTNET: LazyLock<Chain> =
441        LazyLock::new(|| Chain::new(Blockchain::CitreaTestnet, 5115));
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    pub static HYPERLIQUID: LazyLock<Chain> =
470        LazyLock::new(|| Chain::new(Blockchain::Hyperliquid, 999));
471    pub static HYPERLIQUID_TEMP: LazyLock<Chain> =
472        LazyLock::new(|| Chain::new(Blockchain::HyperliquidTemp, 7978));
473    pub static INK: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Ink, 57073));
474    // Use the `foundry`-style development chain-ID 13337 to match the lookup table above.
475    pub static INTERNAL_TEST_CHAIN: LazyLock<Chain> =
476        LazyLock::new(|| Chain::new(Blockchain::InternalTestChain, 13337));
477    pub static KROMA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Kroma, 255));
478    pub static LINEA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Linea, 59144));
479    pub static LISK: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Lisk, 1135));
480    pub static LUKSO: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Lukso, 42));
481    pub static LUKSO_TESTNET: LazyLock<Chain> =
482        LazyLock::new(|| Chain::new(Blockchain::LuksoTestnet, 4201));
483    pub static MANTA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Manta, 169));
484    pub static MANTLE: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Mantle, 5000));
485    pub static MEGAETH_TESTNET: LazyLock<Chain> =
486        LazyLock::new(|| Chain::new(Blockchain::MegaethTestnet, 777));
487    pub static MERLIN: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Merlin, 4200));
488    pub static METALL2: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Metall2, 1750));
489    pub static METIS: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Metis, 1088));
490    pub static MEV_COMMIT: LazyLock<Chain> =
491        LazyLock::new(|| Chain::new(Blockchain::MevCommit, 11));
492    pub static MODE: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Mode, 34443));
493    pub static MONAD_TESTNET: LazyLock<Chain> =
494        LazyLock::new(|| Chain::new(Blockchain::MonadTestnet, 10143));
495    pub static MONAD_TESTNET_BACKUP: LazyLock<Chain> =
496        LazyLock::new(|| Chain::new(Blockchain::MonadTestnetBackup, 2358));
497    pub static MOONBASE_ALPHA: LazyLock<Chain> =
498        LazyLock::new(|| Chain::new(Blockchain::MoonbaseAlpha, 1287));
499    pub static MOONBEAM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Moonbeam, 1284));
500    pub static MORPH: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Morph, 2818));
501    pub static MORPH_HOLESKY: LazyLock<Chain> =
502        LazyLock::new(|| Chain::new(Blockchain::MorphHolesky, 2_710_111));
503    pub static OPBNB: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Opbnb, 204));
504    pub static OPTIMISM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Optimism, 10));
505    pub static OPTIMISM_SEPOLIA: LazyLock<Chain> =
506        LazyLock::new(|| Chain::new(Blockchain::OptimismSepolia, 11_155_420));
507    pub static PHAROS_DEVNET: LazyLock<Chain> =
508        LazyLock::new(|| Chain::new(Blockchain::PharosDevnet, 1337));
509    pub static POLYGON: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Polygon, 137));
510    pub static POLYGON_AMOY: LazyLock<Chain> =
511        LazyLock::new(|| Chain::new(Blockchain::PolygonAmoy, 80002));
512    pub static POLYGON_ZKEVM: LazyLock<Chain> =
513        LazyLock::new(|| Chain::new(Blockchain::PolygonZkEvm, 1101));
514    pub static ROOTSTOCK: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Rootstock, 30));
515    pub static SAAKURU: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Saakuru, 1204));
516    pub static SCROLL: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Scroll, 534_352));
517    pub static SEPOLIA: LazyLock<Chain> =
518        LazyLock::new(|| Chain::new(Blockchain::Sepolia, 11_155_111));
519    pub static SHIMMER_EVM: LazyLock<Chain> =
520        LazyLock::new(|| Chain::new(Blockchain::ShimmerEvm, 148));
521    pub static SONEIUM: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Soneium, 1868));
522    pub static SOPHON: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Sophon, 50104));
523    pub static SOPHON_TESTNET: LazyLock<Chain> =
524        LazyLock::new(|| Chain::new(Blockchain::SophonTestnet, 139));
525    pub static SUPERSEDE: LazyLock<Chain> =
526        LazyLock::new(|| Chain::new(Blockchain::Superseed, 5330));
527    pub static UNICHAIN: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Unichain, 130));
528    pub static UNICHAIN_SEPOLIA: LazyLock<Chain> =
529        LazyLock::new(|| Chain::new(Blockchain::UnichainSepolia, 9997));
530    pub static XDC: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Xdc, 50));
531    pub static XDC_TESTNET: LazyLock<Chain> =
532        LazyLock::new(|| Chain::new(Blockchain::XdcTestnet, 51));
533    pub static ZETA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Zeta, 7000));
534    pub static ZIRCUIT: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Zircuit, 78600));
535    pub static ZKSYNC: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::ZKsync, 324));
536    pub static ZORA: LazyLock<Chain> = LazyLock::new(|| Chain::new(Blockchain::Zora, 7_777_777));
537}
538
539#[cfg(test)]
540mod tests {
541    use rstest::rstest;
542
543    use super::*;
544
545    #[rstest]
546    fn test_ethereum_chain() {
547        let eth_chain = chains::ETHEREUM.clone();
548        assert_eq!(eth_chain.to_string(), "Chain(name=Ethereum, id=1)");
549        assert_eq!(eth_chain.name, Blockchain::Ethereum);
550        assert_eq!(eth_chain.chain_id, 1);
551        assert_eq!(eth_chain.hypersync_url.as_str(), "https://1.hypersync.xyz");
552
553        // Test native currency
554        let currency = eth_chain.native_currency();
555        assert_eq!(currency.code, "ETH");
556        assert_eq!(currency.precision, 18);
557        assert_eq!(currency.name, "Ethereum");
558    }
559
560    #[rstest]
561    fn test_arbitrum_chain() {
562        let arbitrum_chain = chains::ARBITRUM.clone();
563        assert_eq!(arbitrum_chain.to_string(), "Chain(name=Arbitrum, id=42161)");
564        assert_eq!(arbitrum_chain.name, Blockchain::Arbitrum);
565        assert_eq!(arbitrum_chain.chain_id, 42161);
566        assert_eq!(
567            arbitrum_chain.hypersync_url.as_str(),
568            "https://42161.hypersync.xyz"
569        );
570
571        // Test native currency (Arbitrum uses ETH)
572        let currency = arbitrum_chain.native_currency();
573        assert_eq!(currency.code, "ETH");
574        assert_eq!(currency.precision, 18);
575        assert_eq!(currency.name, "Ethereum");
576    }
577
578    #[rstest]
579    fn test_chain_constructor() {
580        let chain = Chain::new(Blockchain::Polygon, 137);
581
582        assert_eq!(chain.name, Blockchain::Polygon);
583        assert_eq!(chain.chain_id, 137);
584        assert_eq!(chain.hypersync_url, "https://137.hypersync.xyz");
585        assert!(chain.rpc_url.is_none());
586        assert_eq!(chain.native_currency_decimals, 18);
587    }
588
589    #[rstest]
590    fn test_chain_set_rpc_url() {
591        let mut chain = Chain::new(Blockchain::Ethereum, 1);
592        assert!(chain.rpc_url.is_none());
593
594        let rpc_url = "https://mainnet.infura.io/v3/YOUR-PROJECT-ID".to_string();
595        chain.set_rpc_url(rpc_url.clone());
596
597        assert_eq!(chain.rpc_url, Some(rpc_url));
598    }
599
600    #[rstest]
601    fn test_chain_from_chain_id_valid() {
602        // Test some known chain IDs
603        assert!(Chain::from_chain_id(1).is_some()); // Ethereum
604        assert!(Chain::from_chain_id(137).is_some()); // Polygon
605        assert!(Chain::from_chain_id(42161).is_some()); // Arbitrum
606        assert!(Chain::from_chain_id(8453).is_some()); // Base
607
608        // Verify specific chain
609        let eth_chain = Chain::from_chain_id(1).unwrap();
610        assert_eq!(eth_chain.name, Blockchain::Ethereum);
611        assert_eq!(eth_chain.chain_id, 1);
612    }
613
614    #[rstest]
615    #[case(Blockchain::Chiliz, 88888)]
616    #[case(Blockchain::CitreaTestnet, 5115)]
617    #[case(Blockchain::Hyperliquid, 999)]
618    #[case(Blockchain::Ink, 57073)]
619    #[case(Blockchain::Lisk, 1135)]
620    #[case(Blockchain::Metall2, 1750)]
621    #[case(Blockchain::MonadTestnet, 10143)]
622    #[case(Blockchain::Morph, 2818)]
623    #[case(Blockchain::Soneium, 1868)]
624    #[case(Blockchain::Sophon, 50104)]
625    #[case(Blockchain::Superseed, 5330)]
626    #[case(Blockchain::Unichain, 130)]
627    fn test_chain_ids_match_network_chain_ids(#[case] blockchain: Blockchain, #[case] id: u32) {
628        let chain_by_name = Chain::from_chain_name(&blockchain.to_string()).unwrap();
629        let chain_by_id = Chain::from_chain_id(id).unwrap();
630
631        assert_eq!(chain_by_name.name, blockchain);
632        assert_eq!(chain_by_name.chain_id, id);
633        assert_eq!(
634            chain_by_name.hypersync_url,
635            format!("https://{id}.hypersync.xyz")
636        );
637        assert_eq!(chain_by_id.name, blockchain);
638    }
639
640    #[rstest]
641    fn test_chain_from_chain_id_invalid() {
642        // Test unknown chain ID
643        assert!(Chain::from_chain_id(999_999).is_none());
644        assert!(Chain::from_chain_id(0).is_none());
645    }
646
647    #[rstest]
648    fn test_chain_from_chain_name_valid() {
649        // Test some known chain names
650        assert!(Chain::from_chain_name("Ethereum").is_some());
651        assert!(Chain::from_chain_name("Polygon").is_some());
652        assert!(Chain::from_chain_name("Arbitrum").is_some());
653        assert!(Chain::from_chain_name("Base").is_some());
654
655        // Verify specific chain
656        let eth_chain = Chain::from_chain_name("Ethereum").unwrap();
657        assert_eq!(eth_chain.name, Blockchain::Ethereum);
658        assert_eq!(eth_chain.chain_id, 1);
659
660        // Verify ArbitrumNova (compound name)
661        let arbitrum_nova_chain = Chain::from_chain_name("ArbitrumNova").unwrap();
662        assert_eq!(arbitrum_nova_chain.name, Blockchain::ArbitrumNova);
663        assert_eq!(arbitrum_nova_chain.chain_id, 42170);
664
665        // Verify BSC (abbreviated name)
666        let bsc_chain = Chain::from_chain_name("Bsc").unwrap();
667        assert_eq!(bsc_chain.name, Blockchain::Bsc);
668        assert_eq!(bsc_chain.chain_id, 56);
669    }
670
671    #[rstest]
672    fn test_chain_from_chain_name_invalid() {
673        // Test unknown chain names
674        assert!(Chain::from_chain_name("InvalidChain").is_none());
675        assert!(Chain::from_chain_name("").is_none());
676        assert!(Chain::from_chain_name("NonExistentNetwork").is_none());
677    }
678
679    #[rstest]
680    fn test_chain_from_chain_name_case_sensitive() {
681        // Test case sensitivity - should be case insensitive
682        assert!(Chain::from_chain_name("Ethereum").is_some());
683        assert!(Chain::from_chain_name("ethereum").is_some()); // lowercase
684        assert!(Chain::from_chain_name("ETHEREUM").is_some()); // uppercase
685        assert!(Chain::from_chain_name("EtHeReUm").is_some()); // mixed case
686
687        assert!(Chain::from_chain_name("Arbitrum").is_some());
688        assert!(Chain::from_chain_name("arbitrum").is_some()); // lowercase
689    }
690
691    #[rstest]
692    fn test_chain_from_chain_name_consistency_with_id() {
693        // Test that from_chain_name and from_chain_id return the same chain instances
694        let chains_to_test = [
695            ("Ethereum", 1),
696            ("Polygon", 137),
697            ("Arbitrum", 42161),
698            ("Base", 8453),
699            ("Optimism", 10),
700            ("Avalanche", 43114),
701            ("Fantom", 250),
702            ("Bsc", 56),
703        ];
704
705        for (name, id) in chains_to_test {
706            let chain_by_name =
707                Chain::from_chain_name(name).unwrap_or_else(|| panic!("Chain {name} should exist"));
708            let chain_by_id =
709                Chain::from_chain_id(id).unwrap_or_else(|| panic!("Chain {name} should exist"));
710
711            // Should return the same chain instance
712            assert_eq!(chain_by_name.name, chain_by_id.name);
713            assert_eq!(chain_by_name.chain_id, chain_by_id.chain_id);
714            assert_eq!(chain_by_name.hypersync_url, chain_by_id.hypersync_url);
715        }
716    }
717}