drasi_source_hyperliquid/
config.rs1use anyhow::{anyhow, Result};
18use serde::{Deserialize, Serialize};
19
20#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
22#[serde(tag = "type", rename_all = "snake_case")]
23pub enum InitialCursor {
24 StartFromBeginning,
26 #[default]
28 StartFromNow,
29 StartFromTimestamp { timestamp: i64 },
31}
32
33impl InitialCursor {
34 pub fn start_timestamp(&self) -> Option<i64> {
36 match self {
37 Self::StartFromBeginning => None,
38 Self::StartFromNow => Some(chrono::Utc::now().timestamp_millis()),
39 Self::StartFromTimestamp { timestamp } => Some(*timestamp),
40 }
41 }
42}
43
44#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(rename_all = "snake_case")]
47pub enum HyperliquidNetwork {
48 #[default]
49 Mainnet,
50 Testnet,
51 Custom {
52 rest_url: String,
53 ws_url: String,
54 },
55}
56
57impl HyperliquidNetwork {
58 pub fn rest_url(&self) -> String {
59 match self {
60 Self::Mainnet => "https://api.hyperliquid.xyz/info".to_string(),
61 Self::Testnet => "https://api.hyperliquid-testnet.xyz/info".to_string(),
62 Self::Custom { rest_url, .. } => rest_url.clone(),
63 }
64 }
65
66 pub fn ws_url(&self) -> String {
67 match self {
68 Self::Mainnet => "wss://api.hyperliquid.xyz/ws".to_string(),
69 Self::Testnet => "wss://api.hyperliquid-testnet.xyz/ws".to_string(),
70 Self::Custom { ws_url, .. } => ws_url.clone(),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
77#[serde(tag = "type", rename_all = "snake_case")]
78pub enum CoinSelection {
79 Specific { coins: Vec<String> },
81 #[default]
83 All,
84}
85
86impl CoinSelection {
87 pub fn coins(&self) -> Option<&[String]> {
88 match self {
89 Self::Specific { coins } => Some(coins.as_slice()),
90 Self::All => None,
91 }
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97pub struct HyperliquidSourceConfig {
98 pub network: HyperliquidNetwork,
99 pub coins: CoinSelection,
100 pub enable_trades: bool,
101 pub enable_order_book: bool,
102 pub enable_mid_prices: bool,
103 pub enable_funding_rates: bool,
104 pub enable_liquidations: bool,
105 pub funding_poll_interval_secs: u64,
106 pub initial_cursor: InitialCursor,
107}
108
109impl Default for HyperliquidSourceConfig {
110 fn default() -> Self {
111 Self {
112 network: HyperliquidNetwork::Mainnet,
113 coins: CoinSelection::All,
114 enable_trades: false,
115 enable_order_book: false,
116 enable_mid_prices: true,
117 enable_funding_rates: false,
118 enable_liquidations: false,
119 funding_poll_interval_secs: 60,
120 initial_cursor: InitialCursor::StartFromNow,
121 }
122 }
123}
124
125impl HyperliquidSourceConfig {
126 pub fn validate(&self) -> Result<()> {
128 if let CoinSelection::Specific { coins } = &self.coins {
129 if coins.is_empty() {
130 return Err(anyhow!(
131 "Validation error: coins cannot be empty when using Specific selection"
132 ));
133 }
134 }
135
136 if self.funding_poll_interval_secs == 0 {
137 return Err(anyhow!(
138 "Validation error: funding_poll_interval_secs must be greater than 0"
139 ));
140 }
141
142 if !self.enable_trades
143 && !self.enable_order_book
144 && !self.enable_mid_prices
145 && !self.enable_funding_rates
146 && !self.enable_liquidations
147 {
148 return Err(anyhow!(
149 "Validation error: at least one data channel must be enabled"
150 ));
151 }
152
153 Ok(())
154 }
155}