1use nautilus_model::identifiers::{AccountId, TraderId};
19use serde::{Deserialize, Serialize};
20
21use crate::{
22 common::{
23 consts::{DYDX_CHAIN_ID, DYDX_TESTNET_CHAIN_ID},
24 enums::DydxNetwork,
25 urls,
26 },
27 grpc::types::ChainId,
28};
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct DydxAdapterConfig {
33 #[serde(default)]
35 pub network: DydxNetwork,
36 pub base_url: String,
38 pub ws_url: String,
40 pub grpc_url: String,
45 #[serde(default)]
51 pub grpc_urls: Vec<String>,
52 pub chain_id: String,
54 pub timeout_secs: u64,
56 #[serde(default)]
58 pub wallet_address: Option<String>,
59 #[serde(default)]
61 pub subaccount: u32,
62 #[serde(default)]
69 pub is_testnet: bool,
70 #[serde(default)]
72 pub mnemonic: Option<String>,
73 #[serde(default)]
82 pub authenticator_ids: Vec<u64>,
83 #[serde(default = "default_max_retries")]
85 pub max_retries: u32,
86 #[serde(default = "default_retry_delay_initial_ms")]
88 pub retry_delay_initial_ms: u64,
89 #[serde(default = "default_retry_delay_max_ms")]
91 pub retry_delay_max_ms: u64,
92}
93
94fn default_max_retries() -> u32 {
95 3
96}
97
98fn default_retry_delay_initial_ms() -> u64 {
99 1000
100}
101
102fn default_retry_delay_max_ms() -> u64 {
103 10000
104}
105
106impl DydxAdapterConfig {
107 #[must_use]
112 pub fn get_grpc_urls(&self) -> Vec<String> {
113 if self.grpc_urls.is_empty() {
114 vec![self.grpc_url.clone()]
115 } else {
116 self.grpc_urls.clone()
117 }
118 }
119
120 #[must_use]
124 pub const fn get_chain_id(&self) -> ChainId {
125 self.network.chain_id()
126 }
127
128 #[must_use]
133 pub const fn compute_is_testnet(&self) -> bool {
134 matches!(self.network, DydxNetwork::Testnet)
135 }
136}
137
138impl Default for DydxAdapterConfig {
139 fn default() -> Self {
140 let network = DydxNetwork::default();
141 let is_testnet = matches!(network, DydxNetwork::Testnet);
142 let grpc_urls = urls::grpc_urls(is_testnet);
143 Self {
144 network,
145 base_url: urls::http_base_url(is_testnet).to_string(),
146 ws_url: urls::ws_url(is_testnet).to_string(),
147 grpc_url: grpc_urls[0].to_string(),
148 grpc_urls: grpc_urls.iter().map(|&s| s.to_string()).collect(),
149 chain_id: if is_testnet {
150 DYDX_TESTNET_CHAIN_ID
151 } else {
152 DYDX_CHAIN_ID
153 }
154 .to_string(),
155 timeout_secs: 30,
156 wallet_address: None,
157 subaccount: 0,
158 is_testnet,
159 mnemonic: None,
160 authenticator_ids: Vec::new(),
161 max_retries: default_max_retries(),
162 retry_delay_initial_ms: default_retry_delay_initial_ms(),
163 retry_delay_max_ms: default_retry_delay_max_ms(),
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct DydxDataClientConfig {
171 pub base_url_http: Option<String>,
173 pub base_url_ws: Option<String>,
175 pub http_timeout_secs: Option<u64>,
177 pub max_retries: Option<u64>,
179 pub retry_delay_initial_ms: Option<u64>,
181 pub retry_delay_max_ms: Option<u64>,
183 pub is_testnet: bool,
185 pub http_proxy_url: Option<String>,
187 pub ws_proxy_url: Option<String>,
189 pub orderbook_refresh_interval_secs: Option<u64>,
192 pub instrument_refresh_interval_secs: Option<u64>,
195}
196
197impl Default for DydxDataClientConfig {
198 fn default() -> Self {
199 Self {
200 base_url_http: None,
201 base_url_ws: None,
202 http_timeout_secs: Some(60),
203 max_retries: Some(3),
204 retry_delay_initial_ms: Some(100),
205 retry_delay_max_ms: Some(5000),
206 is_testnet: false,
207 http_proxy_url: None,
208 ws_proxy_url: None,
209 orderbook_refresh_interval_secs: Some(60),
210 instrument_refresh_interval_secs: Some(3600),
211 }
212 }
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct DYDXExecClientConfig {
218 pub trader_id: TraderId,
220 pub account_id: AccountId,
222 #[serde(default)]
224 pub network: DydxNetwork,
225 pub grpc_endpoint: Option<String>,
227 #[serde(default)]
229 pub grpc_urls: Vec<String>,
230 pub ws_endpoint: Option<String>,
232 pub http_endpoint: Option<String>,
234 pub mnemonic: Option<String>,
236 pub wallet_address: Option<String>,
238 #[serde(default)]
240 pub subaccount_number: u32,
241 #[serde(default)]
243 pub authenticator_ids: Vec<u64>,
244 pub http_timeout_secs: Option<u64>,
246 pub max_retries: Option<u32>,
248 pub retry_delay_initial_ms: Option<u64>,
250 pub retry_delay_max_ms: Option<u64>,
252}
253
254impl DYDXExecClientConfig {
255 #[must_use]
260 pub fn get_grpc_urls(&self) -> Vec<String> {
261 if !self.grpc_urls.is_empty() {
262 return self.grpc_urls.clone();
263 }
264 if let Some(ref endpoint) = self.grpc_endpoint {
265 return vec![endpoint.clone()];
266 }
267 let is_testnet = matches!(self.network, DydxNetwork::Testnet);
268 urls::grpc_urls(is_testnet)
269 .iter()
270 .map(|&s| s.to_string())
271 .collect()
272 }
273
274 #[must_use]
276 pub fn get_ws_url(&self) -> String {
277 self.ws_endpoint.clone().unwrap_or_else(|| {
278 let is_testnet = matches!(self.network, DydxNetwork::Testnet);
279 urls::ws_url(is_testnet).to_string()
280 })
281 }
282
283 #[must_use]
285 pub fn get_http_url(&self) -> String {
286 self.http_endpoint.clone().unwrap_or_else(|| {
287 let is_testnet = matches!(self.network, DydxNetwork::Testnet);
288 urls::http_base_url(is_testnet).to_string()
289 })
290 }
291
292 #[must_use]
294 pub const fn get_chain_id(&self) -> ChainId {
295 self.network.chain_id()
296 }
297
298 #[must_use]
300 pub const fn is_testnet(&self) -> bool {
301 matches!(self.network, DydxNetwork::Testnet)
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use rstest::rstest;
308
309 use super::*;
310
311 #[rstest]
312 fn test_config_get_chain_id_mainnet() {
313 let config = DydxAdapterConfig {
314 network: DydxNetwork::Mainnet,
315 ..Default::default()
316 };
317 assert_eq!(config.get_chain_id(), ChainId::Mainnet1);
318 }
319
320 #[rstest]
321 fn test_config_get_chain_id_testnet() {
322 let config = DydxAdapterConfig {
323 network: DydxNetwork::Testnet,
324 ..Default::default()
325 };
326 assert_eq!(config.get_chain_id(), ChainId::Testnet4);
327 }
328
329 #[rstest]
330 fn test_config_compute_is_testnet() {
331 let mainnet_config = DydxAdapterConfig {
332 network: DydxNetwork::Mainnet,
333 ..Default::default()
334 };
335 assert!(!mainnet_config.compute_is_testnet());
336
337 let testnet_config = DydxAdapterConfig {
338 network: DydxNetwork::Testnet,
339 ..Default::default()
340 };
341 assert!(testnet_config.compute_is_testnet());
342 }
343
344 #[rstest]
345 fn test_config_default_uses_mainnet() {
346 let config = DydxAdapterConfig::default();
347 assert_eq!(config.network, DydxNetwork::Mainnet);
348 assert!(!config.is_testnet);
349 }
350
351 #[rstest]
352 fn test_config_network_canonical_over_is_testnet() {
353 let config = DydxAdapterConfig {
355 network: DydxNetwork::Mainnet,
356 is_testnet: true, ..Default::default()
358 };
359 assert_eq!(config.get_chain_id(), ChainId::Mainnet1); assert!(!config.compute_is_testnet()); }
362
363 #[rstest]
364 fn test_config_serde_backwards_compat() {
365 let json = r#"{"base_url":"https://indexer.dydx.trade","ws_url":"wss://indexer.dydx.trade/v4/ws","grpc_url":"https://dydx-ops-grpc.kingnodes.com:443","grpc_urls":[],"chain_id":"dydx-mainnet-1","timeout_secs":30,"subaccount":0,"is_testnet":false,"max_retries":3,"retry_delay_initial_ms":1000,"retry_delay_max_ms":10000}"#;
367
368 let config: Result<DydxAdapterConfig, _> = serde_json::from_str(json);
369 assert!(config.is_ok());
370 let config = config.unwrap();
371 assert_eq!(config.network, DydxNetwork::Mainnet);
373 }
374
375 #[rstest]
376 fn test_config_get_grpc_urls_fallback() {
377 let config = DydxAdapterConfig {
378 grpc_url: "https://primary.example.com".to_string(),
379 grpc_urls: vec![],
380 ..Default::default()
381 };
382
383 let urls = config.get_grpc_urls();
384 assert_eq!(urls.len(), 1);
385 assert_eq!(urls[0], "https://primary.example.com");
386 }
387
388 #[rstest]
389 fn test_config_get_grpc_urls_multiple() {
390 let config = DydxAdapterConfig {
391 grpc_url: "https://primary.example.com".to_string(),
392 grpc_urls: vec![
393 "https://fallback1.example.com".to_string(),
394 "https://fallback2.example.com".to_string(),
395 ],
396 ..Default::default()
397 };
398
399 let urls = config.get_grpc_urls();
400 assert_eq!(urls.len(), 2);
401 assert_eq!(urls[0], "https://fallback1.example.com");
402 assert_eq!(urls[1], "https://fallback2.example.com");
403 }
404}