Skip to main content

df_hyperliquid_core/client/
network.rs

1//! Mainnet and testnet. Everything that differs between them is a method on
2//! [`Network`]; an action signed for one is never valid on the other, so the
3//! network is never assumed.
4
5use std::{fmt, str::FromStr};
6
7/// The chain id an L1 action's domain carries on both networks: a literal,
8/// not a real chain.
9pub const L1_CHAIN_ID: u64 = 1337;
10
11/// Spot USDC's index on both networks.
12pub const USDC_INDEX: u32 = 0;
13
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
15pub enum Network {
16    #[default]
17    Mainnet,
18    Testnet,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22#[error("`{0}` is not a network; expected mainnet or testnet")]
23pub struct UnknownNetwork(pub String);
24
25impl Network {
26    pub const ALL: [Network; 2] = [Network::Mainnet, Network::Testnet];
27
28    /// Both endpoints live under it: `/info` and `/exchange`.
29    #[must_use]
30    pub const fn api_url(self) -> &'static str {
31        match self {
32            Network::Mainnet => "https://api.hyperliquid.xyz",
33            Network::Testnet => "https://api.hyperliquid-testnet.xyz",
34        }
35    }
36
37    /// The public websocket.
38    #[must_use]
39    pub const fn ws_url(self) -> &'static str {
40        match self {
41            Network::Mainnet => "wss://api.hyperliquid.xyz/ws",
42            Network::Testnet => "wss://api.hyperliquid-testnet.xyz/ws",
43        }
44    }
45
46    /// HyperEVM's JSON-RPC.
47    #[must_use]
48    pub const fn evm_rpc_url(self) -> &'static str {
49        match self {
50            Network::Mainnet => "https://rpc.hyperliquid.xyz/evm",
51            Network::Testnet => "https://rpc.hyperliquid-testnet.xyz/evm",
52        }
53    }
54
55    /// HyperEVM's chain id.
56    #[must_use]
57    pub const fn evm_chain_id(self) -> u64 {
58        match self {
59            Network::Mainnet => 999,
60            Network::Testnet => 998,
61        }
62    }
63
64    /// The chain every user-signed action is signed under and names as its
65    /// `signatureChainId`: Arbitrum One on mainnet, Arbitrum Sepolia on
66    /// testnet. The venue tells the networks apart by `hyperliquidChain`.
67    #[must_use]
68    pub const fn signature_chain_id(self) -> u64 {
69        match self {
70            Network::Mainnet => 42_161,
71            Network::Testnet => 421_614,
72        }
73    }
74
75    /// The Arbitrum chain the bridge is on, where a deposit goes. Separate
76    /// from [`Network::signature_chain_id`], though the two agree today.
77    #[must_use]
78    pub const fn arbitrum_chain_id(self) -> u64 {
79        match self {
80            Network::Mainnet => 42_161,
81            Network::Testnet => 421_614,
82        }
83    }
84
85    /// [`Network::signature_chain_id`] as an action spells it.
86    #[must_use]
87    pub const fn signature_chain_id_hex(self) -> &'static str {
88        match self {
89            Network::Mainnet => "0xa4b1",
90            Network::Testnet => "0x66eee",
91        }
92    }
93
94    /// What a user-signed action names as its `hyperliquidChain`.
95    #[must_use]
96    pub const fn hyperliquid_chain(self) -> &'static str {
97        match self {
98            Network::Mainnet => "Mainnet",
99            Network::Testnet => "Testnet",
100        }
101    }
102
103    /// The phantom agent's `source` for an L1 action.
104    #[must_use]
105    pub const fn agent_source(self) -> &'static str {
106        match self {
107            Network::Mainnet => "a",
108            Network::Testnet => "b",
109        }
110    }
111
112    /// Spot USDC, whose token id differs between the networks.
113    #[must_use]
114    pub const fn usdc(self) -> Token {
115        match self {
116            Network::Mainnet => MAINNET_USDC,
117            Network::Testnet => TESTNET_USDC,
118        }
119    }
120
121    /// Spot HYPE, whose index and token id differ between the networks.
122    #[must_use]
123    pub const fn hype(self) -> Token {
124        match self {
125            Network::Mainnet => MAINNET_HYPE,
126            Network::Testnet => TESTNET_HYPE,
127        }
128    }
129
130    #[must_use]
131    pub const fn name(self) -> &'static str {
132        match self {
133            Network::Mainnet => "mainnet",
134            Network::Testnet => "testnet",
135        }
136    }
137}
138
139impl fmt::Display for Network {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        f.write_str(self.name())
142    }
143}
144
145impl FromStr for Network {
146    type Err = UnknownNetwork;
147
148    fn from_str(text: &str) -> Result<Self, Self::Err> {
149        match text.to_ascii_lowercase().as_str() {
150            "mainnet" => Ok(Network::Mainnet),
151            "testnet" => Ok(Network::Testnet),
152            _ => Err(UnknownNetwork(text.to_owned())),
153        }
154    }
155}
156
157/// A spot token as the venue names it in each place it appears.
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
159pub struct Token {
160    pub name: &'static str,
161    /// `token` in `spotClearinghouseState`, and the index in `spotMeta`.
162    pub index: u32,
163    pub token_id: &'static str,
164    /// `NAME:0x<tokenId>`, how a transfer names the token.
165    pub wire: &'static str,
166    /// The decimals the venue counts the token in.
167    pub wei_decimals: u32,
168}
169
170/// Spot USDC on mainnet, as `spotMeta` lists it.
171pub const MAINNET_USDC: Token = Token {
172    name: "USDC",
173    index: USDC_INDEX,
174    token_id: "0x6d1e7cde53ba9467b783cb7c530ce054",
175    wire: "USDC:0x6d1e7cde53ba9467b783cb7c530ce054",
176    wei_decimals: 8,
177};
178
179/// Spot USDC on testnet, as its `spotMeta` lists it.
180pub const TESTNET_USDC: Token = Token {
181    name: "USDC",
182    index: USDC_INDEX,
183    token_id: "0xeb62eee3685fc4c43992febcd9e75443",
184    wire: "USDC:0xeb62eee3685fc4c43992febcd9e75443",
185    wei_decimals: 8,
186};
187
188/// HYPE on mainnet, as `spotMeta` lists it.
189pub const MAINNET_HYPE: Token = Token {
190    name: "HYPE",
191    index: 150,
192    token_id: "0x0d01dc56dcaaca66ad901c959b4011ec",
193    wire: "HYPE:0x0d01dc56dcaaca66ad901c959b4011ec",
194    wei_decimals: 8,
195};
196
197/// HYPE on testnet, as its `spotMeta` lists it. Testnet's token 150 is a
198/// different token.
199pub const TESTNET_HYPE: Token = Token {
200    name: "HYPE",
201    index: 1105,
202    token_id: "0x7317beb7cceed72ef0b346074cc8e7ab",
203    wire: "HYPE:0x7317beb7cceed72ef0b346074cc8e7ab",
204    wei_decimals: 8,
205};
206
207#[cfg(test)]
208#[path = "tests/network.rs"]
209mod tests;