crypto_ws_client/clients/dydx/
dydx_swap.rs1use async_trait::async_trait;
2use std::collections::HashMap;
3use tokio_tungstenite::tungstenite::Message;
4
5use crate::{
6 clients::common_traits::{
7 Candlestick, Level3OrderBook, OrderBook, OrderBookTopK, Ticker, Trade, BBO,
8 },
9 common::{
10 command_translator::CommandTranslator,
11 message_handler::{MessageHandler, MiscMessage},
12 ws_client_internal::WSClientInternal,
13 },
14 WSClient,
15};
16
17use super::EXCHANGE_NAME;
18use log::*;
19use serde_json::Value;
20
21const WEBSOCKET_URL: &str = "wss://api.dydx.exchange/v3/ws";
22
23pub struct DydxSwapWSClient {
28 client: WSClientInternal<DydxMessageHandler>,
29 translator: DydxCommandTranslator,
30}
31
32impl_new_constructor!(
33 DydxSwapWSClient,
34 EXCHANGE_NAME,
35 WEBSOCKET_URL,
36 DydxMessageHandler {},
37 DydxCommandTranslator {}
38);
39
40impl_trait!(Trade, DydxSwapWSClient, subscribe_trade, "v3_trades");
41#[rustfmt::skip]
42impl_trait!(OrderBook, DydxSwapWSClient, subscribe_orderbook, "v3_orderbook");
43
44panic_ticker!(DydxSwapWSClient);
45panic_bbo!(DydxSwapWSClient);
46panic_l2_topk!(DydxSwapWSClient);
47panic_l3_orderbook!(DydxSwapWSClient);
48panic_candlestick!(DydxSwapWSClient);
49
50impl_ws_client_trait!(DydxSwapWSClient);
51
52struct DydxMessageHandler {}
53struct DydxCommandTranslator {}
54
55impl MessageHandler for DydxMessageHandler {
56 fn handle_message(&mut self, msg: &str) -> MiscMessage {
57 let obj = serde_json::from_str::<HashMap<String, Value>>(msg).unwrap();
58
59 match obj.get("type").unwrap().as_str().unwrap() {
60 "error" => {
61 error!("Received {} from {}", msg, EXCHANGE_NAME);
62 if obj.contains_key("message")
63 && obj
64 .get("message")
65 .unwrap()
66 .as_str()
67 .unwrap()
68 .starts_with("Invalid subscription id for channel")
69 {
70 panic!("Received {msg} from {EXCHANGE_NAME}");
71 } else {
72 MiscMessage::Other
73 }
74 }
75 "connected" | "pong" => {
76 debug!("Received {} from {}", msg, EXCHANGE_NAME);
77 MiscMessage::Other
78 }
79 "channel_data" | "subscribed" => MiscMessage::Normal,
80 _ => {
81 warn!("Received {} from {}", msg, EXCHANGE_NAME);
82 MiscMessage::Other
83 }
84 }
85 }
86
87 fn get_ping_msg_and_interval(&self) -> Option<(Message, u64)> {
88 Some((Message::Text(r#"{"type":"ping"}"#.to_string()), 30))
92 }
93}
94
95impl DydxCommandTranslator {
96 fn topic_to_command(topic: &(String, String), subscribe: bool) -> String {
97 format!(
98 r#"{{"type": "{}", "channel": "{}", "id": "{}"}}"#,
99 if subscribe { "subscribe" } else { "unsubscribe" },
100 topic.0,
101 topic.1,
102 )
103 }
104}
105
106impl CommandTranslator for DydxCommandTranslator {
107 fn translate_to_commands(&self, subscribe: bool, topics: &[(String, String)]) -> Vec<String> {
108 topics.iter().map(|t| Self::topic_to_command(t, subscribe)).collect()
109 }
110
111 fn translate_to_candlestick_commands(
112 &self,
113 _subscribe: bool,
114 _symbol_interval_list: &[(String, usize)],
115 ) -> Vec<String> {
116 panic!("dYdX does NOT have candlestick channel");
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use crate::common::command_translator::CommandTranslator;
123
124 #[test]
125 fn test_one_topic() {
126 let translator = super::DydxCommandTranslator {};
127 let commands = translator
128 .translate_to_commands(true, &[("v3_trades".to_string(), "BTC-USD".to_string())]);
129
130 assert_eq!(1, commands.len());
131 assert_eq!(
132 r#"{"type": "subscribe", "channel": "v3_trades", "id": "BTC-USD"}"#,
133 commands[0]
134 );
135 }
136
137 #[test]
138 fn test_two_topic() {
139 let translator = super::DydxCommandTranslator {};
140 let commands = translator.translate_to_commands(
141 true,
142 &[
143 ("v3_trades".to_string(), "BTC-USD".to_string()),
144 ("v3_orderbook".to_string(), "BTC-USD".to_string()),
145 ],
146 );
147
148 assert_eq!(2, commands.len());
149 assert_eq!(
150 r#"{"type": "subscribe", "channel": "v3_trades", "id": "BTC-USD"}"#,
151 commands[0]
152 );
153 assert_eq!(
154 r#"{"type": "subscribe", "channel": "v3_orderbook", "id": "BTC-USD"}"#,
155 commands[1]
156 );
157 }
158}