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    /// The venue's own web app, for a link a person follows.
139    #[must_use]
140    pub const fn app_url(self) -> &'static str {
141        match self {
142            Network::Mainnet => "https://app.hyperliquid.xyz",
143            Network::Testnet => "https://app.hyperliquid-testnet.xyz",
144        }
145    }
146
147    #[must_use]
148    pub const fn is_mainnet(self) -> bool {
149        matches!(self, Network::Mainnet)
150    }
151}
152
153impl fmt::Display for Network {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.write_str(self.name())
156    }
157}
158
159impl FromStr for Network {
160    type Err = UnknownNetwork;
161
162    fn from_str(text: &str) -> Result<Self, Self::Err> {
163        match text.trim().to_ascii_lowercase().as_str() {
164            "mainnet" | "main" => Ok(Network::Mainnet),
165            "testnet" | "test" => Ok(Network::Testnet),
166            _ => Err(UnknownNetwork(text.trim().to_owned())),
167        }
168    }
169}
170
171/// A spot token as the venue names it in each place it appears.
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
173pub struct Token {
174    pub name: &'static str,
175    /// `token` in `spotClearinghouseState`, and the index in `spotMeta`.
176    pub index: u32,
177    pub token_id: &'static str,
178    /// `NAME:0x<tokenId>`, how a transfer names the token.
179    pub wire: &'static str,
180    /// The decimals the venue counts the token in.
181    pub wei_decimals: u32,
182}
183
184/// Spot USDC on mainnet, as `spotMeta` lists it.
185pub const MAINNET_USDC: Token = Token {
186    name: "USDC",
187    index: USDC_INDEX,
188    token_id: "0x6d1e7cde53ba9467b783cb7c530ce054",
189    wire: "USDC:0x6d1e7cde53ba9467b783cb7c530ce054",
190    wei_decimals: 8,
191};
192
193/// Spot USDC on testnet, as its `spotMeta` lists it.
194pub const TESTNET_USDC: Token = Token {
195    name: "USDC",
196    index: USDC_INDEX,
197    token_id: "0xeb62eee3685fc4c43992febcd9e75443",
198    wire: "USDC:0xeb62eee3685fc4c43992febcd9e75443",
199    wei_decimals: 8,
200};
201
202/// HYPE on mainnet, as `spotMeta` lists it.
203pub const MAINNET_HYPE: Token = Token {
204    name: "HYPE",
205    index: 150,
206    token_id: "0x0d01dc56dcaaca66ad901c959b4011ec",
207    wire: "HYPE:0x0d01dc56dcaaca66ad901c959b4011ec",
208    wei_decimals: 8,
209};
210
211/// HYPE on testnet, as its `spotMeta` lists it. Testnet's token 150 is a
212/// different token.
213pub const TESTNET_HYPE: Token = Token {
214    name: "HYPE",
215    index: 1105,
216    token_id: "0x7317beb7cceed72ef0b346074cc8e7ab",
217    wire: "HYPE:0x7317beb7cceed72ef0b346074cc8e7ab",
218    wei_decimals: 8,
219};
220
221#[cfg(test)]
222#[path = "tests/network.rs"]
223mod tests;