Skip to main content

cow_chains/
chains.rs

1//! Extended chain enums, chain info types, and utility functions.
2//!
3//! Mirrors the `TypeScript` SDK `chains` package. This module is the
4//! authoritative source for chain metadata beyond what [`SupportedChainId`]
5//! provides — it covers bridge-only chains, non-EVM chains (BTC, Solana),
6//! rich per-chain metadata ([`ChainInfo`]), and classification helpers.
7//!
8//! # Key items
9//!
10//! | Item | Purpose |
11//! |---|---|
12//! | [`EvmChains`] | Superset of [`SupportedChainId`] including bridge-only EVM chains |
13//! | [`NonEvmChains`] | BTC and Solana chain IDs |
14//! | [`AdditionalTargetChainId`] | Bridge destination chains not in [`SupportedChainId`] |
15//! | [`ChainInfo`] | Rich per-chain metadata (RPC URLs, contracts, tokens, …) |
16//! | [`get_chain_info`] | Look up [`ChainInfo`] by numeric chain ID |
17//! | [`is_evm_chain`] / [`is_btc_chain`] | Chain classification helpers |
18//! | [`all_supported_chains`] / [`all_chain_ids`] | Iteration helpers |
19
20use alloy_primitives::{Address, U256};
21use serde::{Deserialize, Serialize};
22
23/// Per-chain address mapping for supported chains.
24pub type AddressPerChain = Vec<(super::chain::SupportedChainId, Address)>;
25
26/// Per-chain base URL mapping for supported chains.
27pub type ApiBaseUrls = Vec<(super::chain::SupportedChainId, String)>;
28
29use super::chain::SupportedChainId;
30
31// ── CDN paths ────────────────────────────────────────────────────────────────
32
33/// Base CDN path for SDK files.
34pub const RAW_FILES_PATH: &str = "https://files.cow.fi/cow-sdk";
35
36/// CDN path for chain data.
37pub const RAW_CHAINS_FILES_PATH: &str = "https://files.cow.fi/cow-sdk/chains";
38
39/// CDN path for token list images.
40pub const TOKEN_LIST_IMAGES_PATH: &str = "https://files.cow.fi/token-lists/images";
41
42// ── Chain enums ──────────────────────────────────────────────────────────────
43
44/// All EVM chains supported by `CoW` Protocol or available for bridging.
45///
46/// This is a superset of [`SupportedChainId`] -- it includes bridge-only chains
47/// like Optimism.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
49#[repr(u64)]
50pub enum EvmChains {
51    /// Ethereum mainnet (chain ID 1).
52    Mainnet = 1,
53    /// Optimism (chain ID 10).
54    Optimism = 10,
55    /// BNB Smart Chain (chain ID 56).
56    Bnb = 56,
57    /// Gnosis Chain (chain ID 100).
58    GnosisChain = 100,
59    /// Polygon `PoS` (chain ID 137).
60    Polygon = 137,
61    /// Base (chain ID 8453).
62    Base = 8_453,
63    /// Plasma (chain ID 9745).
64    Plasma = 9_745,
65    /// Arbitrum One (chain ID 42161).
66    ArbitrumOne = 42_161,
67    /// Avalanche C-Chain (chain ID 43114).
68    Avalanche = 43_114,
69    /// Ink (chain ID 57073).
70    Ink = 57_073,
71    /// Linea (chain ID 59144).
72    Linea = 59_144,
73    /// Ethereum Sepolia testnet (chain ID 11155111).
74    Sepolia = 11_155_111,
75}
76
77impl EvmChains {
78    /// Return the numeric EIP-155 chain ID.
79    ///
80    /// # Returns
81    ///
82    /// The `u64` chain ID.
83    #[must_use]
84    pub const fn as_u64(self) -> u64 {
85        self as u64
86    }
87
88    /// Try to construct an [`EvmChains`] from a raw chain ID.
89    ///
90    /// # Arguments
91    ///
92    /// * `chain_id` — the numeric EIP-155 chain ID.
93    ///
94    /// # Returns
95    ///
96    /// `Some(variant)` if `chain_id` is a known EVM chain, `None` otherwise.
97    #[must_use]
98    pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
99        match chain_id {
100            1 => Some(Self::Mainnet),
101            10 => Some(Self::Optimism),
102            56 => Some(Self::Bnb),
103            100 => Some(Self::GnosisChain),
104            137 => Some(Self::Polygon),
105            8_453 => Some(Self::Base),
106            9_745 => Some(Self::Plasma),
107            42_161 => Some(Self::ArbitrumOne),
108            43_114 => Some(Self::Avalanche),
109            57_073 => Some(Self::Ink),
110            59_144 => Some(Self::Linea),
111            11_155_111 => Some(Self::Sepolia),
112            _ => None,
113        }
114    }
115}
116
117/// All non-EVM chains available for bridging only.
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
119#[repr(u64)]
120pub enum NonEvmChains {
121    /// Bitcoin (custom internal ID `1_000_000_000`).
122    Bitcoin = 1_000_000_000,
123    /// Solana (custom internal ID `1_000_000_001`).
124    Solana = 1_000_000_001,
125}
126
127impl NonEvmChains {
128    /// Return the numeric chain ID.
129    ///
130    /// # Returns
131    ///
132    /// The `u64` chain ID.
133    #[must_use]
134    pub const fn as_u64(self) -> u64 {
135        self as u64
136    }
137
138    /// Try to construct a [`NonEvmChains`] from a raw chain ID.
139    ///
140    /// # Arguments
141    ///
142    /// * `chain_id` — the numeric chain ID.
143    ///
144    /// # Returns
145    ///
146    /// `Some(variant)` if `chain_id` is a known non-EVM chain, `None` otherwise.
147    #[must_use]
148    pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
149        match chain_id {
150            1_000_000_000 => Some(Self::Bitcoin),
151            1_000_000_001 => Some(Self::Solana),
152            _ => None,
153        }
154    }
155}
156
157/// Chains where you can buy tokens using bridge functionality but that are not
158/// directly supported by `CoW` Protocol for selling.
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
160#[repr(u64)]
161pub enum AdditionalTargetChainId {
162    /// Optimism (chain ID 10).
163    Optimism = 10,
164    /// Bitcoin (custom internal ID `1_000_000_000`).
165    Bitcoin = 1_000_000_000,
166    /// Solana (custom internal ID `1_000_000_001`).
167    Solana = 1_000_000_001,
168}
169
170impl AdditionalTargetChainId {
171    /// Return the numeric chain ID.
172    ///
173    /// # Returns
174    ///
175    /// The `u64` chain ID.
176    #[must_use]
177    pub const fn as_u64(self) -> u64 {
178        self as u64
179    }
180
181    /// Try to construct an [`AdditionalTargetChainId`] from a raw chain ID.
182    ///
183    /// # Arguments
184    ///
185    /// * `chain_id` — the numeric chain ID.
186    ///
187    /// # Returns
188    ///
189    /// `Some(variant)` if `chain_id` is a known additional target, `None` otherwise.
190    #[must_use]
191    pub const fn try_from_u64(chain_id: u64) -> Option<Self> {
192        match chain_id {
193            10 => Some(Self::Optimism),
194            1_000_000_000 => Some(Self::Bitcoin),
195            1_000_000_001 => Some(Self::Solana),
196            _ => None,
197        }
198    }
199
200    /// Return all additional target chain IDs.
201    ///
202    /// # Returns
203    ///
204    /// A static slice of every [`AdditionalTargetChainId`] variant.
205    #[must_use]
206    pub const fn all() -> &'static [Self] {
207        &[Self::Optimism, Self::Bitcoin, Self::Solana]
208    }
209}
210
211/// A chain ID that is either a [`SupportedChainId`] or an
212/// [`AdditionalTargetChainId`].
213///
214/// This union covers all chains where you can either trade directly or bridge
215/// to.
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217pub enum TargetChainId {
218    /// A chain supported directly by `CoW` Protocol.
219    Supported(SupportedChainId),
220    /// A bridge-only target chain.
221    Additional(AdditionalTargetChainId),
222}
223
224impl TargetChainId {
225    /// Return the numeric chain ID.
226    ///
227    /// # Returns
228    ///
229    /// The `u64` chain ID from the inner variant.
230    #[must_use]
231    pub const fn as_u64(&self) -> u64 {
232        match self {
233            Self::Supported(c) => c.as_u64(),
234            Self::Additional(c) => c.as_u64(),
235        }
236    }
237}
238
239// ── Chain info types ─────────────────────────────────────────────────────────
240
241/// A themed image with light and dark variants.
242#[derive(Debug, Clone)]
243pub struct ThemedImage {
244    /// URL for the light theme logo.
245    pub light: &'static str,
246    /// URL for the dark theme logo.
247    pub dark: &'static str,
248}
249
250/// A named URL.
251#[derive(Debug, Clone)]
252pub struct WebUrl {
253    /// Display name.
254    pub name: &'static str,
255    /// The URL.
256    pub url: &'static str,
257}
258
259/// An on-chain contract reference with an optional creation block.
260#[derive(Debug, Clone, Copy)]
261pub struct ChainContract {
262    /// The contract address.
263    pub address: Address,
264    /// The block at which the contract was created, if known.
265    pub block_created: Option<u64>,
266}
267
268/// Well-known contracts on an EVM chain.
269#[derive(Debug, Clone)]
270pub struct ChainContracts {
271    /// Multicall3 contract.
272    pub multicall3: Option<ChainContract>,
273    /// ENS registry contract.
274    pub ens_registry: Option<ChainContract>,
275    /// ENS universal resolver contract.
276    pub ens_universal_resolver: Option<ChainContract>,
277}
278
279/// RPC URL configuration for an EVM chain.
280#[derive(Debug, Clone)]
281pub struct ChainRpcUrls {
282    /// HTTP RPC endpoints.
283    pub http: &'static [&'static str],
284    /// WebSocket RPC endpoints (optional).
285    pub web_socket: Option<&'static [&'static str]>,
286}
287
288/// Token info used in chain metadata.
289///
290/// Uses a string address rather than `alloy_primitives::Address` because
291/// non-EVM chains (Bitcoin, Solana) have non-hex addresses.
292#[derive(Debug, Clone)]
293pub struct ChainTokenInfo {
294    /// Chain-specific ID.
295    pub chain_id: u64,
296    /// Token address as a string.
297    pub address: &'static str,
298    /// Decimal places.
299    pub decimals: u8,
300    /// Token name.
301    pub name: &'static str,
302    /// Token symbol.
303    pub symbol: &'static str,
304    /// Logo URL, if available.
305    pub logo_url: Option<&'static str>,
306}
307
308/// Metadata for an EVM chain.
309#[derive(Debug, Clone)]
310pub struct EvmChainInfo {
311    /// The EIP-155 chain ID.
312    pub id: u64,
313    /// Display label.
314    pub label: &'static str,
315    /// EIP-155 label (used for wallet connections).
316    pub eip155_label: &'static str,
317    /// ERC-3770 address prefix.
318    pub address_prefix: &'static str,
319    /// Native currency token info.
320    pub native_currency: ChainTokenInfo,
321    /// Whether this is a testnet.
322    pub is_testnet: bool,
323    /// Brand color for UI display.
324    pub color: &'static str,
325    /// Chain logo.
326    pub logo: ThemedImage,
327    /// Chain website.
328    pub website: WebUrl,
329    /// Chain documentation.
330    pub docs: WebUrl,
331    /// Block explorer.
332    pub block_explorer: WebUrl,
333    /// Bridges.
334    pub bridges: &'static [WebUrl],
335    /// Well-known contracts.
336    pub contracts: ChainContracts,
337    /// Default RPC URLs.
338    pub rpc_urls: ChainRpcUrls,
339    /// Whether this chain is zkSync-based.
340    pub is_zk_sync: bool,
341    /// Whether this chain is under development.
342    pub is_under_development: bool,
343    /// Whether this chain is deprecated (no new trading).
344    pub is_deprecated: bool,
345}
346
347/// Metadata for a non-EVM chain (e.g. Bitcoin, Solana).
348#[derive(Debug, Clone)]
349pub struct NonEvmChainInfo {
350    /// Internal chain ID.
351    pub id: u64,
352    /// Display label.
353    pub label: &'static str,
354    /// Address prefix.
355    pub address_prefix: &'static str,
356    /// Native currency info.
357    pub native_currency: ChainTokenInfo,
358    /// Whether this is a testnet.
359    pub is_testnet: bool,
360    /// Brand color for UI display.
361    pub color: &'static str,
362    /// Chain logo.
363    pub logo: ThemedImage,
364    /// Chain website.
365    pub website: WebUrl,
366    /// Chain documentation.
367    pub docs: WebUrl,
368    /// Block explorer.
369    pub block_explorer: WebUrl,
370    /// Whether this chain is under development.
371    pub is_under_development: bool,
372    /// Whether this chain is deprecated.
373    pub is_deprecated: bool,
374}
375
376/// A chain on the network -- either an EVM chain or a non-EVM chain.
377#[derive(Debug, Clone)]
378pub enum ChainInfo {
379    /// An EVM-compatible chain.
380    Evm(EvmChainInfo),
381    /// A non-EVM chain (e.g. Bitcoin, Solana).
382    NonEvm(NonEvmChainInfo),
383}
384
385impl ChainInfo {
386    /// Return the chain ID.
387    ///
388    /// # Returns
389    ///
390    /// The `u64` chain ID from the inner variant.
391    #[must_use]
392    pub const fn id(&self) -> u64 {
393        match self {
394            Self::Evm(info) => info.id,
395            Self::NonEvm(info) => info.id,
396        }
397    }
398
399    /// Return the display label.
400    ///
401    /// # Returns
402    ///
403    /// A human-readable chain name (e.g. `"Ethereum"`, `"Bitcoin"`).
404    #[must_use]
405    pub const fn label(&self) -> &'static str {
406        match self {
407            Self::Evm(info) => info.label,
408            Self::NonEvm(info) => info.label,
409        }
410    }
411
412    /// Returns `true` if this chain is under development.
413    ///
414    /// # Returns
415    ///
416    /// `true` when the chain's `is_under_development` flag is set.
417    #[must_use]
418    pub const fn is_under_development(&self) -> bool {
419        match self {
420            Self::Evm(info) => info.is_under_development,
421            Self::NonEvm(info) => info.is_under_development,
422        }
423    }
424
425    /// Returns `true` if this chain is deprecated.
426    ///
427    /// # Returns
428    ///
429    /// `true` when the chain's `is_deprecated` flag is set.
430    #[must_use]
431    pub const fn is_deprecated(&self) -> bool {
432        match self {
433            Self::Evm(info) => info.is_deprecated,
434            Self::NonEvm(info) => info.is_deprecated,
435        }
436    }
437
438    /// Returns `true` if this is an EVM chain.
439    ///
440    /// # Returns
441    ///
442    /// `true` for the [`Evm`](Self::Evm) variant.
443    #[must_use]
444    pub const fn is_evm(&self) -> bool {
445        matches!(self, Self::Evm(_))
446    }
447
448    /// Returns `true` if this is a non-EVM chain.
449    ///
450    /// # Returns
451    ///
452    /// `true` for the [`NonEvm`](Self::NonEvm) variant.
453    #[must_use]
454    pub const fn is_non_evm(&self) -> bool {
455        matches!(self, Self::NonEvm(_))
456    }
457
458    /// Returns the inner [`EvmChainInfo`] if this is an EVM chain.
459    ///
460    /// # Returns
461    ///
462    /// `Some(&info)` for EVM chains, `None` for non-EVM chains.
463    #[must_use]
464    pub const fn as_evm(&self) -> Option<&EvmChainInfo> {
465        match self {
466            Self::Evm(info) => Some(info),
467            Self::NonEvm(_) => None,
468        }
469    }
470
471    /// Returns the inner [`NonEvmChainInfo`] if this is a non-EVM chain.
472    ///
473    /// # Returns
474    ///
475    /// `Some(&info)` for non-EVM chains, `None` for EVM chains.
476    #[must_use]
477    pub const fn as_non_evm(&self) -> Option<&NonEvmChainInfo> {
478        match self {
479            Self::NonEvm(info) => Some(info),
480            Self::Evm(_) => None,
481        }
482    }
483
484    /// Return the native currency info for this chain.
485    ///
486    /// # Returns
487    ///
488    /// A reference to the [`ChainTokenInfo`] describing the chain's native currency.
489    #[must_use]
490    pub const fn native_currency(&self) -> &ChainTokenInfo {
491        match self {
492            Self::Evm(info) => &info.native_currency,
493            Self::NonEvm(info) => &info.native_currency,
494        }
495    }
496}
497
498// ── Chain classification functions ───────────────────────────────────────────
499
500/// Check if a chain ID represents an EVM chain (including bridge-only ones
501/// like Optimism).
502///
503/// # Arguments
504///
505/// * `chain_id` — the numeric chain ID to check.
506///
507/// # Returns
508///
509/// `true` if `chain_id` belongs to a known EVM chain.
510///
511/// ```
512/// use cow_chains::chains::is_evm_chain;
513///
514/// assert!(is_evm_chain(1)); // Mainnet
515/// assert!(is_evm_chain(10)); // Optimism
516/// assert!(!is_evm_chain(1_000_000_000)); // Bitcoin
517/// ```
518#[must_use]
519pub const fn is_evm_chain(chain_id: u64) -> bool {
520    EvmChains::try_from_u64(chain_id).is_some()
521}
522
523/// Check if a chain ID represents a non-EVM chain (Bitcoin, Solana).
524///
525/// # Arguments
526///
527/// * `chain_id` — the numeric chain ID to check.
528///
529/// # Returns
530///
531/// `true` if `chain_id` belongs to a known non-EVM chain.
532///
533/// ```
534/// use cow_chains::chains::is_non_evm_chain;
535///
536/// assert!(is_non_evm_chain(1_000_000_000)); // Bitcoin
537/// assert!(is_non_evm_chain(1_000_000_001)); // Solana
538/// assert!(!is_non_evm_chain(1)); // Mainnet
539/// ```
540#[must_use]
541pub const fn is_non_evm_chain(chain_id: u64) -> bool {
542    NonEvmChains::try_from_u64(chain_id).is_some()
543}
544
545/// Check if a [`ChainInfo`] represents an EVM chain.
546///
547/// Type guard equivalent of `isEvmChainInfo` from the `TypeScript` SDK.
548///
549/// # Arguments
550///
551/// * `chain_info` — the [`ChainInfo`] to inspect.
552///
553/// # Returns
554///
555/// `true` for the [`ChainInfo::Evm`] variant.
556///
557/// ```
558/// use cow_chains::{
559///     SupportedChainId,
560///     chains::{is_evm_chain_info, supported_chain_info},
561/// };
562///
563/// let info = supported_chain_info(SupportedChainId::Mainnet);
564/// assert!(is_evm_chain_info(&info));
565/// ```
566#[must_use]
567pub const fn is_evm_chain_info(chain_info: &ChainInfo) -> bool {
568    chain_info.is_evm()
569}
570
571/// Check if a [`ChainInfo`] represents a non-EVM chain.
572///
573/// Type guard equivalent of `isNonEvmChainInfo` from the `TypeScript` SDK.
574///
575/// # Arguments
576///
577/// * `chain_info` — the [`ChainInfo`] to inspect.
578///
579/// # Returns
580///
581/// `true` for the [`ChainInfo::NonEvm`] variant.
582///
583/// ```
584/// use cow_chains::{
585///     AdditionalTargetChainId,
586///     chains::{additional_target_chain_info, is_non_evm_chain_info},
587/// };
588///
589/// let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
590/// assert!(is_non_evm_chain_info(&info));
591/// ```
592#[must_use]
593pub const fn is_non_evm_chain_info(chain_info: &ChainInfo) -> bool {
594    chain_info.is_non_evm()
595}
596
597/// Check if a chain ID represents Bitcoin.
598///
599/// # Arguments
600///
601/// * `chain_id` — the numeric chain ID to check.
602///
603/// # Returns
604///
605/// `true` when `chain_id` equals [`NonEvmChains::Bitcoin`].
606///
607/// ```
608/// use cow_chains::chains::is_btc_chain;
609///
610/// assert!(is_btc_chain(1_000_000_000));
611/// assert!(!is_btc_chain(1));
612/// ```
613#[must_use]
614pub const fn is_btc_chain(chain_id: u64) -> bool {
615    chain_id == NonEvmChains::Bitcoin as u64
616}
617
618/// Check if a chain ID is directly supported by `CoW` Protocol for trading.
619///
620/// # Arguments
621///
622/// * `chain_id` — the numeric chain ID to check.
623///
624/// # Returns
625///
626/// `true` if `chain_id` is a [`SupportedChainId`] variant.
627///
628/// ```
629/// use cow_chains::chains::is_supported_chain;
630///
631/// assert!(is_supported_chain(1)); // Mainnet
632/// assert!(!is_supported_chain(10)); // Optimism (bridge-only)
633/// ```
634#[must_use]
635pub const fn is_supported_chain(chain_id: u64) -> bool {
636    SupportedChainId::try_from_u64(chain_id).is_some()
637}
638
639/// Check if a chain ID is a bridge-only target chain.
640///
641/// # Arguments
642///
643/// * `chain_id` — the numeric chain ID to check.
644///
645/// # Returns
646///
647/// `true` if `chain_id` is an [`AdditionalTargetChainId`] variant.
648///
649/// ```
650/// use cow_chains::chains::is_additional_target_chain;
651///
652/// assert!(is_additional_target_chain(10)); // Optimism
653/// assert!(is_additional_target_chain(1_000_000_000)); // Bitcoin
654/// assert!(!is_additional_target_chain(1)); // Mainnet
655/// ```
656#[must_use]
657pub const fn is_additional_target_chain(chain_id: u64) -> bool {
658    AdditionalTargetChainId::try_from_u64(chain_id).is_some()
659}
660
661/// Check if a chain ID is either a supported chain or a bridge target.
662///
663/// # Arguments
664///
665/// * `chain_id` — the numeric chain ID to check.
666///
667/// # Returns
668///
669/// `true` if `chain_id` is a supported or additional target chain.
670///
671/// ```
672/// use cow_chains::chains::is_target_chain_id;
673///
674/// assert!(is_target_chain_id(1)); // Mainnet (supported)
675/// assert!(is_target_chain_id(10)); // Optimism (bridge target)
676/// assert!(is_target_chain_id(1_000_000_000)); // Bitcoin (bridge target)
677/// assert!(!is_target_chain_id(999)); // Unknown
678/// ```
679#[must_use]
680pub const fn is_target_chain_id(chain_id: u64) -> bool {
681    is_supported_chain(chain_id) || is_additional_target_chain(chain_id)
682}
683
684/// Check if a chain is zkSync-based.
685///
686/// # Arguments
687///
688/// * `chain_id` — the numeric chain ID to check.
689///
690/// # Returns
691///
692/// `true` if the chain is an EVM chain with `is_zk_sync` set.
693///
694/// ```
695/// use cow_chains::chains::is_zk_sync_chain;
696///
697/// assert!(!is_zk_sync_chain(1)); // Mainnet
698/// ```
699#[must_use]
700pub fn is_zk_sync_chain(chain_id: u64) -> bool {
701    if !is_evm_chain(chain_id) {
702        return false;
703    }
704    get_chain_info(chain_id)
705        .and_then(|info| info.as_evm().cloned())
706        .is_some_and(|evm| evm.is_zk_sync)
707}
708
709/// Check if a chain is under development.
710///
711/// # Arguments
712///
713/// * `chain_id` — the numeric chain ID to check.
714///
715/// # Returns
716///
717/// `true` if the chain exists and its `is_under_development` flag is set.
718///
719/// ```
720/// use cow_chains::chains::is_chain_under_development;
721///
722/// assert!(!is_chain_under_development(1)); // Mainnet
723/// ```
724#[must_use]
725pub fn is_chain_under_development(chain_id: u64) -> bool {
726    get_chain_info(chain_id).is_some_and(|info| info.is_under_development())
727}
728
729/// Check if a chain is deprecated (no new trading; chain remains for
730/// history/Explorer).
731///
732/// # Arguments
733///
734/// * `chain_id` — the numeric chain ID to check.
735///
736/// # Returns
737///
738/// `true` if the chain exists and its `is_deprecated` flag is set.
739///
740/// ```
741/// use cow_chains::chains::is_chain_deprecated;
742///
743/// assert!(!is_chain_deprecated(1)); // Mainnet
744/// ```
745#[must_use]
746pub fn is_chain_deprecated(chain_id: u64) -> bool {
747    get_chain_info(chain_id).is_some_and(|info| info.is_deprecated())
748}
749
750/// Return the chain info for a given chain ID, or `None` if the chain is not
751/// known.
752///
753/// Looks up both supported chains and additional target chains.
754///
755/// # Arguments
756///
757/// * `chain_id` — the numeric chain ID to look up.
758///
759/// # Returns
760///
761/// `Some(chain_info)` if the chain is known, `None` otherwise.
762///
763/// ```
764/// use cow_chains::chains::get_chain_info;
765///
766/// let info = get_chain_info(1).unwrap();
767/// assert_eq!(info.label(), "Ethereum");
768///
769/// let btc = get_chain_info(1_000_000_000).unwrap();
770/// assert_eq!(btc.label(), "Bitcoin");
771///
772/// assert!(get_chain_info(9999).is_none());
773/// ```
774#[must_use]
775pub const fn get_chain_info(chain_id: u64) -> Option<ChainInfo> {
776    if let Some(supported) = SupportedChainId::try_from_u64(chain_id) {
777        return Some(supported_chain_info(supported));
778    }
779
780    if let Some(additional) = AdditionalTargetChainId::try_from_u64(chain_id) {
781        return Some(additional_target_chain_info(additional));
782    }
783
784    None
785}
786
787// ── Collection helpers ───────────────────────────────────────────────────────
788
789/// Map a value across all supported chains, returning a `Vec` of
790/// `(SupportedChainId, T)` pairs.
791///
792/// # Arguments
793///
794/// * `f` — a closure that maps each [`SupportedChainId`] to a value of type `T`.
795///
796/// # Returns
797///
798/// A `Vec` of `(chain, value)` pairs for every supported chain.
799///
800/// ```
801/// use cow_chains::chains::map_supported_networks;
802///
803/// let names = map_supported_networks(|chain| chain.to_string());
804/// assert!(!names.is_empty());
805/// ```
806#[must_use]
807pub fn map_supported_networks<T>(f: impl Fn(SupportedChainId) -> T) -> Vec<(SupportedChainId, T)> {
808    SupportedChainId::all().iter().map(|&chain| (chain, f(chain))).collect()
809}
810
811/// Map a value across all target chains (supported + additional), returning a
812/// `Vec` of `(TargetChainId, T)` pairs.
813///
814/// # Arguments
815///
816/// * `f` — a closure that maps each [`TargetChainId`] to a value of type `T`.
817///
818/// # Returns
819///
820/// A `Vec` of `(chain, value)` pairs for every known chain.
821#[must_use]
822pub fn map_all_networks<T>(f: impl Fn(TargetChainId) -> T) -> Vec<(TargetChainId, T)> {
823    all_chain_ids().into_iter().map(|id| (id, f(id))).collect()
824}
825
826/// Map an address to all supported networks.
827///
828/// Useful for contracts that have the same address on every chain.
829///
830/// # Arguments
831///
832/// * `address` — the [`Address`] to replicate across all chains.
833///
834/// # Returns
835///
836/// A `Vec` of `(chain, address)` pairs for every supported chain.
837#[must_use]
838pub fn map_address_to_supported_networks(address: Address) -> Vec<(SupportedChainId, Address)> {
839    map_supported_networks(|_| address)
840}
841
842/// Return all supported chain IDs as a `Vec`.
843///
844/// # Returns
845///
846/// A `Vec` containing every [`SupportedChainId`] variant.
847#[must_use]
848pub fn all_supported_chain_ids() -> Vec<SupportedChainId> {
849    SupportedChainId::all().to_vec()
850}
851
852/// Return chain info for all supported chains.
853///
854/// # Returns
855///
856/// A `Vec` of [`ChainInfo`] for every supported chain.
857#[must_use]
858pub fn all_supported_chains() -> Vec<ChainInfo> {
859    SupportedChainId::all().iter().map(|&c| supported_chain_info(c)).collect()
860}
861
862/// Return chain IDs where new trading is allowed (excludes deprecated chains).
863///
864/// # Returns
865///
866/// A `Vec` of [`SupportedChainId`] variants that are not deprecated.
867#[must_use]
868pub fn tradable_supported_chain_ids() -> Vec<SupportedChainId> {
869    SupportedChainId::all()
870        .iter()
871        .copied()
872        .filter(|&c| !supported_chain_info(c).is_deprecated())
873        .collect()
874}
875
876/// Return chain info for tradable supported chains (excludes deprecated).
877///
878/// # Returns
879///
880/// A `Vec` of [`ChainInfo`] for supported chains that are not deprecated.
881#[must_use]
882pub fn tradable_supported_chains() -> Vec<ChainInfo> {
883    SupportedChainId::all()
884        .iter()
885        .copied()
886        .filter(|&c| !supported_chain_info(c).is_deprecated())
887        .map(supported_chain_info)
888        .collect()
889}
890
891/// Return chain info for all additional target chains (bridge-only).
892///
893/// # Returns
894///
895/// A `Vec` of [`ChainInfo`] for every bridge-only target chain.
896#[must_use]
897pub fn all_additional_target_chains() -> Vec<ChainInfo> {
898    AdditionalTargetChainId::all().iter().map(|&c| additional_target_chain_info(c)).collect()
899}
900
901/// Return all chain IDs for additional target chains (bridge-only).
902///
903/// # Returns
904///
905/// A `Vec` containing every [`AdditionalTargetChainId`] variant.
906#[must_use]
907pub fn all_additional_target_chain_ids() -> Vec<AdditionalTargetChainId> {
908    AdditionalTargetChainId::all().to_vec()
909}
910
911/// Return chain info for all known chains (both supported and bridge-only).
912///
913/// # Returns
914///
915/// A `Vec` of [`ChainInfo`] for every known chain.
916#[must_use]
917pub fn all_chains() -> Vec<ChainInfo> {
918    let mut chains = all_supported_chains();
919    chains.extend(all_additional_target_chains());
920    chains
921}
922
923/// Return all known chain IDs as [`TargetChainId`] values.
924///
925/// # Returns
926///
927/// A `Vec` of [`TargetChainId`] covering both supported and additional target chains.
928#[must_use]
929pub fn all_chain_ids() -> Vec<TargetChainId> {
930    let mut ids: Vec<TargetChainId> =
931        SupportedChainId::all().iter().map(|&c| TargetChainId::Supported(c)).collect();
932    ids.extend(AdditionalTargetChainId::all().iter().map(|&c| TargetChainId::Additional(c)));
933    ids
934}
935
936// ── Chain info data ──────────────────────────────────────────────────────────
937
938/// Return the [`ChainInfo`] for a [`SupportedChainId`].
939///
940/// # Arguments
941///
942/// * `chain` — the supported chain to look up.
943///
944/// # Returns
945///
946/// A [`ChainInfo::Evm`] containing the chain's metadata.
947#[must_use]
948pub const fn supported_chain_info(chain: SupportedChainId) -> ChainInfo {
949    ChainInfo::Evm(evm_chain_detail(chain))
950}
951
952/// Return the [`ChainInfo`] for an [`AdditionalTargetChainId`].
953///
954/// # Arguments
955///
956/// * `chain` — the additional target chain to look up.
957///
958/// # Returns
959///
960/// A [`ChainInfo`] (EVM for Optimism, non-EVM for Bitcoin/Solana).
961#[must_use]
962pub const fn additional_target_chain_info(chain: AdditionalTargetChainId) -> ChainInfo {
963    match chain {
964        AdditionalTargetChainId::Optimism => ChainInfo::Evm(optimism_chain_info()),
965        AdditionalTargetChainId::Bitcoin => ChainInfo::NonEvm(bitcoin_chain_info()),
966        AdditionalTargetChainId::Solana => ChainInfo::NonEvm(solana_chain_info()),
967    }
968}
969
970const fn evm_chain_detail(chain: SupportedChainId) -> EvmChainInfo {
971    match chain {
972        SupportedChainId::Mainnet => mainnet_chain_info(),
973        SupportedChainId::GnosisChain => gnosis_chain_info(),
974        SupportedChainId::ArbitrumOne => arbitrum_chain_info(),
975        SupportedChainId::Base => base_chain_info(),
976        SupportedChainId::Sepolia => sepolia_chain_info(),
977        SupportedChainId::Polygon => polygon_chain_info(),
978        SupportedChainId::Avalanche => avalanche_chain_info(),
979        SupportedChainId::BnbChain => bnb_chain_info(),
980        SupportedChainId::Linea => linea_chain_info(),
981        SupportedChainId::Lens => lens_chain_info(),
982        SupportedChainId::Plasma => plasma_chain_info(),
983        SupportedChainId::Ink => ink_chain_info(),
984    }
985}
986
987/// The standard EVM native currency address.
988const EVM_NATIVE_ADDR: &str = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
989/// The BTC genesis address used as token address.
990const BTC_ADDR: &str = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
991/// The SOL default program address used as token address.
992const SOL_ADDR: &str = "11111111111111111111111111111111";
993
994// Multicall3 address constant used by most chains.
995const MULTICALL3: Address = Address::new([
996    0xca, 0x11, 0xbd, 0xe0, 0x59, 0x77, 0xb3, 0x63, 0x11, 0x67, 0x02, 0x88, 0x62, 0xbe, 0x2a, 0x17,
997    0x39, 0x76, 0xca, 0x11,
998]);
999
1000const fn default_native_currency(chain_id: u64) -> ChainTokenInfo {
1001    ChainTokenInfo {
1002        chain_id,
1003        address: EVM_NATIVE_ADDR,
1004        decimals: 18,
1005        name: "Ether",
1006        symbol: "ETH",
1007        logo_url: Some(
1008            "https://files.cow.fi/token-lists/images/1/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/logo.png",
1009        ),
1010    }
1011}
1012
1013const fn no_contracts() -> ChainContracts {
1014    ChainContracts { multicall3: None, ens_registry: None, ens_universal_resolver: None }
1015}
1016
1017const fn multicall3_only(block_created: u64) -> ChainContracts {
1018    ChainContracts {
1019        multicall3: Some(ChainContract { address: MULTICALL3, block_created: Some(block_created) }),
1020        ens_registry: None,
1021        ens_universal_resolver: None,
1022    }
1023}
1024
1025const fn mainnet_chain_info() -> EvmChainInfo {
1026    EvmChainInfo {
1027        id: 1,
1028        label: "Ethereum",
1029        eip155_label: "Ethereum Mainnet",
1030        address_prefix: "eth",
1031        native_currency: default_native_currency(1),
1032        is_testnet: false,
1033        color: "#62688F",
1034        logo: ThemedImage {
1035            light: "https://files.cow.fi/cow-sdk/chains/images/mainnet-logo.svg",
1036            dark: "https://files.cow.fi/cow-sdk/chains/images/mainnet-logo.svg",
1037        },
1038        website: WebUrl { name: "Ethereum", url: "https://ethereum.org" },
1039        docs: WebUrl { name: "Ethereum Docs", url: "https://ethereum.org/en/developers/docs" },
1040        block_explorer: WebUrl { name: "Etherscan", url: "https://etherscan.io" },
1041        bridges: &[],
1042        contracts: ChainContracts {
1043            multicall3: Some(ChainContract {
1044                address: MULTICALL3,
1045                block_created: Some(14_353_601),
1046            }),
1047            ens_registry: Some(ChainContract {
1048                address: Address::new([
1049                    0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x07, 0x4e, 0xc6, 0x9a, 0x0d, 0xfb,
1050                    0x29, 0x97, 0xba, 0x6c, 0x7d, 0x2e, 0x1e,
1051                ]),
1052                block_created: None,
1053            }),
1054            ens_universal_resolver: Some(ChainContract {
1055                address: Address::new([
1056                    0xce, 0x01, 0xf8, 0xee, 0xe7, 0xe4, 0x79, 0xc9, 0x28, 0xf8, 0x91, 0x9a, 0xbd,
1057                    0x53, 0xe5, 0x53, 0xa3, 0x6c, 0xef, 0x67,
1058                ]),
1059                block_created: Some(19_258_213),
1060            }),
1061        },
1062        rpc_urls: ChainRpcUrls { http: &["https://eth.merkle.io"], web_socket: None },
1063        is_zk_sync: false,
1064        is_under_development: false,
1065        is_deprecated: false,
1066    }
1067}
1068
1069const fn gnosis_chain_info() -> EvmChainInfo {
1070    EvmChainInfo {
1071        id: 100,
1072        label: "Gnosis",
1073        eip155_label: "Gnosis",
1074        address_prefix: "gno",
1075        native_currency: ChainTokenInfo {
1076            chain_id: 100,
1077            address: EVM_NATIVE_ADDR,
1078            decimals: 18,
1079            name: "xDAI",
1080            symbol: "xDAI",
1081            logo_url: Some(
1082                "https://files.cow.fi/token-lists/images/100/0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/logo.png",
1083            ),
1084        },
1085        is_testnet: false,
1086        color: "#07795B",
1087        logo: ThemedImage {
1088            light: "https://files.cow.fi/cow-sdk/chains/images/gnosis-logo.svg",
1089            dark: "https://files.cow.fi/cow-sdk/chains/images/gnosis-logo.svg",
1090        },
1091        website: WebUrl { name: "Gnosis Chain", url: "https://www.gnosischain.com" },
1092        docs: WebUrl { name: "Gnosis Chain Docs", url: "https://docs.gnosischain.com" },
1093        block_explorer: WebUrl { name: "Gnosisscan", url: "https://gnosisscan.io" },
1094        bridges: &[WebUrl { name: "Gnosis Chain Bridge", url: "https://bridge.gnosischain.com" }],
1095        contracts: multicall3_only(21_022_491),
1096        rpc_urls: ChainRpcUrls {
1097            http: &["https://rpc.gnosischain.com"],
1098            web_socket: Some(&["wss://rpc.gnosischain.com/wss"]),
1099        },
1100        is_zk_sync: false,
1101        is_under_development: false,
1102        is_deprecated: false,
1103    }
1104}
1105
1106const fn arbitrum_chain_info() -> EvmChainInfo {
1107    EvmChainInfo {
1108        id: 42_161,
1109        label: "Arbitrum",
1110        eip155_label: "Arbitrum One",
1111        address_prefix: "arb1",
1112        native_currency: default_native_currency(42_161),
1113        is_testnet: false,
1114        color: "#1B4ADD",
1115        logo: ThemedImage {
1116            light: "https://files.cow.fi/cow-sdk/chains/images/arbitrum-logo.svg",
1117            dark: "https://files.cow.fi/cow-sdk/chains/images/arbitrum-logo.svg",
1118        },
1119        website: WebUrl { name: "Arbitrum", url: "https://arbitrum.io" },
1120        docs: WebUrl { name: "Arbitrum Docs", url: "https://docs.arbitrum.io" },
1121        block_explorer: WebUrl { name: "Arbiscan", url: "https://arbiscan.io" },
1122        bridges: &[WebUrl { name: "Arbitrum Bridge", url: "https://bridge.arbitrum.io" }],
1123        contracts: multicall3_only(7_654_707),
1124        rpc_urls: ChainRpcUrls { http: &["https://arb1.arbitrum.io/rpc"], web_socket: None },
1125        is_zk_sync: false,
1126        is_under_development: false,
1127        is_deprecated: false,
1128    }
1129}
1130
1131const fn base_chain_info() -> EvmChainInfo {
1132    EvmChainInfo {
1133        id: 8_453,
1134        label: "Base",
1135        eip155_label: "Base",
1136        address_prefix: "base",
1137        native_currency: default_native_currency(8_453),
1138        is_testnet: false,
1139        color: "#0052FF",
1140        logo: ThemedImage {
1141            light: "https://files.cow.fi/cow-sdk/chains/images/base-logo.svg",
1142            dark: "https://files.cow.fi/cow-sdk/chains/images/base-logo.svg",
1143        },
1144        website: WebUrl { name: "Base", url: "https://base.org" },
1145        docs: WebUrl { name: "Base Docs", url: "https://docs.base.org" },
1146        block_explorer: WebUrl { name: "Basescan", url: "https://basescan.org" },
1147        bridges: &[WebUrl { name: "Superchain Bridges", url: "https://bridge.base.org/deposit" }],
1148        contracts: multicall3_only(5022),
1149        rpc_urls: ChainRpcUrls { http: &["https://mainnet.base.org"], web_socket: None },
1150        is_zk_sync: false,
1151        is_under_development: false,
1152        is_deprecated: false,
1153    }
1154}
1155
1156const fn sepolia_chain_info() -> EvmChainInfo {
1157    EvmChainInfo {
1158        id: 11_155_111,
1159        label: "Sepolia",
1160        eip155_label: "Ethereum Sepolia",
1161        address_prefix: "sep",
1162        native_currency: default_native_currency(11_155_111),
1163        is_testnet: true,
1164        color: "#C12FF2",
1165        logo: ThemedImage {
1166            light: "https://files.cow.fi/cow-sdk/chains/images/sepolia-logo.svg",
1167            dark: "https://files.cow.fi/cow-sdk/chains/images/sepolia-logo.svg",
1168        },
1169        website: WebUrl { name: "Ethereum", url: "https://sepolia.dev" },
1170        docs: WebUrl {
1171            name: "Sepolia Docs",
1172            url: "https://ethereum.org/en/developers/docs/networks/#sepolia",
1173        },
1174        block_explorer: WebUrl { name: "Etherscan", url: "https://sepolia.etherscan.io" },
1175        bridges: &[],
1176        contracts: ChainContracts {
1177            multicall3: Some(ChainContract { address: MULTICALL3, block_created: Some(751_532) }),
1178            ens_registry: Some(ChainContract {
1179                address: Address::new([
1180                    0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x2e, 0x07, 0x4e, 0xc6, 0x9a, 0x0d, 0xfb,
1181                    0x29, 0x97, 0xba, 0x6c, 0x7d, 0x2e, 0x1e,
1182                ]),
1183                block_created: None,
1184            }),
1185            ens_universal_resolver: Some(ChainContract {
1186                address: Address::new([
1187                    0xc8, 0xaf, 0x99, 0x9e, 0x38, 0x27, 0x3d, 0x65, 0x8b, 0xe1, 0xb9, 0x21, 0xb8,
1188                    0x8a, 0x9d, 0xdf, 0x00, 0x57, 0x69, 0xcc,
1189                ]),
1190                block_created: Some(5_317_080),
1191            }),
1192        },
1193        rpc_urls: ChainRpcUrls { http: &["https://sepolia.drpc.org"], web_socket: None },
1194        is_zk_sync: false,
1195        is_under_development: false,
1196        is_deprecated: false,
1197    }
1198}
1199
1200const fn polygon_chain_info() -> EvmChainInfo {
1201    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/polygon-logo.svg";
1202    EvmChainInfo {
1203        id: 137,
1204        label: "Polygon",
1205        eip155_label: "Polygon Mainnet",
1206        address_prefix: "matic",
1207        native_currency: ChainTokenInfo {
1208            chain_id: 137,
1209            address: EVM_NATIVE_ADDR,
1210            decimals: 18,
1211            name: "POL",
1212            symbol: "POL",
1213            logo_url: Some(logo_url),
1214        },
1215        is_testnet: false,
1216        color: "#8247e5",
1217        logo: ThemedImage { light: logo_url, dark: logo_url },
1218        website: WebUrl { name: "Polygon", url: "https://polygon.technology" },
1219        docs: WebUrl { name: "Polygon Docs", url: "https://docs.polygon.technology" },
1220        block_explorer: WebUrl { name: "Polygonscan", url: "https://polygonscan.com" },
1221        bridges: &[],
1222        contracts: multicall3_only(25_770_160),
1223        rpc_urls: ChainRpcUrls { http: &["https://polygon-rpc.com"], web_socket: None },
1224        is_zk_sync: false,
1225        is_under_development: false,
1226        is_deprecated: false,
1227    }
1228}
1229
1230const fn avalanche_chain_info() -> EvmChainInfo {
1231    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/avax-logo.svg";
1232    EvmChainInfo {
1233        id: 43_114,
1234        label: "Avalanche",
1235        eip155_label: "Avalanche C-Chain",
1236        address_prefix: "avax",
1237        native_currency: ChainTokenInfo {
1238            chain_id: 43_114,
1239            address: EVM_NATIVE_ADDR,
1240            decimals: 18,
1241            name: "Avalanche",
1242            symbol: "AVAX",
1243            logo_url: Some(logo_url),
1244        },
1245        is_testnet: false,
1246        color: "#ff3944",
1247        logo: ThemedImage { light: logo_url, dark: logo_url },
1248        website: WebUrl { name: "Avalanche", url: "https://www.avax.network/" },
1249        docs: WebUrl { name: "Avalanche Docs", url: "https://build.avax.network/docs" },
1250        block_explorer: WebUrl { name: "Snowscan", url: "https://snowscan.xyz" },
1251        bridges: &[],
1252        contracts: multicall3_only(11_907_934),
1253        rpc_urls: ChainRpcUrls {
1254            http: &["https://api.avax.network/ext/bc/C/rpc"],
1255            web_socket: None,
1256        },
1257        is_zk_sync: false,
1258        is_under_development: false,
1259        is_deprecated: false,
1260    }
1261}
1262
1263const fn bnb_chain_info() -> EvmChainInfo {
1264    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/bnb-logo.svg";
1265    EvmChainInfo {
1266        id: 56,
1267        label: "BNB",
1268        eip155_label: "BNB Chain Mainnet",
1269        address_prefix: "bnb",
1270        native_currency: ChainTokenInfo {
1271            chain_id: 56,
1272            address: EVM_NATIVE_ADDR,
1273            decimals: 18,
1274            name: "BNB Chain Native Token",
1275            symbol: "BNB",
1276            logo_url: Some(logo_url),
1277        },
1278        is_testnet: false,
1279        color: "#F0B90B",
1280        logo: ThemedImage { light: logo_url, dark: logo_url },
1281        website: WebUrl { name: "BNB Chain", url: "https://www.bnbchain.org" },
1282        docs: WebUrl { name: "BNB Chain Docs", url: "https://docs.bnbchain.org" },
1283        block_explorer: WebUrl { name: "Bscscan", url: "https://bscscan.com" },
1284        bridges: &[WebUrl {
1285            name: "BNB Chain Cross-Chain Bridge",
1286            url: "https://www.bnbchain.org/en/bnb-chain-bridge",
1287        }],
1288        contracts: multicall3_only(15_921_452),
1289        rpc_urls: ChainRpcUrls { http: &["https://bsc-dataseed1.bnbchain.org"], web_socket: None },
1290        is_zk_sync: false,
1291        is_under_development: false,
1292        is_deprecated: false,
1293    }
1294}
1295
1296const fn linea_chain_info() -> EvmChainInfo {
1297    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/linea-logo.svg";
1298    EvmChainInfo {
1299        id: 59_144,
1300        label: "Linea",
1301        eip155_label: "Linea Mainnet",
1302        address_prefix: "linea",
1303        native_currency: default_native_currency(59_144),
1304        is_testnet: false,
1305        color: "#61dfff",
1306        logo: ThemedImage { light: logo_url, dark: logo_url },
1307        website: WebUrl { name: "Linea", url: "https://linea.build" },
1308        docs: WebUrl { name: "Linea Docs", url: "https://docs.linea.build" },
1309        block_explorer: WebUrl { name: "Lineascan", url: "https://lineascan.build" },
1310        bridges: &[WebUrl { name: "Linea Bridge", url: "https://linea.build/hub/bridge" }],
1311        contracts: multicall3_only(42),
1312        rpc_urls: ChainRpcUrls { http: &["https://rpc.linea.build"], web_socket: None },
1313        is_zk_sync: false,
1314        is_under_development: false,
1315        is_deprecated: false,
1316    }
1317}
1318
1319// Lens is in the Rust SDK but not in the TS config at this time.
1320const fn lens_chain_info() -> EvmChainInfo {
1321    EvmChainInfo {
1322        id: 232,
1323        label: "Lens",
1324        eip155_label: "Lens Network",
1325        address_prefix: "lens",
1326        native_currency: ChainTokenInfo {
1327            chain_id: 232,
1328            address: EVM_NATIVE_ADDR,
1329            decimals: 18,
1330            name: "GHO",
1331            symbol: "GHO",
1332            logo_url: None,
1333        },
1334        is_testnet: false,
1335        color: "#00501e",
1336        logo: ThemedImage {
1337            light: "https://files.cow.fi/cow-sdk/chains/images/lens-logo.svg",
1338            dark: "https://files.cow.fi/cow-sdk/chains/images/lens-logo.svg",
1339        },
1340        website: WebUrl { name: "Lens", url: "https://lens.xyz" },
1341        docs: WebUrl { name: "Lens Docs", url: "https://docs.lens.xyz" },
1342        block_explorer: WebUrl { name: "Lens Explorer", url: "https://explorer.lens.xyz" },
1343        bridges: &[],
1344        contracts: no_contracts(),
1345        rpc_urls: ChainRpcUrls { http: &["https://rpc.lens.xyz"], web_socket: None },
1346        is_zk_sync: true,
1347        is_under_development: false,
1348        is_deprecated: false,
1349    }
1350}
1351
1352const fn plasma_chain_info() -> EvmChainInfo {
1353    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/plasma-logo.svg";
1354    EvmChainInfo {
1355        id: 9_745,
1356        label: "Plasma",
1357        eip155_label: "Plasma Mainnet",
1358        address_prefix: "plasma",
1359        native_currency: ChainTokenInfo {
1360            chain_id: 9_745,
1361            address: EVM_NATIVE_ADDR,
1362            decimals: 18,
1363            name: "Plasma",
1364            symbol: "XPL",
1365            logo_url: Some(logo_url),
1366        },
1367        is_testnet: false,
1368        color: "#569F8C",
1369        logo: ThemedImage { light: logo_url, dark: logo_url },
1370        website: WebUrl { name: "Plasma", url: "https://www.plasma.to" },
1371        docs: WebUrl { name: "Plasma Docs", url: "https://docs.plasma.to" },
1372        block_explorer: WebUrl { name: "Plasmascan", url: "https://plasmascan.to" },
1373        bridges: &[],
1374        contracts: multicall3_only(0),
1375        rpc_urls: ChainRpcUrls { http: &["https://rpc.plasma.to"], web_socket: None },
1376        is_zk_sync: false,
1377        is_under_development: false,
1378        is_deprecated: false,
1379    }
1380}
1381
1382const fn ink_chain_info() -> EvmChainInfo {
1383    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/ink-logo.svg";
1384    EvmChainInfo {
1385        id: 57_073,
1386        label: "Ink",
1387        eip155_label: "Ink Chain Mainnet",
1388        address_prefix: "ink",
1389        native_currency: default_native_currency(57_073),
1390        is_testnet: false,
1391        color: "#7132f5",
1392        logo: ThemedImage { light: logo_url, dark: logo_url },
1393        website: WebUrl { name: "Ink", url: "https://inkonchain.com/" },
1394        docs: WebUrl { name: "Ink Docs", url: "https://docs.inkonchain.com" },
1395        block_explorer: WebUrl { name: "Ink Explorer", url: "https://explorer.inkonchain.com" },
1396        bridges: &[WebUrl { name: "Ink Bridge", url: "https://inkonchain.com/bridge" }],
1397        contracts: multicall3_only(0),
1398        rpc_urls: ChainRpcUrls { http: &["https://rpc-ten.inkonchain.com"], web_socket: None },
1399        is_zk_sync: false,
1400        is_under_development: false,
1401        is_deprecated: false,
1402    }
1403}
1404
1405const fn optimism_chain_info() -> EvmChainInfo {
1406    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/optimism-logo.svg";
1407    EvmChainInfo {
1408        id: 10,
1409        label: "Optimism",
1410        eip155_label: "OP Mainnet",
1411        address_prefix: "op",
1412        native_currency: default_native_currency(10),
1413        is_testnet: false,
1414        color: "#ff0420",
1415        logo: ThemedImage { light: logo_url, dark: logo_url },
1416        website: WebUrl { name: "Optimism", url: "https://optimism.io" },
1417        docs: WebUrl { name: "Optimism Docs", url: "https://docs.optimism.io" },
1418        block_explorer: WebUrl { name: "Etherscan", url: "https://optimistic.etherscan.io" },
1419        bridges: &[],
1420        contracts: multicall3_only(4_286_263),
1421        rpc_urls: ChainRpcUrls { http: &["https://mainnet.optimism.io"], web_socket: None },
1422        is_zk_sync: false,
1423        is_under_development: false,
1424        is_deprecated: false,
1425    }
1426}
1427
1428const fn bitcoin_chain_info() -> NonEvmChainInfo {
1429    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/bitcoin-logo.svg";
1430    NonEvmChainInfo {
1431        id: 1_000_000_000,
1432        label: "Bitcoin",
1433        address_prefix: "btc",
1434        native_currency: ChainTokenInfo {
1435            chain_id: 1_000_000_000,
1436            address: BTC_ADDR,
1437            decimals: 8,
1438            name: "Bitcoin",
1439            symbol: "BTC",
1440            logo_url: Some(logo_url),
1441        },
1442        is_testnet: false,
1443        color: "#f7931a",
1444        logo: ThemedImage { light: logo_url, dark: logo_url },
1445        website: WebUrl { name: "Bitcoin", url: "https://bitcoin.org" },
1446        docs: WebUrl {
1447            name: "Bitcoin Docs",
1448            url: "https://bitcoin.org/en/developer-documentation",
1449        },
1450        block_explorer: WebUrl { name: "Blockstream Explorer", url: "https://blockstream.info" },
1451        is_under_development: false,
1452        is_deprecated: false,
1453    }
1454}
1455
1456const fn solana_chain_info() -> NonEvmChainInfo {
1457    let logo_url = "https://files.cow.fi/cow-sdk/chains/images/solana-logo.svg";
1458    NonEvmChainInfo {
1459        id: 1_000_000_001,
1460        label: "Solana",
1461        address_prefix: "sol",
1462        native_currency: ChainTokenInfo {
1463            chain_id: 1_000_000_001,
1464            address: SOL_ADDR,
1465            decimals: 9,
1466            name: "Solana",
1467            symbol: "SOL",
1468            logo_url: Some(logo_url),
1469        },
1470        is_testnet: false,
1471        color: "#9945FF",
1472        logo: ThemedImage { light: logo_url, dark: logo_url },
1473        website: WebUrl { name: "Solana", url: "https://solana.com" },
1474        docs: WebUrl { name: "Solana Docs", url: "https://docs.solana.com" },
1475        block_explorer: WebUrl { name: "Solana Explorer", url: "https://explorer.solana.com" },
1476        is_under_development: false,
1477        is_deprecated: false,
1478    }
1479}
1480
1481// ── API context types ────────────────────────────────────────────────────────
1482
1483/// IPFS configuration for reading and writing app data.
1484#[derive(Debug, Clone, Default)]
1485pub struct IpfsConfig {
1486    /// The URI of the IPFS node.
1487    pub uri: Option<String>,
1488    /// The URI of the IPFS node for writing.
1489    pub write_uri: Option<String>,
1490    /// The URI of the IPFS node for reading.
1491    pub read_uri: Option<String>,
1492    /// Pinata API key.
1493    pub pinata_api_key: Option<String>,
1494    /// Pinata API secret.
1495    pub pinata_api_secret: Option<String>,
1496}
1497
1498/// The `CoW` Protocol API context.
1499///
1500/// Defines the chain, environment, and optional overrides for connecting to the
1501/// `CoW` Protocol API.
1502#[derive(Debug, Clone)]
1503pub struct ApiContext {
1504    /// The target chain ID.
1505    pub chain_id: SupportedChainId,
1506    /// The API environment (`prod` or `staging`).
1507    pub env: super::chain::Env,
1508    /// Optional per-chain base URL overrides.
1509    pub base_urls: Option<ApiBaseUrls>,
1510    /// Optional API key for the partner API.
1511    pub api_key: Option<String>,
1512}
1513
1514impl Default for ApiContext {
1515    /// Returns the default API context (production, mainnet).
1516    fn default() -> Self {
1517        Self {
1518            chain_id: SupportedChainId::Mainnet,
1519            env: super::chain::Env::Prod,
1520            base_urls: None,
1521            api_key: None,
1522        }
1523    }
1524}
1525
1526/// Protocol-level options for overriding `CoW` Protocol contract addresses and
1527/// environment.
1528#[derive(Debug, Clone, Default)]
1529pub struct ProtocolOptions {
1530    /// The API environment.
1531    pub env: Option<super::chain::Env>,
1532    /// Per-chain settlement contract address overrides.
1533    pub settlement_contract_override: Option<AddressPerChain>,
1534    /// Per-chain `EthFlow` contract address overrides.
1535    pub eth_flow_contract_override: Option<AddressPerChain>,
1536}
1537
1538/// An EVM call with a target address, calldata, and value.
1539#[derive(Debug, Clone)]
1540pub struct EvmCall {
1541    /// The target contract address.
1542    pub to: Address,
1543    /// The encoded calldata.
1544    pub data: Vec<u8>,
1545    /// The value to send (in wei).
1546    pub value: U256,
1547}
1548
1549#[cfg(test)]
1550mod tests {
1551    use super::*;
1552
1553    // ── EvmChains ───────────────────────────────────────────────────────
1554
1555    #[test]
1556    fn evm_chains_roundtrip_u64() {
1557        let chains = [
1558            (EvmChains::Mainnet, 1),
1559            (EvmChains::Optimism, 10),
1560            (EvmChains::Bnb, 56),
1561            (EvmChains::GnosisChain, 100),
1562            (EvmChains::Polygon, 137),
1563            (EvmChains::Base, 8_453),
1564            (EvmChains::Plasma, 9_745),
1565            (EvmChains::ArbitrumOne, 42_161),
1566            (EvmChains::Avalanche, 43_114),
1567            (EvmChains::Ink, 57_073),
1568            (EvmChains::Linea, 59_144),
1569            (EvmChains::Sepolia, 11_155_111),
1570        ];
1571        for (chain, id) in chains {
1572            assert_eq!(chain.as_u64(), id);
1573            assert_eq!(EvmChains::try_from_u64(id), Some(chain));
1574        }
1575    }
1576
1577    #[test]
1578    fn evm_chains_unknown_returns_none() {
1579        assert_eq!(EvmChains::try_from_u64(9999), None);
1580    }
1581
1582    // ── NonEvmChains ────────────────────────────────────────────────────
1583
1584    #[test]
1585    fn non_evm_chains_roundtrip() {
1586        assert_eq!(NonEvmChains::Bitcoin.as_u64(), 1_000_000_000);
1587        assert_eq!(NonEvmChains::Solana.as_u64(), 1_000_000_001);
1588        assert_eq!(NonEvmChains::try_from_u64(1_000_000_000), Some(NonEvmChains::Bitcoin));
1589        assert_eq!(NonEvmChains::try_from_u64(1_000_000_001), Some(NonEvmChains::Solana));
1590        assert_eq!(NonEvmChains::try_from_u64(999), None);
1591    }
1592
1593    // ── AdditionalTargetChainId ─────────────────────────────────────────
1594
1595    #[test]
1596    fn additional_target_roundtrip() {
1597        for &chain in AdditionalTargetChainId::all() {
1598            let id = chain.as_u64();
1599            assert_eq!(AdditionalTargetChainId::try_from_u64(id), Some(chain));
1600        }
1601    }
1602
1603    #[test]
1604    fn additional_target_all_has_three() {
1605        assert_eq!(AdditionalTargetChainId::all().len(), 3);
1606    }
1607
1608    // ── TargetChainId ───────────────────────────────────────────────────
1609
1610    #[test]
1611    fn target_chain_id_as_u64() {
1612        let supported = TargetChainId::Supported(SupportedChainId::Mainnet);
1613        assert_eq!(supported.as_u64(), 1);
1614        let additional = TargetChainId::Additional(AdditionalTargetChainId::Bitcoin);
1615        assert_eq!(additional.as_u64(), 1_000_000_000);
1616    }
1617
1618    // ── Classification helpers ──────────────────────────────────────────
1619
1620    #[test]
1621    fn is_evm_chain_correct() {
1622        assert!(is_evm_chain(1));
1623        assert!(is_evm_chain(10));
1624        assert!(!is_evm_chain(1_000_000_000));
1625        assert!(!is_evm_chain(9999));
1626    }
1627
1628    #[test]
1629    fn is_non_evm_chain_correct() {
1630        assert!(is_non_evm_chain(1_000_000_000));
1631        assert!(is_non_evm_chain(1_000_000_001));
1632        assert!(!is_non_evm_chain(1));
1633    }
1634
1635    #[test]
1636    fn is_btc_chain_correct() {
1637        assert!(is_btc_chain(1_000_000_000));
1638        assert!(!is_btc_chain(1));
1639    }
1640
1641    #[test]
1642    fn is_supported_chain_correct() {
1643        assert!(is_supported_chain(1));
1644        assert!(is_supported_chain(100));
1645        assert!(!is_supported_chain(10));
1646        assert!(!is_supported_chain(9999));
1647    }
1648
1649    #[test]
1650    fn is_additional_target_chain_correct() {
1651        assert!(is_additional_target_chain(10));
1652        assert!(is_additional_target_chain(1_000_000_000));
1653        assert!(!is_additional_target_chain(1));
1654    }
1655
1656    #[test]
1657    fn is_target_chain_id_correct() {
1658        assert!(is_target_chain_id(1));
1659        assert!(is_target_chain_id(10));
1660        assert!(is_target_chain_id(1_000_000_000));
1661        assert!(!is_target_chain_id(9999));
1662    }
1663
1664    #[test]
1665    fn is_zk_sync_chain_is_false_for_all() {
1666        assert!(!is_zk_sync_chain(1));
1667        assert!(!is_zk_sync_chain(100));
1668    }
1669
1670    // ── ChainInfo ───────────────────────────────────────────────────────
1671
1672    #[test]
1673    fn get_chain_info_all_supported() {
1674        for &chain in SupportedChainId::all() {
1675            let chain_info = get_chain_info(chain.as_u64());
1676            assert!(chain_info.is_some(), "no chain info for {chain:?}");
1677            let info = chain_info.unwrap_or_else(|| supported_chain_info(chain));
1678            assert_eq!(info.id(), chain.as_u64());
1679            assert!(!info.label().is_empty());
1680        }
1681    }
1682
1683    #[test]
1684    fn get_chain_info_additional_targets() {
1685        for &chain in AdditionalTargetChainId::all() {
1686            let info = get_chain_info(chain.as_u64());
1687            assert!(info.is_some(), "no chain info for {chain:?}");
1688        }
1689    }
1690
1691    #[test]
1692    fn get_chain_info_unknown_returns_none() {
1693        assert!(get_chain_info(9999).is_none());
1694    }
1695
1696    #[test]
1697    fn chain_info_evm_predicates() {
1698        let info = supported_chain_info(SupportedChainId::Mainnet);
1699        assert!(info.is_evm());
1700        assert!(!info.is_non_evm());
1701        assert!(info.as_evm().is_some());
1702        assert!(info.as_non_evm().is_none());
1703    }
1704
1705    #[test]
1706    fn chain_info_non_evm_predicates() {
1707        let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1708        assert!(!info.is_evm());
1709        assert!(info.is_non_evm());
1710        assert!(info.as_non_evm().is_some());
1711    }
1712
1713    #[test]
1714    fn chain_info_native_currency() {
1715        let info = supported_chain_info(SupportedChainId::Mainnet);
1716        let currency = info.native_currency();
1717        assert_eq!(currency.decimals, 18);
1718    }
1719
1720    // ── Iteration helpers ───────────────────────────────────────────────
1721
1722    #[test]
1723    fn all_supported_chain_ids_matches_all() {
1724        let ids = all_supported_chain_ids();
1725        assert_eq!(ids.len(), SupportedChainId::all().len());
1726    }
1727
1728    #[test]
1729    fn all_supported_chains_matches_all() {
1730        let chains = all_supported_chains();
1731        assert_eq!(chains.len(), SupportedChainId::all().len());
1732    }
1733
1734    #[test]
1735    fn tradable_chains_excludes_deprecated_and_dev() {
1736        let tradable = tradable_supported_chain_ids();
1737        assert!(!tradable.is_empty());
1738        assert!(tradable.len() <= SupportedChainId::all().len());
1739    }
1740
1741    #[test]
1742    fn all_additional_target_chain_ids_has_three() {
1743        assert_eq!(all_additional_target_chain_ids().len(), 3);
1744    }
1745
1746    #[test]
1747    fn all_chains_includes_supported_and_additional() {
1748        let all = all_chains();
1749        assert!(all.len() >= SupportedChainId::all().len());
1750    }
1751
1752    #[test]
1753    fn all_chain_ids_includes_both() {
1754        let ids = all_chain_ids();
1755        assert!(ids.len() >= SupportedChainId::all().len() + AdditionalTargetChainId::all().len());
1756    }
1757
1758    #[test]
1759    fn map_supported_networks_maps_all() {
1760        let mapped = map_supported_networks(|c| c.as_u64());
1761        assert_eq!(mapped.len(), SupportedChainId::all().len());
1762    }
1763
1764    #[test]
1765    fn map_address_to_supported_networks_produces_correct_count() {
1766        let mapped = map_address_to_supported_networks(Address::ZERO);
1767        assert_eq!(mapped.len(), SupportedChainId::all().len());
1768    }
1769
1770    // ── Additional coverage ────────────────────────────────────────────
1771
1772    #[test]
1773    fn is_zk_sync_chain_returns_false_for_lens() {
1774        // Lens (232) has is_zk_sync = true in chain info, but it is not in EvmChains
1775        // so is_zk_sync_chain returns false because it first checks is_evm_chain.
1776        assert!(!is_zk_sync_chain(232));
1777    }
1778
1779    #[test]
1780    fn is_zk_sync_chain_returns_false_for_non_evm() {
1781        assert!(!is_zk_sync_chain(1_000_000_000));
1782    }
1783
1784    #[test]
1785    fn is_chain_under_development_unknown_chain() {
1786        assert!(!is_chain_under_development(9999));
1787    }
1788
1789    #[test]
1790    fn is_chain_deprecated_unknown_chain() {
1791        assert!(!is_chain_deprecated(9999));
1792    }
1793
1794    #[test]
1795    fn chain_info_is_under_development_non_evm() {
1796        let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1797        assert!(!info.is_under_development());
1798    }
1799
1800    #[test]
1801    fn chain_info_is_deprecated_non_evm() {
1802        let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1803        assert!(!info.is_deprecated());
1804    }
1805
1806    #[test]
1807    fn evm_chain_info_type_guards() {
1808        let info = supported_chain_info(SupportedChainId::Mainnet);
1809        assert!(is_evm_chain_info(&info));
1810        assert!(!is_non_evm_chain_info(&info));
1811    }
1812
1813    #[test]
1814    fn non_evm_chain_info_type_guards() {
1815        let info = additional_target_chain_info(AdditionalTargetChainId::Bitcoin);
1816        assert!(is_non_evm_chain_info(&info));
1817        assert!(!is_evm_chain_info(&info));
1818    }
1819
1820    #[test]
1821    fn tradable_supported_chains_returns_chain_infos() {
1822        let chains = tradable_supported_chains();
1823        assert!(!chains.is_empty());
1824        for c in &chains {
1825            assert!(!c.is_deprecated());
1826        }
1827    }
1828
1829    #[test]
1830    fn all_additional_target_chains_returns_infos() {
1831        let chains = all_additional_target_chains();
1832        assert_eq!(chains.len(), 3);
1833    }
1834
1835    #[test]
1836    fn map_all_networks_covers_everything() {
1837        let mapped = map_all_networks(|t| t.as_u64());
1838        assert!(
1839            mapped.len() >= SupportedChainId::all().len() + AdditionalTargetChainId::all().len()
1840        );
1841    }
1842
1843    #[test]
1844    fn chain_info_label_non_evm() {
1845        let info = additional_target_chain_info(AdditionalTargetChainId::Solana);
1846        assert_eq!(info.label(), "Solana");
1847        assert_eq!(info.id(), 1_000_000_001);
1848    }
1849
1850    #[test]
1851    fn additional_target_optimism_is_evm() {
1852        let info = additional_target_chain_info(AdditionalTargetChainId::Optimism);
1853        assert!(info.is_evm());
1854        assert_eq!(info.label(), "Optimism");
1855    }
1856
1857    #[test]
1858    fn api_context_default() {
1859        let ctx = ApiContext::default();
1860        assert_eq!(ctx.chain_id, SupportedChainId::Mainnet);
1861        assert!(ctx.base_urls.is_none());
1862        assert!(ctx.api_key.is_none());
1863    }
1864
1865    #[test]
1866    fn additional_target_try_from_u64_unknown() {
1867        assert!(AdditionalTargetChainId::try_from_u64(999).is_none());
1868    }
1869}