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