1use serde::{Deserialize, Serialize};
19
20use crate::{
21 common::{
22 consts::{DYDX_CHAIN_ID, DYDX_GRPC_URLS, DYDX_TESTNET_CHAIN_ID, DYDX_WS_URL},
23 enums::DydxNetwork,
24 urls,
25 },
26 grpc::types::ChainId,
27};
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct DydxAdapterConfig {
32 #[serde(default)]
34 pub network: DydxNetwork,
35 pub base_url: String,
37 pub ws_url: String,
39 pub grpc_url: String,
44 #[serde(default)]
50 pub grpc_urls: Vec<String>,
51 pub chain_id: String,
53 pub timeout_secs: u64,
55 #[serde(default)]
57 pub wallet_address: Option<String>,
58 #[serde(default)]
60 pub subaccount: u32,
61 #[serde(default)]
68 pub is_testnet: bool,
69 #[serde(default)]
71 pub mnemonic: Option<String>,
72 #[serde(default)]
81 pub authenticator_ids: Vec<u64>,
82 #[serde(default = "default_max_retries")]
84 pub max_retries: u32,
85 #[serde(default = "default_retry_delay_initial_ms")]
87 pub retry_delay_initial_ms: u64,
88 #[serde(default = "default_retry_delay_max_ms")]
90 pub retry_delay_max_ms: u64,
91}
92
93fn default_max_retries() -> u32 {
94 3
95}
96
97fn default_retry_delay_initial_ms() -> u64 {
98 1000
99}
100
101fn default_retry_delay_max_ms() -> u64 {
102 10000
103}
104
105impl DydxAdapterConfig {
106 #[must_use]
111 pub fn get_grpc_urls(&self) -> Vec<String> {
112 if !self.grpc_urls.is_empty() {
113 self.grpc_urls.clone()
114 } else {
115 vec![self.grpc_url.clone()]
116 }
117 }
118
119 #[must_use]
123 pub const fn get_chain_id(&self) -> ChainId {
124 self.network.chain_id()
125 }
126
127 #[must_use]
132 pub const fn compute_is_testnet(&self) -> bool {
133 matches!(self.network, DydxNetwork::Testnet)
134 }
135}
136
137impl Default for DydxAdapterConfig {
138 fn default() -> Self {
139 let network = DydxNetwork::default();
140 let is_testnet = matches!(network, DydxNetwork::Testnet);
141 let grpc_urls = urls::grpc_urls(is_testnet);
142 Self {
143 network,
144 base_url: urls::http_base_url(is_testnet).to_string(),
145 ws_url: urls::ws_url(is_testnet).to_string(),
146 grpc_url: grpc_urls[0].to_string(),
147 grpc_urls: grpc_urls.iter().map(|&s| s.to_string()).collect(),
148 chain_id: if is_testnet {
149 DYDX_TESTNET_CHAIN_ID
150 } else {
151 DYDX_CHAIN_ID
152 }
153 .to_string(),
154 timeout_secs: 30,
155 wallet_address: None,
156 subaccount: 0,
157 is_testnet,
158 mnemonic: None,
159 authenticator_ids: Vec::new(),
160 max_retries: default_max_retries(),
161 retry_delay_initial_ms: default_retry_delay_initial_ms(),
162 retry_delay_max_ms: default_retry_delay_max_ms(),
163 }
164 }
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct DydxDataClientConfig {
170 pub base_url_http: Option<String>,
172 pub base_url_ws: Option<String>,
174 pub http_timeout_secs: Option<u64>,
176 pub max_retries: Option<u64>,
178 pub retry_delay_initial_ms: Option<u64>,
180 pub retry_delay_max_ms: Option<u64>,
182 pub is_testnet: bool,
184 pub http_proxy_url: Option<String>,
186 pub ws_proxy_url: Option<String>,
188 pub orderbook_refresh_interval_secs: Option<u64>,
191 pub instrument_refresh_interval_secs: Option<u64>,
194}
195
196impl Default for DydxDataClientConfig {
197 fn default() -> Self {
198 Self {
199 base_url_http: None,
200 base_url_ws: None,
201 http_timeout_secs: Some(60),
202 max_retries: Some(3),
203 retry_delay_initial_ms: Some(100),
204 retry_delay_max_ms: Some(5000),
205 is_testnet: false,
206 http_proxy_url: None,
207 ws_proxy_url: None,
208 orderbook_refresh_interval_secs: Some(60),
209 instrument_refresh_interval_secs: Some(3600),
210 }
211 }
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct DYDXExecClientConfig {
217 pub grpc_endpoint: String,
219 pub ws_endpoint: String,
221 pub mnemonic: Option<String>,
223 pub wallet_address: Option<String>,
225 pub subaccount_number: u32,
227 pub http_timeout_secs: Option<u64>,
229 pub max_retries: Option<u64>,
231 pub retry_delay_initial_ms: Option<u64>,
233 pub retry_delay_max_ms: Option<u64>,
235 pub is_testnet: bool,
237}
238
239impl Default for DYDXExecClientConfig {
240 fn default() -> Self {
241 Self {
242 grpc_endpoint: DYDX_GRPC_URLS[0].to_string(),
243 ws_endpoint: DYDX_WS_URL.to_string(),
244 mnemonic: None,
245 wallet_address: None,
246 subaccount_number: 0,
247 http_timeout_secs: Some(60),
248 max_retries: Some(3),
249 retry_delay_initial_ms: Some(100),
250 retry_delay_max_ms: Some(5000),
251 is_testnet: false,
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use rstest::rstest;
259
260 use super::*;
261
262 #[rstest]
263 fn test_config_get_chain_id_mainnet() {
264 let config = DydxAdapterConfig {
265 network: DydxNetwork::Mainnet,
266 ..Default::default()
267 };
268 assert_eq!(config.get_chain_id(), ChainId::Mainnet1);
269 }
270
271 #[rstest]
272 fn test_config_get_chain_id_testnet() {
273 let config = DydxAdapterConfig {
274 network: DydxNetwork::Testnet,
275 ..Default::default()
276 };
277 assert_eq!(config.get_chain_id(), ChainId::Testnet4);
278 }
279
280 #[rstest]
281 fn test_config_compute_is_testnet() {
282 let mainnet_config = DydxAdapterConfig {
283 network: DydxNetwork::Mainnet,
284 ..Default::default()
285 };
286 assert!(!mainnet_config.compute_is_testnet());
287
288 let testnet_config = DydxAdapterConfig {
289 network: DydxNetwork::Testnet,
290 ..Default::default()
291 };
292 assert!(testnet_config.compute_is_testnet());
293 }
294
295 #[rstest]
296 fn test_config_default_uses_mainnet() {
297 let config = DydxAdapterConfig::default();
298 assert_eq!(config.network, DydxNetwork::Mainnet);
299 assert!(!config.is_testnet);
300 }
301
302 #[rstest]
303 fn test_config_network_canonical_over_is_testnet() {
304 let config = DydxAdapterConfig {
306 network: DydxNetwork::Mainnet,
307 is_testnet: true, ..Default::default()
309 };
310 assert_eq!(config.get_chain_id(), ChainId::Mainnet1); assert!(!config.compute_is_testnet()); }
313
314 #[rstest]
315 fn test_config_serde_backwards_compat() {
316 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}"#;
318
319 let config: Result<DydxAdapterConfig, _> = serde_json::from_str(json);
320 assert!(config.is_ok());
321 let config = config.unwrap();
322 assert_eq!(config.network, DydxNetwork::Mainnet);
324 }
325
326 #[rstest]
327 fn test_config_get_grpc_urls_fallback() {
328 let config = DydxAdapterConfig {
329 grpc_url: "https://primary.example.com".to_string(),
330 grpc_urls: vec![],
331 ..Default::default()
332 };
333
334 let urls = config.get_grpc_urls();
335 assert_eq!(urls.len(), 1);
336 assert_eq!(urls[0], "https://primary.example.com");
337 }
338
339 #[rstest]
340 fn test_config_get_grpc_urls_multiple() {
341 let config = DydxAdapterConfig {
342 grpc_url: "https://primary.example.com".to_string(),
343 grpc_urls: vec![
344 "https://fallback1.example.com".to_string(),
345 "https://fallback2.example.com".to_string(),
346 ],
347 ..Default::default()
348 };
349
350 let urls = config.get_grpc_urls();
351 assert_eq!(urls.len(), 2);
352 assert_eq!(urls[0], "https://fallback1.example.com");
353 assert_eq!(urls[1], "https://fallback2.example.com");
354 }
355}