use nautilus_network::websocket::TransportBackend;
use serde::{Deserialize, Serialize};
use crate::common::{
consts::{info_url, ws_url},
enums::HyperliquidEnvironment,
};
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.hyperliquid")
)]
pub struct HyperliquidDataClientConfig {
pub private_key: Option<String>,
pub base_url_ws: Option<String>,
pub base_url_http: Option<String>,
pub proxy_url: Option<String>,
#[builder(default)]
pub environment: HyperliquidEnvironment,
#[builder(default = 60)]
pub http_timeout_secs: u64,
#[builder(default = 30)]
pub ws_timeout_secs: u64,
#[builder(default = 60)]
pub update_instruments_interval_mins: u64,
#[builder(default)]
pub transport_backend: TransportBackend,
}
impl Default for HyperliquidDataClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl HyperliquidDataClientConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn has_credentials(&self) -> bool {
self.private_key
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
}
#[must_use]
pub fn ws_url(&self) -> String {
self.base_url_ws
.clone()
.unwrap_or_else(|| ws_url(self.environment).to_string())
}
#[must_use]
pub fn http_url(&self) -> String {
self.base_url_http
.clone()
.unwrap_or_else(|| info_url(self.environment).to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, bon::Builder)]
#[serde(default, deny_unknown_fields)]
#[cfg_attr(
feature = "python",
pyo3::pyclass(
module = "nautilus_trader.core.nautilus_pyo3.hyperliquid",
from_py_object
)
)]
#[cfg_attr(
feature = "python",
pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.hyperliquid")
)]
pub struct HyperliquidExecClientConfig {
pub private_key: Option<String>,
pub vault_address: Option<String>,
pub account_address: Option<String>,
pub base_url_ws: Option<String>,
pub base_url_http: Option<String>,
pub base_url_exchange: Option<String>,
pub proxy_url: Option<String>,
#[builder(default)]
pub environment: HyperliquidEnvironment,
#[builder(default = 60)]
pub http_timeout_secs: u64,
#[builder(default = 3)]
pub max_retries: u32,
#[builder(default = 100)]
pub retry_delay_initial_ms: u64,
#[builder(default = 5000)]
pub retry_delay_max_ms: u64,
#[builder(default = true)]
pub normalize_prices: bool,
#[builder(default = 50)]
pub market_order_slippage_bps: u32,
#[builder(default)]
pub transport_backend: TransportBackend,
#[builder(default = 0)]
pub outcome_settlement_poll_secs: u64,
}
impl Default for HyperliquidExecClientConfig {
fn default() -> Self {
Self::builder().build()
}
}
impl HyperliquidExecClientConfig {
#[must_use]
pub fn has_credentials(&self) -> bool {
self.private_key
.as_deref()
.is_some_and(|s| !s.trim().is_empty())
}
#[must_use]
pub fn ws_url(&self) -> String {
self.base_url_ws
.clone()
.unwrap_or_else(|| ws_url(self.environment).to_string())
}
#[must_use]
pub fn http_url(&self) -> String {
self.base_url_http
.clone()
.unwrap_or_else(|| info_url(self.environment).to_string())
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_exec_config_default_account_address_is_none() {
let config = HyperliquidExecClientConfig::default();
assert!(config.account_address.is_none());
}
#[rstest]
fn test_exec_config_with_account_address() {
let config = HyperliquidExecClientConfig {
account_address: Some("0x1234".to_string()),
..HyperliquidExecClientConfig::default()
};
assert_eq!(config.account_address.as_deref(), Some("0x1234"));
}
#[rstest]
fn test_data_config_toml_minimal() {
let config: HyperliquidDataClientConfig = toml::from_str(
r#"
environment = "testnet"
http_timeout_secs = 30
update_instruments_interval_mins = 10
transport_backend = "tungstenite"
"#,
)
.unwrap();
assert_eq!(config.environment, HyperliquidEnvironment::Testnet);
assert_eq!(config.http_timeout_secs, 30);
assert_eq!(config.update_instruments_interval_mins, 10);
assert_eq!(config.transport_backend, TransportBackend::Tungstenite);
}
#[rstest]
fn test_exec_config_toml_empty_uses_defaults() {
let config: HyperliquidExecClientConfig = toml::from_str("").unwrap();
let expected = HyperliquidExecClientConfig::default();
assert_eq!(config.environment, expected.environment);
assert_eq!(config.http_timeout_secs, expected.http_timeout_secs);
assert_eq!(config.max_retries, expected.max_retries);
assert_eq!(config.normalize_prices, expected.normalize_prices);
assert_eq!(
config.market_order_slippage_bps,
expected.market_order_slippage_bps,
);
assert_eq!(config.transport_backend, expected.transport_backend);
assert_eq!(
config.outcome_settlement_poll_secs,
expected.outcome_settlement_poll_secs,
);
}
}