ccxt_exchanges/hyperliquid/
builder.rs

1//! HyperLiquid builder module.
2//!
3//! Provides a builder pattern for creating HyperLiquid exchange instances.
4
5use ccxt_core::{ExchangeConfig, Result};
6
7use super::{HyperLiquid, HyperLiquidAuth, HyperLiquidOptions};
8
9/// Builder for creating HyperLiquid exchange instances.
10///
11/// # Example
12///
13/// ```no_run
14/// use ccxt_exchanges::hyperliquid::HyperLiquidBuilder;
15///
16/// let exchange = HyperLiquidBuilder::new()
17///     .private_key("0x...")
18///     .testnet(true)
19///     .default_leverage(10)
20///     .build()
21///     .unwrap();
22/// ```
23#[derive(Debug, Default)]
24pub struct HyperLiquidBuilder {
25    private_key: Option<String>,
26    testnet: bool,
27    vault_address: Option<String>,
28    default_leverage: u32,
29}
30
31impl HyperLiquidBuilder {
32    /// Creates a new HyperLiquidBuilder with default settings.
33    pub fn new() -> Self {
34        Self {
35            private_key: None,
36            testnet: false,
37            vault_address: None,
38            default_leverage: 1,
39        }
40    }
41
42    /// Sets the Ethereum private key for authentication.
43    ///
44    /// The private key should be a 64-character hex string (32 bytes),
45    /// optionally prefixed with "0x".
46    ///
47    /// # Arguments
48    ///
49    /// * `key` - The private key in hex format.
50    ///
51    /// # Example
52    ///
53    /// ```no_run
54    /// use ccxt_exchanges::hyperliquid::HyperLiquidBuilder;
55    ///
56    /// let builder = HyperLiquidBuilder::new()
57    ///     .private_key("0x1234567890abcdef...");
58    /// ```
59    pub fn private_key(mut self, key: &str) -> Self {
60        self.private_key = Some(key.to_string());
61        self
62    }
63
64    /// Enables or disables testnet mode.
65    ///
66    /// When enabled, the exchange will connect to HyperLiquid testnet
67    /// instead of mainnet.
68    ///
69    /// # Arguments
70    ///
71    /// * `enabled` - Whether to use testnet.
72    pub fn testnet(mut self, enabled: bool) -> Self {
73        self.testnet = enabled;
74        self
75    }
76
77    /// Sets the vault address for vault trading.
78    ///
79    /// When set, orders will be placed on behalf of the vault.
80    ///
81    /// # Arguments
82    ///
83    /// * `address` - The vault's Ethereum address.
84    pub fn vault_address(mut self, address: &str) -> Self {
85        self.vault_address = Some(address.to_string());
86        self
87    }
88
89    /// Sets the default leverage multiplier.
90    ///
91    /// This leverage will be used when placing orders if not specified.
92    ///
93    /// # Arguments
94    ///
95    /// * `leverage` - The leverage multiplier (1-50).
96    pub fn default_leverage(mut self, leverage: u32) -> Self {
97        self.default_leverage = leverage.clamp(1, 50);
98        self
99    }
100
101    /// Builds the HyperLiquid exchange instance.
102    ///
103    /// # Returns
104    ///
105    /// Returns a configured `HyperLiquid` instance.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if:
110    /// - The private key format is invalid
111    /// - The exchange configuration fails
112    pub fn build(self) -> Result<HyperLiquid> {
113        // Create authentication if private key is provided
114        let auth = if let Some(ref key) = self.private_key {
115            Some(HyperLiquidAuth::from_private_key(key)?)
116        } else {
117            None
118        };
119
120        // Create options
121        let options = HyperLiquidOptions {
122            testnet: self.testnet,
123            vault_address: self.vault_address,
124            default_leverage: self.default_leverage,
125        };
126
127        // Create exchange config
128        let config = ExchangeConfig {
129            id: "hyperliquid".to_string(),
130            name: "HyperLiquid".to_string(),
131            sandbox: self.testnet,
132            ..Default::default()
133        };
134
135        HyperLiquid::new_with_options(config, options, auth)
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_builder_default() {
145        let builder = HyperLiquidBuilder::new();
146        assert!(builder.private_key.is_none());
147        assert!(!builder.testnet);
148        assert!(builder.vault_address.is_none());
149        assert_eq!(builder.default_leverage, 1);
150    }
151
152    #[test]
153    fn test_builder_testnet() {
154        let builder = HyperLiquidBuilder::new().testnet(true);
155        assert!(builder.testnet);
156    }
157
158    #[test]
159    fn test_builder_leverage_clamping() {
160        let builder = HyperLiquidBuilder::new().default_leverage(100);
161        assert_eq!(builder.default_leverage, 50);
162
163        let builder = HyperLiquidBuilder::new().default_leverage(0);
164        assert_eq!(builder.default_leverage, 1);
165    }
166
167    #[test]
168    fn test_builder_vault_address() {
169        let builder =
170            HyperLiquidBuilder::new().vault_address("0x1234567890abcdef1234567890abcdef12345678");
171        assert!(builder.vault_address.is_some());
172    }
173
174    #[test]
175    fn test_build_without_auth() {
176        let exchange = HyperLiquidBuilder::new().testnet(true).build();
177
178        assert!(exchange.is_ok());
179        let exchange = exchange.unwrap();
180        assert_eq!(exchange.id(), "hyperliquid");
181        assert!(exchange.options().testnet);
182        assert!(exchange.auth().is_none());
183    }
184}