ccxt_exchanges/hyperliquid/
builder.rs1use ccxt_core::types::default_type::DefaultType;
6use ccxt_core::{Error, ExchangeConfig, Result};
7
8use super::{HyperLiquid, HyperLiquidAuth, HyperLiquidOptions};
9
10#[derive(Debug, Default)]
31pub struct HyperLiquidBuilder {
32 private_key: Option<String>,
33 testnet: bool,
34 vault_address: Option<String>,
35 default_leverage: u32,
36 default_type: Option<DefaultType>,
37}
38
39impl HyperLiquidBuilder {
40 pub fn new() -> Self {
42 Self {
43 private_key: None,
44 testnet: false,
45 vault_address: None,
46 default_leverage: 1,
47 default_type: None, }
49 }
50
51 pub fn private_key(mut self, key: &str) -> Self {
69 self.private_key = Some(key.to_string());
70 self
71 }
72
73 pub fn sandbox(mut self, enabled: bool) -> Self {
85 self.testnet = enabled;
86 self
87 }
88
89 pub fn testnet(mut self, enabled: bool) -> Self {
101 self.testnet = enabled;
102 self
103 }
104
105 pub fn vault_address(mut self, address: &str) -> Self {
113 self.vault_address = Some(address.to_string());
114 self
115 }
116
117 pub fn default_leverage(mut self, leverage: u32) -> Self {
125 self.default_leverage = leverage.clamp(1, 50);
126 self
127 }
128
129 pub fn default_type(mut self, default_type: impl Into<DefaultType>) -> Self {
158 self.default_type = Some(default_type.into());
159 self
160 }
161
162 pub fn build(self) -> Result<HyperLiquid> {
175 let default_type = self.default_type.unwrap_or(DefaultType::Swap);
177 validate_default_type(default_type)?;
178
179 let auth = if let Some(ref key) = self.private_key {
181 Some(HyperLiquidAuth::from_private_key(key)?)
182 } else {
183 None
184 };
185
186 let options = HyperLiquidOptions {
188 testnet: self.testnet,
189 vault_address: self.vault_address,
190 default_leverage: self.default_leverage,
191 default_type,
192 };
193
194 let config = ExchangeConfig {
196 id: "hyperliquid".to_string(),
197 name: "HyperLiquid".to_string(),
198 sandbox: self.testnet,
199 ..Default::default()
200 };
201
202 HyperLiquid::new_with_options(config, options, auth)
203 }
204}
205
206pub fn validate_default_type(default_type: DefaultType) -> Result<()> {
219 match default_type {
220 DefaultType::Swap => Ok(()),
221 DefaultType::Spot => Err(Error::invalid_request(
222 "HyperLiquid does not support spot trading. Only perpetual futures (Swap) are available.",
223 )),
224 DefaultType::Futures => Err(Error::invalid_request(
225 "HyperLiquid does not support delivery futures. Only perpetual futures (Swap) are available.",
226 )),
227 DefaultType::Margin => Err(Error::invalid_request(
228 "HyperLiquid does not support margin trading. Only perpetual futures (Swap) are available.",
229 )),
230 DefaultType::Option => Err(Error::invalid_request(
231 "HyperLiquid does not support options trading. Only perpetual futures (Swap) are available.",
232 )),
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn test_builder_default() {
242 let builder = HyperLiquidBuilder::new();
243 assert!(builder.private_key.is_none());
244 assert!(!builder.testnet);
245 assert!(builder.vault_address.is_none());
246 assert_eq!(builder.default_leverage, 1);
247 assert!(builder.default_type.is_none()); }
249
250 #[test]
251 fn test_builder_sandbox() {
252 let builder = HyperLiquidBuilder::new().sandbox(true);
253 assert!(builder.testnet);
254 }
255
256 #[test]
257 fn test_builder_testnet() {
258 let builder = HyperLiquidBuilder::new().testnet(true);
259 assert!(builder.testnet);
260 }
261
262 #[test]
263 fn test_builder_sandbox_testnet_equivalence() {
264 let sandbox_builder = HyperLiquidBuilder::new().sandbox(true);
266 let testnet_builder = HyperLiquidBuilder::new().testnet(true);
267
268 assert_eq!(sandbox_builder.testnet, testnet_builder.testnet);
269 }
270
271 #[test]
272 fn test_builder_leverage_clamping() {
273 let builder = HyperLiquidBuilder::new().default_leverage(100);
274 assert_eq!(builder.default_leverage, 50);
275
276 let builder = HyperLiquidBuilder::new().default_leverage(0);
277 assert_eq!(builder.default_leverage, 1);
278 }
279
280 #[test]
281 fn test_builder_vault_address() {
282 let builder =
283 HyperLiquidBuilder::new().vault_address("0x1234567890abcdef1234567890abcdef12345678");
284 assert!(builder.vault_address.is_some());
285 }
286
287 #[test]
288 fn test_builder_default_type_swap() {
289 let builder = HyperLiquidBuilder::new().default_type(DefaultType::Swap);
290 assert_eq!(builder.default_type, Some(DefaultType::Swap));
291 }
292
293 #[test]
294 fn test_builder_default_type_from_string() {
295 let builder = HyperLiquidBuilder::new().default_type("swap");
296 assert_eq!(builder.default_type, Some(DefaultType::Swap));
297 }
298
299 #[test]
300 fn test_build_without_auth() {
301 let exchange = HyperLiquidBuilder::new().testnet(true).build();
302
303 assert!(exchange.is_ok());
304 let exchange = exchange.unwrap();
305 assert_eq!(exchange.id(), "hyperliquid");
306 assert!(exchange.options().testnet);
307 assert!(exchange.auth().is_none());
308 assert_eq!(exchange.options().default_type, DefaultType::Swap);
310 }
311
312 #[test]
313 fn test_build_with_swap_type() {
314 let exchange = HyperLiquidBuilder::new()
315 .testnet(true)
316 .default_type(DefaultType::Swap)
317 .build();
318
319 assert!(exchange.is_ok());
320 let exchange = exchange.unwrap();
321 assert_eq!(exchange.options().default_type, DefaultType::Swap);
322 }
323
324 #[test]
325 fn test_build_with_spot_type_fails() {
326 let result = HyperLiquidBuilder::new()
327 .testnet(true)
328 .default_type(DefaultType::Spot)
329 .build();
330
331 assert!(result.is_err());
332 let err = result.unwrap_err();
333 assert!(err.to_string().contains("spot"));
334 }
335
336 #[test]
337 fn test_build_with_futures_type_fails() {
338 let result = HyperLiquidBuilder::new()
339 .testnet(true)
340 .default_type(DefaultType::Futures)
341 .build();
342
343 assert!(result.is_err());
344 let err = result.unwrap_err();
345 assert!(err.to_string().contains("delivery futures"));
346 }
347
348 #[test]
349 fn test_build_with_margin_type_fails() {
350 let result = HyperLiquidBuilder::new()
351 .testnet(true)
352 .default_type(DefaultType::Margin)
353 .build();
354
355 assert!(result.is_err());
356 let err = result.unwrap_err();
357 assert!(err.to_string().contains("margin"));
358 }
359
360 #[test]
361 fn test_build_with_option_type_fails() {
362 let result = HyperLiquidBuilder::new()
363 .testnet(true)
364 .default_type(DefaultType::Option)
365 .build();
366
367 assert!(result.is_err());
368 let err = result.unwrap_err();
369 assert!(err.to_string().contains("options"));
370 }
371
372 #[test]
373 fn test_validate_default_type_swap() {
374 assert!(validate_default_type(DefaultType::Swap).is_ok());
375 }
376
377 #[test]
378 fn test_validate_default_type_spot() {
379 let result = validate_default_type(DefaultType::Spot);
380 assert!(result.is_err());
381 }
382
383 #[test]
384 fn test_validate_default_type_futures() {
385 let result = validate_default_type(DefaultType::Futures);
386 assert!(result.is_err());
387 }
388
389 #[test]
390 fn test_validate_default_type_margin() {
391 let result = validate_default_type(DefaultType::Margin);
392 assert!(result.is_err());
393 }
394
395 #[test]
396 fn test_validate_default_type_option() {
397 let result = validate_default_type(DefaultType::Option);
398 assert!(result.is_err());
399 }
400}