Skip to main content

nautilus_hyperliquid/
config.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Configuration structures for the Hyperliquid adapter.
17
18use nautilus_network::websocket::TransportBackend;
19use serde::{Deserialize, Serialize};
20
21use crate::common::{
22    consts::{info_url, ws_url},
23    enums::HyperliquidEnvironment,
24};
25
26/// Configuration for the Hyperliquid data client.
27#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
28#[serde(default, deny_unknown_fields)]
29#[cfg_attr(
30    feature = "python",
31    pyo3::pyclass(
32        module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
33        from_py_object
34    )
35)]
36#[cfg_attr(
37    feature = "python",
38    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
39)]
40pub struct HyperliquidDataClientConfig {
41    /// Optional private key for authenticated endpoints.
42    pub private_key: Option<String>,
43    /// Override for the WebSocket URL.
44    pub base_url_ws: Option<String>,
45    /// Override for the HTTP info URL.
46    pub base_url_http: Option<String>,
47    /// Optional proxy URL for HTTP and WebSocket transports.
48    pub proxy_url: Option<String>,
49    /// The target environment (mainnet or testnet).
50    #[builder(default)]
51    pub environment: HyperliquidEnvironment,
52    /// HTTP timeout in seconds.
53    #[builder(default = 60)]
54    pub http_timeout_secs: u64,
55    /// WebSocket timeout in seconds.
56    #[builder(default = 30)]
57    pub ws_timeout_secs: u64,
58    /// Interval for refreshing instruments in minutes.
59    #[builder(default = 60)]
60    pub update_instruments_interval_mins: u64,
61    /// WebSocket transport backend (`Sockudo` by default; `Tungstenite` when
62    /// the `transport-sockudo` feature is disabled).
63    #[builder(default)]
64    pub transport_backend: TransportBackend,
65}
66
67impl Default for HyperliquidDataClientConfig {
68    fn default() -> Self {
69        Self::builder().build()
70    }
71}
72
73impl HyperliquidDataClientConfig {
74    /// Creates a new configuration with default settings.
75    #[must_use]
76    pub fn new() -> Self {
77        Self::default()
78    }
79
80    /// Returns `true` when private key is populated and non-empty.
81    #[must_use]
82    pub fn has_credentials(&self) -> bool {
83        self.private_key
84            .as_deref()
85            .is_some_and(|s| !s.trim().is_empty())
86    }
87
88    /// Returns the WebSocket URL, respecting the environment and overrides.
89    #[must_use]
90    pub fn ws_url(&self) -> String {
91        self.base_url_ws
92            .clone()
93            .unwrap_or_else(|| ws_url(self.environment).to_string())
94    }
95
96    /// Returns the HTTP info URL, respecting the environment and overrides.
97    #[must_use]
98    pub fn http_url(&self) -> String {
99        self.base_url_http
100            .clone()
101            .unwrap_or_else(|| info_url(self.environment).to_string())
102    }
103}
104
105/// Configuration for the Hyperliquid execution client.
106#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
107#[serde(default, deny_unknown_fields)]
108#[cfg_attr(
109    feature = "python",
110    pyo3::pyclass(
111        module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
112        from_py_object
113    )
114)]
115#[cfg_attr(
116    feature = "python",
117    pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.adapters.hyperliquid")
118)]
119pub struct HyperliquidExecClientConfig {
120    /// Private key for signing transactions.
121    ///
122    /// If not provided, falls back to environment variable:
123    /// - Mainnet: `HYPERLIQUID_PK`
124    /// - Testnet: `HYPERLIQUID_TESTNET_PK`
125    pub private_key: Option<String>,
126    /// Optional vault address for vault operations.
127    ///
128    /// If not provided, falls back to environment variable:
129    /// - Mainnet: `HYPERLIQUID_VAULT`
130    /// - Testnet: `HYPERLIQUID_TESTNET_VAULT`
131    pub vault_address: Option<String>,
132    /// Optional main account address when using an agent wallet (API sub-key).
133    /// When set, used for balance queries, position reports, and WS subscriptions
134    /// instead of the address derived from the private key.
135    ///
136    /// If not provided and no explicit vault address is set, falls back to
137    /// the `HYPERLIQUID_ACCOUNT_ADDRESS` environment variable.
138    pub account_address: Option<String>,
139    /// Override for the WebSocket URL.
140    pub base_url_ws: Option<String>,
141    /// Override for the HTTP info URL.
142    pub base_url_http: Option<String>,
143    /// Override for the exchange API URL.
144    pub base_url_exchange: Option<String>,
145    /// Optional proxy URL for HTTP and WebSocket transports.
146    pub proxy_url: Option<String>,
147    /// The target environment (mainnet or testnet).
148    #[builder(default)]
149    pub environment: HyperliquidEnvironment,
150    /// HTTP timeout in seconds.
151    #[builder(default = 60)]
152    pub http_timeout_secs: u64,
153    /// Maximum number of retry attempts for HTTP requests.
154    #[builder(default = 3)]
155    pub max_retries: u32,
156    /// Initial retry delay in milliseconds.
157    #[builder(default = 100)]
158    pub retry_delay_initial_ms: u64,
159    /// Maximum retry delay in milliseconds.
160    #[builder(default = 5000)]
161    pub retry_delay_max_ms: u64,
162    /// When true, normalize order prices to 5 significant figures
163    /// before submission (Hyperliquid requirement).
164    #[builder(default = true)]
165    pub normalize_prices: bool,
166    /// Slippage buffer in basis points applied to MARKET orders and
167    /// stop-to-limit trigger derivations. Can be overridden per-order via
168    /// `SubmitOrder.params["market_order_slippage_bps"]`.
169    #[builder(default = 50)]
170    pub market_order_slippage_bps: u32,
171    /// If true, attach Nautilus builder attribution to eligible mainnet orders.
172    #[builder(default = true)]
173    pub include_builder_attribution: bool,
174    /// WebSocket transport backend (`Sockudo` by default; `Tungstenite` when
175    /// the `transport-sockudo` feature is disabled).
176    #[builder(default)]
177    pub transport_backend: TransportBackend,
178    /// Timeout in seconds for WebSocket post trading requests.
179    #[builder(default = 10)]
180    pub ws_post_timeout_secs: u64,
181    /// Poll interval in seconds for `outcomeMeta` settlement detection.
182    /// Disabled by default; venue `Settlement` fills drive HIP-4 settlement
183    /// through the standard user-fills stream. Set to a non-zero value only
184    /// when the venue fill stream is unavailable.
185    #[builder(default = 0)]
186    pub outcome_settlement_poll_secs: u64,
187}
188
189impl Default for HyperliquidExecClientConfig {
190    fn default() -> Self {
191        Self::builder().build()
192    }
193}
194
195impl HyperliquidExecClientConfig {
196    /// Returns `true` when private key is populated and non-empty.
197    #[must_use]
198    pub fn has_credentials(&self) -> bool {
199        self.private_key
200            .as_deref()
201            .is_some_and(|s| !s.trim().is_empty())
202    }
203
204    /// Returns the WebSocket URL, respecting the environment and overrides.
205    #[must_use]
206    pub fn ws_url(&self) -> String {
207        self.base_url_ws
208            .clone()
209            .unwrap_or_else(|| ws_url(self.environment).to_string())
210    }
211
212    /// Returns the HTTP info URL, respecting the environment and overrides.
213    #[must_use]
214    pub fn http_url(&self) -> String {
215        self.base_url_http
216            .clone()
217            .unwrap_or_else(|| info_url(self.environment).to_string())
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use rstest::rstest;
224
225    use super::*;
226
227    #[rstest]
228    fn test_exec_config_default_account_address_is_none() {
229        let config = HyperliquidExecClientConfig::default();
230        assert!(config.account_address.is_none());
231    }
232
233    #[rstest]
234    fn test_exec_config_with_account_address() {
235        let config = HyperliquidExecClientConfig {
236            account_address: Some("0x1234".to_string()),
237            ..HyperliquidExecClientConfig::default()
238        };
239        assert_eq!(config.account_address.as_deref(), Some("0x1234"));
240    }
241
242    #[rstest]
243    fn test_data_config_toml_minimal() {
244        let config: HyperliquidDataClientConfig = toml::from_str(
245            r#"
246environment = "testnet"
247http_timeout_secs = 30
248update_instruments_interval_mins = 10
249transport_backend = "tungstenite"
250"#,
251        )
252        .unwrap();
253
254        assert_eq!(config.environment, HyperliquidEnvironment::Testnet);
255        assert_eq!(config.http_timeout_secs, 30);
256        assert_eq!(config.update_instruments_interval_mins, 10);
257        assert_eq!(config.transport_backend, TransportBackend::Tungstenite);
258    }
259
260    #[rstest]
261    fn test_exec_config_toml_empty_uses_defaults() {
262        let config: HyperliquidExecClientConfig = toml::from_str("").unwrap();
263        let expected = HyperliquidExecClientConfig::default();
264
265        assert_eq!(config.environment, expected.environment);
266        assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
267        assert_eq!(config.max_retries, expected.max_retries);
268        assert_eq!(config.normalize_prices, expected.normalize_prices);
269        assert_eq!(
270            config.market_order_slippage_bps,
271            expected.market_order_slippage_bps,
272        );
273        assert_eq!(
274            config.include_builder_attribution,
275            expected.include_builder_attribution,
276        );
277        assert_eq!(config.transport_backend, expected.transport_backend);
278        assert_eq!(config.ws_post_timeout_secs, expected.ws_post_timeout_secs);
279        assert_eq!(
280            config.outcome_settlement_poll_secs,
281            expected.outcome_settlement_poll_secs,
282        );
283    }
284
285    #[rstest]
286    fn test_exec_config_toml_include_builder_attribution_false() {
287        let config: HyperliquidExecClientConfig =
288            toml::from_str("include_builder_attribution = false").unwrap();
289
290        assert!(!config.include_builder_attribution);
291    }
292}