Skip to main content

bullet_ws_interface/
server.rs

1use serde::ser::SerializeTuple;
2use serde::{Deserialize, Serialize};
3
4use crate::{RequestId, WSError};
5
6/// client order id (u64 wrapper for type safety)
7pub type ClientOrderId = u64;
8
9#[derive(Serialize, Deserialize, Clone, Debug)]
10pub enum MessageType {
11    #[serde(rename = "s")]
12    Snapshot,
13    #[serde(rename = "u")]
14    Update,
15}
16
17#[derive(Serialize, Deserialize, Clone, Debug)]
18#[serde(rename_all = "camelCase")]
19pub struct DataMessage {
20    pub channel: String,
21    pub symbol: String,
22    pub ts: u64,
23    #[serde(rename = "mt")]
24    pub msg_type: MessageType,
25    pub data: serde_json::Value,
26}
27
28/// Status message for connection lifecycle events
29#[derive(Serialize, Deserialize, Clone, Debug)]
30#[serde(rename_all = "camelCase")]
31pub struct StatusMessage {
32    /// Event time (ms)
33    #[serde(rename = "E")]
34    pub event_time: u64,
35    pub status: String,
36    pub client_id: String,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub reason: Option<String>,
39}
40
41#[derive(Serialize, Deserialize, Clone, Debug)]
42#[serde(rename_all = "camelCase")]
43pub struct PongMessage {
44    pub id: Option<RequestId>,
45    /// Event time (ms)
46    #[serde(rename = "E")]
47    pub event_time: u64,
48}
49
50/// Error message from the server
51#[derive(Serialize, Deserialize, Clone, Debug)]
52#[serde(rename_all = "camelCase")]
53pub struct ErrorMessage {
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub id: Option<RequestId>,
56    /// Event time (ms)
57    #[serde(rename = "E")]
58    pub event_time: u64,
59    pub error: WSError,
60}
61
62/// Price level as [price, quantity]
63#[derive(Clone, Debug)]
64pub struct PriceLevel(pub String, pub String);
65
66impl Serialize for PriceLevel {
67    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
68        let mut tuple = serializer.serialize_tuple(2)?;
69        tuple.serialize_element(&self.0)?;
70        tuple.serialize_element(&self.1)?;
71        tuple.end()
72    }
73}
74
75impl<'de> Deserialize<'de> for PriceLevel {
76    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
77        let (price, qty) = <(String, String)>::deserialize(deserializer)?;
78        Ok(PriceLevel(price, qty))
79    }
80}
81
82/// Binance-compatible depth update message
83#[derive(Serialize, Deserialize, Clone, Debug)]
84pub struct DepthUpdate {
85    #[serde(rename = "e")]
86    pub event_type: String,
87    #[serde(rename = "E")]
88    pub event_time: u64,
89    #[serde(rename = "T")]
90    pub transaction_time: u64,
91    #[serde(rename = "s")]
92    pub symbol: String,
93    #[serde(rename = "U")]
94    pub first_update_id: u64,
95    #[serde(rename = "u")]
96    pub last_update_id: u64,
97    #[serde(rename = "pu")]
98    pub prev_update_id: u64,
99    #[serde(rename = "b")]
100    pub bids: Vec<PriceLevel>,
101    #[serde(rename = "a")]
102    pub asks: Vec<PriceLevel>,
103    #[serde(rename = "mt")]
104    pub msg_type: MessageType,
105}
106
107/// AggTrade message with DEX-specific fields
108#[derive(Serialize, Deserialize, Clone, Debug)]
109pub struct AggTradeMessage {
110    #[serde(rename = "e")]
111    pub event_type: String,
112    #[serde(rename = "E")]
113    pub event_time: u64,
114    #[serde(rename = "s")]
115    pub symbol: String,
116    #[serde(rename = "a")]
117    pub agg_trade_id: u64,
118    #[serde(rename = "p")]
119    pub price: String,
120    #[serde(rename = "q")]
121    pub quantity: String,
122    #[serde(rename = "f")]
123    pub first_trade_id: u64,
124    #[serde(rename = "l")]
125    pub last_trade_id: u64,
126    #[serde(rename = "T")]
127    pub trade_time: u64,
128    #[serde(rename = "m")]
129    pub is_buyer_maker: bool,
130    // DEX-specific fields
131    #[serde(rename = "th")]
132    pub tx_hash: String,
133    #[serde(rename = "ua")]
134    pub user_address: String,
135    #[serde(rename = "oi")]
136    pub order_id: u64,
137    #[serde(rename = "mk")]
138    pub is_maker: bool,
139    #[serde(rename = "ff")]
140    pub is_full_fill: bool,
141    #[serde(rename = "lq")]
142    pub is_liquidation: bool,
143    #[serde(rename = "fe")]
144    pub fee: String,
145    #[serde(rename = "nf")]
146    pub net_fee: String,
147    #[serde(rename = "fa")]
148    pub fee_asset: String,
149    #[serde(rename = "co", skip_serializing_if = "Option::is_none")]
150    pub client_order_id: Option<ClientOrderId>,
151    #[serde(rename = "sd")]
152    pub side: String,
153}
154
155/// Binance-compatible bookTicker (BBO) message
156#[derive(Serialize, Deserialize, Clone, Debug)]
157pub struct BookTickerMessage {
158    #[serde(rename = "e")]
159    pub event_type: String,
160    #[serde(rename = "u")]
161    pub update_id: u64,
162    #[serde(rename = "E")]
163    pub event_time: u64,
164    #[serde(rename = "T")]
165    pub transaction_time: u64,
166    #[serde(rename = "s")]
167    pub symbol: String,
168    #[serde(rename = "b")]
169    pub best_bid_price: String,
170    #[serde(rename = "B")]
171    pub best_bid_qty: String,
172    #[serde(rename = "a")]
173    pub best_ask_price: String,
174    #[serde(rename = "A")]
175    pub best_ask_qty: String,
176    #[serde(rename = "mt")]
177    pub msg_type: MessageType,
178}
179
180/// Binance-compatible forceOrder message for liquidation trades
181#[derive(Serialize, Deserialize, Clone, Debug)]
182pub struct ForceOrderMessage {
183    #[serde(rename = "e")]
184    pub event_type: String,
185    #[serde(rename = "E")]
186    pub event_time: u64,
187    #[serde(rename = "o")]
188    pub order: ForceOrderDetails,
189}
190
191/// Order details within a forceOrder message
192#[derive(Serialize, Deserialize, Clone, Debug)]
193pub struct ForceOrderDetails {
194    #[serde(rename = "s")]
195    pub symbol: String,
196    #[serde(rename = "S")]
197    pub side: String,
198    #[serde(rename = "o")]
199    pub order_type: String,
200    #[serde(rename = "f")]
201    pub time_in_force: String,
202    #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
203    pub quantity: Option<String>,
204    #[serde(rename = "z", skip_serializing_if = "Option::is_none")]
205    pub filled_qty: Option<String>,
206    #[serde(rename = "p")]
207    pub price: String,
208    #[serde(rename = "ap")]
209    pub avg_price: String,
210    #[serde(rename = "X")]
211    pub status: String,
212    #[serde(rename = "l")]
213    pub last_filled_qty: String,
214    #[serde(rename = "T")]
215    pub trade_time: u64,
216    // DEX-specific fields
217    #[serde(rename = "th")]
218    pub tx_hash: String,
219    #[serde(rename = "ua")]
220    pub user_address: String,
221    #[serde(rename = "oi")]
222    pub order_id: u64,
223    #[serde(rename = "ti")]
224    pub trade_id: u64,
225}
226
227/// Binance-compatible markPrice message
228#[derive(Serialize, Deserialize, Clone, Debug)]
229pub struct MarkPriceMessage {
230    #[serde(rename = "e")]
231    pub event_type: String,
232    #[serde(rename = "E")]
233    pub event_time: u64,
234    #[serde(rename = "s")]
235    pub symbol: String,
236    #[serde(rename = "p")]
237    pub mark_price: String,
238    #[serde(rename = "i")]
239    pub index_price: String,
240    #[serde(rename = "P", skip_serializing_if = "Option::is_none")]
241    pub estimated_settle_price: Option<String>,
242    #[serde(rename = "r")]
243    pub funding_rate: String,
244    #[serde(rename = "T", skip_serializing_if = "Option::is_none")]
245    pub next_funding_time: Option<u64>,
246    #[serde(rename = "th", skip_serializing_if = "Option::is_none")]
247    pub tx_hash: Option<String>,
248}
249
250/// User order update message (Binance ORDER_TRADE_UPDATE style)
251#[derive(Serialize, Deserialize, Clone, Debug)]
252pub struct OrderUpdateMessage {
253    #[serde(rename = "e")]
254    pub event_type: String,
255    #[serde(rename = "E")]
256    pub event_time: u64,
257    #[serde(rename = "o")]
258    pub order: OrderUpdateData,
259}
260
261/// Common fields for all order update events
262#[derive(Serialize, Deserialize, Clone, Debug)]
263pub struct OrderUpdateCommon {
264    #[serde(rename = "s")]
265    pub symbol: String,
266    #[serde(rename = "i")]
267    pub order_id: u64,
268    #[serde(rename = "co", skip_serializing_if = "Option::is_none")]
269    pub client_order_id: Option<ClientOrderId>,
270    #[serde(rename = "X")]
271    pub status: String,
272    #[serde(rename = "x")]
273    pub execution_type: String,
274    #[serde(rename = "T")]
275    pub transaction_time: u64,
276    #[serde(rename = "th")]
277    pub tx_hash: String,
278    #[serde(rename = "ua")]
279    pub user_address: String,
280}
281
282/// Order data for NEW order placement
283#[derive(Serialize, Deserialize, Clone, Debug)]
284pub struct PlaceOrderData {
285    #[serde(flatten)]
286    pub common: OrderUpdateCommon,
287    #[serde(rename = "S")]
288    pub side: String,
289    #[serde(rename = "o")]
290    pub order_type: String,
291    #[serde(rename = "f")]
292    pub time_in_force: String,
293    #[serde(rename = "p")]
294    pub price: String,
295    #[serde(rename = "q")]
296    pub quantity: String,
297}
298
299/// Order data for CANCELED orders
300#[derive(Serialize, Deserialize, Clone, Debug)]
301pub struct CancelOrderData {
302    #[serde(flatten)]
303    pub common: OrderUpdateCommon,
304}
305
306/// Order data for TRADE fills
307#[derive(Serialize, Deserialize, Clone, Debug)]
308pub struct TradeFillData {
309    #[serde(flatten)]
310    pub common: OrderUpdateCommon,
311    #[serde(rename = "S")]
312    pub side: String,
313    #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
314    pub price: Option<String>,
315    #[serde(rename = "q", skip_serializing_if = "Option::is_none")]
316    pub quantity: Option<String>,
317    #[serde(rename = "l")]
318    pub last_filled_qty: String,
319    #[serde(rename = "L")]
320    pub last_filled_price: String,
321    #[serde(rename = "n")]
322    pub commission: String,
323}
324
325/// Untagged enum - serializes directly as the variant's fields
326#[derive(Serialize, Deserialize, Clone, Debug)]
327#[serde(untagged)]
328pub enum OrderUpdateData {
329    TradeFill(TradeFillData),
330    PlaceOrder(PlaceOrderData),
331    Cancel(CancelOrderData),
332}