ccxt_exchanges/hyperliquid/endpoint_router.rs
1//! HyperLiquid-specific endpoint router trait.
2//!
3//! This module provides the `HyperLiquidEndpointRouter` trait for routing API requests
4//! to the correct HyperLiquid endpoints.
5//!
6//! # HyperLiquid API Structure
7//!
8//! HyperLiquid uses the simplest endpoint structure among all supported exchanges:
9//! - **REST**: `api.hyperliquid.xyz` - Single REST endpoint for all operations
10//! - **WebSocket**: `api.hyperliquid.xyz/ws` - Single WebSocket endpoint
11//!
12//! Unlike centralized exchanges, HyperLiquid is a decentralized perpetual futures
13//! exchange built on its own L1 blockchain. It uses:
14//! - Ethereum wallet private keys for authentication (EIP-712 typed data signatures)
15//! - Wallet addresses as account identifiers (no registration required)
16//! - USDC as the sole settlement currency
17//!
18//! # Testnet/Sandbox Mode
19//!
20//! HyperLiquid provides a separate testnet environment:
21//! - REST: `api.hyperliquid-testnet.xyz`
22//! - WebSocket: `api.hyperliquid-testnet.xyz/ws`
23//!
24//! # Example
25//!
26//! ```rust,no_run
27//! use ccxt_exchanges::hyperliquid::{HyperLiquid, HyperLiquidEndpointRouter};
28//!
29//! let exchange = HyperLiquid::builder()
30//! .testnet(false)
31//! .build()
32//! .unwrap();
33//!
34//! // Get REST endpoint
35//! let rest_url = exchange.rest_endpoint();
36//! assert!(rest_url.contains("api.hyperliquid.xyz"));
37//!
38//! // Get WebSocket endpoint
39//! let ws_url = exchange.ws_endpoint();
40//! assert!(ws_url.contains("api.hyperliquid.xyz/ws"));
41//! ```
42
43/// HyperLiquid-specific endpoint router trait.
44///
45/// This trait defines methods for obtaining the correct API endpoints for HyperLiquid.
46/// HyperLiquid uses the simplest structure with a single REST and WebSocket endpoint.
47///
48/// # Implementation Notes
49///
50/// - REST API uses a single domain for all operations (`/info` for public, `/exchange` for private)
51/// - WebSocket has a single endpoint for all data streams
52/// - Authentication is handled via EIP-712 signatures, not separate endpoints
53/// - Testnet mode switches to completely different domains
54///
55/// # Sandbox/Testnet Mode
56///
57/// When sandbox mode is enabled (via `config.sandbox` or `options.testnet`):
58/// - REST URL changes to `api.hyperliquid-testnet.xyz`
59/// - WebSocket URL changes to `api.hyperliquid-testnet.xyz/ws`
60pub trait HyperLiquidEndpointRouter {
61 /// Returns the REST API endpoint.
62 ///
63 /// HyperLiquid uses a single REST domain for all operations.
64 /// The operation type is specified in the API path:
65 /// - `/info` - Public data requests
66 /// - `/exchange` - Private/authenticated requests
67 ///
68 /// # Returns
69 ///
70 /// The REST API base URL string.
71 ///
72 /// # Production vs Testnet
73 ///
74 /// - Production: `https://api.hyperliquid.xyz`
75 /// - Testnet: `https://api.hyperliquid-testnet.xyz`
76 ///
77 /// # Example
78 ///
79 /// ```rust,no_run
80 /// use ccxt_exchanges::hyperliquid::{HyperLiquid, HyperLiquidEndpointRouter};
81 ///
82 /// let exchange = HyperLiquid::builder().build().unwrap();
83 /// let url = exchange.rest_endpoint();
84 /// assert_eq!(url, "https://api.hyperliquid.xyz");
85 /// ```
86 fn rest_endpoint(&self) -> &'static str;
87
88 /// Returns the WebSocket endpoint.
89 ///
90 /// HyperLiquid uses a single WebSocket endpoint for all data streams.
91 /// Unlike other exchanges, there is no separation between public and private
92 /// WebSocket connections - authentication is handled at the message level.
93 ///
94 /// # Returns
95 ///
96 /// The complete WebSocket URL.
97 ///
98 /// # Production vs Testnet
99 ///
100 /// - Production: `wss://api.hyperliquid.xyz/ws`
101 /// - Testnet: `wss://api.hyperliquid-testnet.xyz/ws`
102 ///
103 /// # Example
104 ///
105 /// ```rust,no_run
106 /// use ccxt_exchanges::hyperliquid::{HyperLiquid, HyperLiquidEndpointRouter};
107 ///
108 /// let exchange = HyperLiquid::builder().build().unwrap();
109 /// let url = exchange.ws_endpoint();
110 /// assert_eq!(url, "wss://api.hyperliquid.xyz/ws");
111 /// ```
112 fn ws_endpoint(&self) -> &str;
113}
114
115use super::HyperLiquid;
116
117impl HyperLiquidEndpointRouter for HyperLiquid {
118 fn rest_endpoint(&self) -> &'static str {
119 // Return static reference based on sandbox mode
120 // is_sandbox() checks both config.sandbox and options.testnet
121 if self.is_sandbox() {
122 "https://api.hyperliquid-testnet.xyz"
123 } else {
124 "https://api.hyperliquid.xyz"
125 }
126 }
127
128 fn ws_endpoint(&self) -> &str {
129 // Return static reference based on sandbox mode
130 // is_sandbox() checks both config.sandbox and options.testnet
131 if self.is_sandbox() {
132 "wss://api.hyperliquid-testnet.xyz/ws"
133 } else {
134 "wss://api.hyperliquid.xyz/ws"
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::hyperliquid::HyperLiquidOptions;
143 use ccxt_core::ExchangeConfig;
144
145 fn create_test_hyperliquid() -> HyperLiquid {
146 HyperLiquid::builder().build().unwrap()
147 }
148
149 fn create_testnet_hyperliquid() -> HyperLiquid {
150 HyperLiquid::builder().testnet(true).build().unwrap()
151 }
152
153 fn create_sandbox_hyperliquid() -> HyperLiquid {
154 let config = ExchangeConfig {
155 sandbox: true,
156 ..Default::default()
157 };
158 let options = HyperLiquidOptions::default();
159 HyperLiquid::new_with_options(config, options, None).unwrap()
160 }
161
162 // ==================== REST Endpoint Tests ====================
163
164 #[test]
165 fn test_rest_endpoint_production() {
166 let exchange = create_test_hyperliquid();
167 let url = exchange.rest_endpoint();
168 assert_eq!(url, "https://api.hyperliquid.xyz");
169 assert!(!url.contains("testnet"));
170 }
171
172 #[test]
173 fn test_rest_endpoint_testnet() {
174 let exchange = create_testnet_hyperliquid();
175 let url = exchange.rest_endpoint();
176 assert_eq!(url, "https://api.hyperliquid-testnet.xyz");
177 }
178
179 #[test]
180 fn test_rest_endpoint_sandbox() {
181 let exchange = create_sandbox_hyperliquid();
182 let url = exchange.rest_endpoint();
183 assert_eq!(url, "https://api.hyperliquid-testnet.xyz");
184 }
185
186 // ==================== WebSocket Endpoint Tests ====================
187
188 #[test]
189 fn test_ws_endpoint_production() {
190 let exchange = create_test_hyperliquid();
191 let url = exchange.ws_endpoint();
192 assert_eq!(url, "wss://api.hyperliquid.xyz/ws");
193 assert!(!url.contains("testnet"));
194 }
195
196 #[test]
197 fn test_ws_endpoint_testnet() {
198 let exchange = create_testnet_hyperliquid();
199 let url = exchange.ws_endpoint();
200 assert_eq!(url, "wss://api.hyperliquid-testnet.xyz/ws");
201 }
202
203 #[test]
204 fn test_ws_endpoint_sandbox() {
205 let exchange = create_sandbox_hyperliquid();
206 let url = exchange.ws_endpoint();
207 assert_eq!(url, "wss://api.hyperliquid-testnet.xyz/ws");
208 }
209
210 // ==================== Consistency Tests ====================
211
212 #[test]
213 fn test_rest_endpoint_consistency_with_urls() {
214 let exchange = create_test_hyperliquid();
215 let router_url = exchange.rest_endpoint();
216 let urls_rest = &exchange.urls().rest;
217 assert_eq!(router_url, urls_rest);
218 }
219
220 #[test]
221 fn test_ws_endpoint_consistency_with_urls() {
222 let exchange = create_test_hyperliquid();
223 let router_url = exchange.ws_endpoint();
224 let urls_ws = &exchange.urls().ws;
225 assert_eq!(router_url, urls_ws);
226 }
227
228 #[test]
229 fn test_testnet_rest_endpoint_consistency_with_urls() {
230 let exchange = create_testnet_hyperliquid();
231 let router_url = exchange.rest_endpoint();
232 let urls_rest = &exchange.urls().rest;
233 assert_eq!(router_url, urls_rest);
234 }
235
236 #[test]
237 fn test_testnet_ws_endpoint_consistency_with_urls() {
238 let exchange = create_testnet_hyperliquid();
239 let router_url = exchange.ws_endpoint();
240 let urls_ws = &exchange.urls().ws;
241 assert_eq!(router_url, urls_ws);
242 }
243
244 // ==================== URL Format Tests ====================
245
246 #[test]
247 fn test_rest_endpoint_uses_https() {
248 let exchange = create_test_hyperliquid();
249 let url = exchange.rest_endpoint();
250 assert!(url.starts_with("https://"));
251 }
252
253 #[test]
254 fn test_ws_endpoint_uses_wss() {
255 let exchange = create_test_hyperliquid();
256 let url = exchange.ws_endpoint();
257 assert!(url.starts_with("wss://"));
258 }
259
260 #[test]
261 fn test_ws_endpoint_contains_ws_path() {
262 let exchange = create_test_hyperliquid();
263 let url = exchange.ws_endpoint();
264 assert!(url.ends_with("/ws"));
265 }
266
267 // ==================== Sandbox Mode Tests ====================
268
269 #[test]
270 fn test_is_sandbox_with_testnet_option() {
271 let exchange = create_testnet_hyperliquid();
272 assert!(exchange.is_sandbox());
273 }
274
275 #[test]
276 fn test_is_sandbox_with_config_sandbox() {
277 let exchange = create_sandbox_hyperliquid();
278 assert!(exchange.is_sandbox());
279 }
280
281 #[test]
282 fn test_is_not_sandbox_by_default() {
283 let exchange = create_test_hyperliquid();
284 assert!(!exchange.is_sandbox());
285 }
286}