1use crate::indexer::types::{
2 CandleResponse as CandlesInitialMessageContents, CandleResponseObject as Candle,
3 HeightResponse as BlockHeightInitialMessageContents,
4 OrderBookResponseObject as OrdersInitialMessageContents,
5 OrderResponseObject as OrderMessageObject,
6 ParentSubaccountResponseObject as ParentSubaccountMessageObject,
7 PerpetualMarketResponse as MarketsInitialMessageContents,
8 SubaccountResponseObject as SubaccountMessageObject,
9 TradeResponse as TradesInitialMessageContents, *,
10};
11use bigdecimal::BigDecimal;
12use chrono::{DateTime, Utc};
13use serde::Deserialize;
14use serde_json::{json, Value};
15use std::collections::HashMap;
16use tokio_tungstenite::tungstenite::protocol::Message;
17
18#[derive(Debug, Clone, Hash, Eq, PartialEq)]
21pub enum Subscription {
22 Subaccounts(Subaccount),
24 Orders(Ticker),
26 Trades(Ticker),
28 Markets,
30 Candles(Ticker, CandleResolution),
32 ParentSubaccounts(ParentSubaccount),
34 BlockHeight,
36}
37
38impl Subscription {
39 pub(crate) fn sub_message(&self, batched: bool) -> Message {
40 match self {
41 Self::Subaccounts(ref subacc) => subaccounts::sub_message(subacc, batched),
42 Self::Markets => markets::sub_message(batched),
43 Self::Orders(ref ticker) => orders::sub_message(ticker, batched),
44 Self::Trades(ref ticker) => trades::sub_message(ticker, batched),
45 Self::Candles(ref ticker, ref res) => candles::sub_message(ticker, res, batched),
46 Self::ParentSubaccounts(ref subacc) => parent_subaccounts::sub_message(subacc, batched),
47 Self::BlockHeight => block_height::sub_message(batched),
48 }
49 }
50
51 pub(crate) fn unsub_message(&self) -> Message {
52 match self {
53 Self::Subaccounts(ref subacc) => subaccounts::unsub_message(subacc),
54 Self::Markets => markets::unsub_message(),
55 Self::Orders(ref ticker) => orders::unsub_message(ticker),
56 Self::Trades(ref ticker) => trades::unsub_message(ticker),
57 Self::Candles(ref ticker, ref res) => candles::unsub_message(ticker, res),
58 Self::ParentSubaccounts(ref subacc) => parent_subaccounts::unsub_message(subacc),
59 Self::BlockHeight => block_height::unsub_message(),
60 }
61 }
62}
63
64struct MessageFormatter {}
65
66impl MessageFormatter {
67 pub(crate) fn sub_message(channel: &str, fields: Value) -> Message {
68 let message = json!({
69 "type": "subscribe",
70 "channel": channel,
71 });
72 Self::message(fields, message)
73 }
74
75 pub(crate) fn unsub_message(channel: &str, fields: Value) -> Message {
76 let message = json!({
77 "type": "unsubscribe",
78 "channel": channel,
79 });
80 Self::message(fields, message)
81 }
82
83 fn message(mut message: Value, fields: Value) -> Message {
84 if let Value::Object(ref mut map) = message {
85 if let Value::Object(fields) = fields {
86 map.extend(fields);
87 }
88 }
89 Message::Text(message.to_string().into())
90 }
91}
92
93pub(crate) mod subaccounts {
94 use super::{json, Message, MessageFormatter, Subaccount};
95 pub(crate) const CHANNEL: &str = "v4_subaccounts";
96
97 pub(crate) fn sub_message(subacc: &Subaccount, batched: bool) -> Message {
98 let address = &subacc.address;
99 let number = &subacc.number;
100 MessageFormatter::sub_message(
101 CHANNEL,
102 json!({"id": format!("{address}/{number}"), "batched": batched}),
103 )
104 }
105
106 pub(crate) fn unsub_message(subacc: &Subaccount) -> Message {
107 let address = &subacc.address;
108 let number = &subacc.number;
109 MessageFormatter::unsub_message(CHANNEL, json!({"id": format!("{address}/{number}")}))
110 }
111}
112
113pub(crate) mod parent_subaccounts {
114 use super::{json, Message, MessageFormatter, ParentSubaccount};
115 pub(crate) const CHANNEL: &str = "v4_parent_subaccounts";
116
117 pub(crate) fn sub_message(subacc: &ParentSubaccount, batched: bool) -> Message {
118 let address = &subacc.address;
119 let number = &subacc.number;
120 MessageFormatter::sub_message(
121 CHANNEL,
122 json!({"id": format!("{address}/{number}"), "batched": batched}),
123 )
124 }
125
126 pub(crate) fn unsub_message(subacc: &ParentSubaccount) -> Message {
127 let address = &subacc.address;
128 let number = &subacc.number;
129 MessageFormatter::unsub_message(CHANNEL, json!({"id": format!("{address}/{number}")}))
130 }
131}
132
133pub(crate) mod orders {
134 use super::{json, Message, MessageFormatter, Ticker};
135 pub(crate) const CHANNEL: &str = "v4_orderbook";
136
137 pub(crate) fn sub_message(id: &Ticker, batched: bool) -> Message {
138 MessageFormatter::sub_message(CHANNEL, json!({"id": id, "batched": batched}))
139 }
140
141 pub(crate) fn unsub_message(id: &Ticker) -> Message {
142 MessageFormatter::unsub_message(CHANNEL, json!({"id": id}))
143 }
144}
145
146pub(crate) mod trades {
147 use super::{json, Message, MessageFormatter, Ticker};
148 pub(crate) const CHANNEL: &str = "v4_trades";
149
150 pub(crate) fn sub_message(id: &Ticker, batched: bool) -> Message {
151 MessageFormatter::sub_message(CHANNEL, json!({"id": id, "batched": batched}))
152 }
153
154 pub(crate) fn unsub_message(id: &Ticker) -> Message {
155 MessageFormatter::unsub_message(CHANNEL, json!({"id": id}))
156 }
157}
158
159pub(crate) mod markets {
160 use super::{json, Message, MessageFormatter};
161 pub const CHANNEL: &str = "v4_markets";
162
163 pub(crate) fn sub_message(batched: bool) -> Message {
164 MessageFormatter::sub_message(CHANNEL, json!({"batched": batched}))
165 }
166
167 pub(crate) fn unsub_message() -> Message {
168 MessageFormatter::unsub_message(CHANNEL, json!({}))
169 }
170}
171
172pub(crate) mod candles {
173 use super::{json, CandleResolution, Message, MessageFormatter, Ticker};
174 pub(crate) const CHANNEL: &str = "v4_candles";
175
176 pub(crate) fn sub_message(
177 id: &Ticker,
178 resolution: &CandleResolution,
179 batched: bool,
180 ) -> Message {
181 let resolution_str = serde_json::to_string(resolution).unwrap_or_default();
182 let resolution_str = resolution_str.trim_matches('"');
183 MessageFormatter::sub_message(
184 CHANNEL,
185 json!({"id": format!("{id}/{resolution_str}"), "batched": batched}),
186 )
187 }
188
189 pub(crate) fn unsub_message(id: &Ticker, resolution: &CandleResolution) -> Message {
190 let resolution_str = serde_json::to_string(resolution).unwrap_or_default();
191 let resolution_str = resolution_str.trim_matches('"');
192 MessageFormatter::unsub_message(CHANNEL, json!({"id": format!("{id}/{resolution_str}")}))
193 }
194}
195
196pub(crate) mod block_height {
197 use super::{json, Message, MessageFormatter};
198 pub const CHANNEL: &str = "v4_block_height";
199
200 pub(crate) fn sub_message(batched: bool) -> Message {
201 MessageFormatter::sub_message(CHANNEL, json!({"batched": batched}))
202 }
203
204 pub(crate) fn unsub_message() -> Message {
205 MessageFormatter::unsub_message(CHANNEL, json!({}))
206 }
207}
208
209#[allow(clippy::large_enum_variant)]
211#[derive(Debug, Deserialize)]
212#[serde(tag = "type")]
213pub(crate) enum WsMessage {
214 #[serde(rename = "connected")]
215 Setup(StatusConnectedMessage),
216 #[serde(rename = "error")]
217 Error(StatusErrorMessage),
218 #[serde(rename = "unsubscribed")]
219 Unsub(StatusUnsubMessage),
220 #[serde(untagged)]
221 Data(FeedMessage),
222}
223
224#[derive(Debug, Deserialize)]
225pub(crate) struct StatusConnectedMessage {
226 pub(crate) connection_id: String,
227 #[allow(dead_code)] pub(crate) message_id: u64,
229}
230
231#[derive(Debug, Deserialize)]
232pub(crate) struct StatusErrorMessage {
233 pub(crate) message: String,
234 #[allow(dead_code)] pub(crate) connection_id: String,
236 #[allow(dead_code)] pub(crate) message_id: u64,
238}
239
240#[derive(Debug, Deserialize)]
241pub(crate) struct StatusUnsubMessage {
242 #[allow(dead_code)] pub(crate) connection_id: String,
244 #[allow(dead_code)] pub(crate) message_id: u64,
246 pub(crate) channel: String,
247 pub(crate) id: Option<String>,
248}
249
250#[derive(Debug, Deserialize)]
252#[serde(tag = "channel")]
253pub enum FeedMessage {
254 #[serde(rename = "v4_subaccounts")]
256 Subaccounts(SubaccountsMessage),
257 #[serde(rename = "v4_orderbook")]
259 Orders(OrdersMessage),
260 #[serde(rename = "v4_trades")]
262 Trades(TradesMessage),
263 #[serde(rename = "v4_markets")]
265 Markets(MarketsMessage),
266 #[serde(rename = "v4_candles")]
268 Candles(CandlesMessage),
269 #[serde(rename = "v4_parent_subaccounts")]
271 ParentSubaccounts(ParentSubaccountsMessage),
272 #[serde(rename = "v4_block_height")]
274 BlockHeight(BlockHeightMessage),
275}
276
277macro_rules! impl_feed_message_try_from {
278 ($target_type:ty, $variant:ident) => {
279 impl TryFrom<FeedMessage> for $target_type {
280 type Error = ();
281 fn try_from(value: FeedMessage) -> Result<Self, Self::Error> {
282 match value {
283 FeedMessage::$variant(a) => Ok(a),
284 _ => Err(()),
285 }
286 }
287 }
288 };
289}
290
291#[derive(Debug, Deserialize)]
293#[serde(tag = "type")]
294pub enum SubaccountsMessage {
295 #[serde(rename = "subscribed")]
297 Initial(SubaccountsInitialMessage),
298 #[serde(untagged)]
300 Update(SubaccountsUpdateMessage),
301}
302impl_feed_message_try_from!(SubaccountsMessage, Subaccounts);
303
304#[derive(Debug, Deserialize)]
306#[serde(tag = "type")]
307pub enum ParentSubaccountsMessage {
308 #[serde(rename = "subscribed")]
310 Initial(ParentSubaccountsInitialMessage),
311 #[serde(untagged)]
313 Update(ParentSubaccountsUpdateMessage),
314}
315impl_feed_message_try_from!(ParentSubaccountsMessage, ParentSubaccounts);
316
317#[derive(Debug, Deserialize)]
319#[serde(tag = "type")]
320pub enum TradesMessage {
321 #[serde(rename = "subscribed")]
323 Initial(TradesInitialMessage),
324 #[serde(untagged)]
326 Update(TradesUpdateMessage),
327}
328impl_feed_message_try_from!(TradesMessage, Trades);
329
330#[derive(Debug, Deserialize)]
332#[serde(tag = "type")]
333pub enum OrdersMessage {
334 #[serde(rename = "subscribed")]
336 Initial(OrdersInitialMessage),
337 #[serde(untagged)]
339 Update(OrdersUpdateMessage),
340}
341impl_feed_message_try_from!(OrdersMessage, Orders);
342
343#[derive(Debug, Deserialize)]
345#[serde(tag = "type")]
346pub enum MarketsMessage {
347 #[serde(rename = "subscribed")]
349 Initial(MarketsInitialMessage),
350 #[serde(untagged)]
352 Update(MarketsUpdateMessage),
353}
354impl_feed_message_try_from!(MarketsMessage, Markets);
355
356#[derive(Debug, Deserialize)]
358#[serde(tag = "type")]
359pub enum CandlesMessage {
360 #[serde(rename = "subscribed")]
362 Initial(CandlesInitialMessage),
363 #[serde(untagged)]
365 Update(CandlesUpdateMessage),
366}
367impl_feed_message_try_from!(CandlesMessage, Candles);
368
369#[derive(Debug, Deserialize)]
371#[serde(tag = "type")]
372pub enum BlockHeightMessage {
373 #[serde(rename = "subscribed")]
375 Initial(BlockHeightInitialMessage),
376 #[serde(untagged)]
378 Update(BlockHeightUpdateMessage),
379}
380impl_feed_message_try_from!(BlockHeightMessage, BlockHeight);
381
382impl FeedMessage {
383 pub(crate) fn subscription(&self) -> Option<Subscription> {
384 let parse_subacc_id = |id: &str| -> Option<Subaccount> {
385 let mut id_split = id.split('/');
387 let address = id_split.next()?.parse().ok()?;
388 let number_str = id_split.next()?;
389 let number = serde_json::from_str::<SubaccountNumber>(number_str).ok()?;
390 Some(Subaccount::new(address, number))
391 };
392 let parse_psubacc_id = |id: &str| -> Option<ParentSubaccount> {
393 let mut id_split = id.split('/');
395 let address = id_split.next()?.parse().ok()?;
396 let number_str = id_split.next()?;
397 let number = serde_json::from_str::<ParentSubaccountNumber>(number_str).ok()?;
398 Some(ParentSubaccount::new(address, number))
399 };
400 let parse_candles_id = |id: &str| -> Option<(Ticker, CandleResolution)> {
401 let mut id_split = id.split('/');
403 let ticker = Ticker(id_split.next()?.into());
404 let resolution_str = format!("\"{}\"", id_split.next()?);
405 let resolution = serde_json::from_str(&resolution_str).ok()?;
406 Some((ticker, resolution))
407 };
408
409 match self {
410 Self::Subaccounts(SubaccountsMessage::Initial(msg)) => {
411 let subacc = parse_subacc_id(&msg.id)?;
412 Some(Subscription::Subaccounts(subacc))
413 }
414 Self::Subaccounts(SubaccountsMessage::Update(msg)) => {
415 let subacc = parse_subacc_id(&msg.id)?;
416 Some(Subscription::Subaccounts(subacc))
417 }
418
419 Self::ParentSubaccounts(ParentSubaccountsMessage::Initial(msg)) => {
420 let subacc = parse_psubacc_id(&msg.id)?;
421 Some(Subscription::ParentSubaccounts(subacc))
422 }
423 Self::ParentSubaccounts(ParentSubaccountsMessage::Update(msg)) => {
424 let subacc = parse_psubacc_id(&msg.id)?;
425 Some(Subscription::ParentSubaccounts(subacc))
426 }
427
428 Self::Orders(OrdersMessage::Initial(msg)) => {
429 Some(Subscription::Orders(Ticker(msg.id.clone())))
430 }
431 Self::Orders(OrdersMessage::Update(msg)) => {
432 Some(Subscription::Orders(Ticker(msg.id.clone())))
433 }
434
435 Self::Trades(TradesMessage::Initial(msg)) => {
436 Some(Subscription::Trades(Ticker(msg.id.clone())))
437 }
438 Self::Trades(TradesMessage::Update(msg)) => {
439 Some(Subscription::Trades(Ticker(msg.id.clone())))
440 }
441
442 Self::Markets(MarketsMessage::Update(_)) => Some(Subscription::Markets),
443 Self::Markets(MarketsMessage::Initial(_)) => Some(Subscription::Markets),
444
445 Self::Candles(CandlesMessage::Initial(msg)) => {
446 let (ticker, resolution) = parse_candles_id(&msg.id)?;
447 Some(Subscription::Candles(ticker, resolution))
448 }
449 Self::Candles(CandlesMessage::Update(msg)) => {
450 let (ticker, resolution) = parse_candles_id(&msg.id)?;
451 Some(Subscription::Candles(ticker, resolution))
452 }
453
454 Self::BlockHeight(BlockHeightMessage::Initial(_)) => Some(Subscription::BlockHeight),
455 Self::BlockHeight(BlockHeightMessage::Update(_)) => Some(Subscription::BlockHeight),
456 }
457 }
458}
459
460#[derive(Debug, Deserialize)]
462pub struct SubaccountsInitialMessage {
463 pub connection_id: String,
465 pub contents: SubaccountsInitialMessageContents,
467 pub id: String,
469 pub message_id: u64,
471}
472
473#[derive(Debug, Deserialize)]
475#[serde(rename_all = "camelCase")]
476#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
477pub struct SubaccountsInitialMessageContents {
478 pub subaccount: SubaccountMessageObject,
480 pub orders: Vec<OrderMessageObject>,
482 pub block_height: Height,
484}
485
486#[derive(Debug, Deserialize)]
488pub struct ParentSubaccountsInitialMessage {
489 pub connection_id: String,
491 pub contents: ParentSubaccountsInitialMessageContents,
493 pub id: String,
495 pub message_id: u64,
497}
498
499#[derive(Debug, Deserialize)]
501#[serde(rename_all = "camelCase")]
502#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
503pub struct ParentSubaccountsInitialMessageContents {
504 pub subaccount: ParentSubaccountMessageObject,
506 pub orders: Vec<OrderMessageObject>,
508 pub block_height: Height,
510}
511
512#[derive(Debug, Deserialize)]
514pub struct OrdersInitialMessage {
515 pub connection_id: String,
517 pub contents: OrdersInitialMessageContents,
519 pub id: String,
521 pub message_id: u64,
523}
524
525#[derive(Debug, Deserialize)]
527pub struct TradesInitialMessage {
528 pub connection_id: String,
530 pub contents: TradesInitialMessageContents,
532 pub id: String,
534 pub message_id: u64,
536}
537
538#[derive(Debug, Deserialize)]
540pub struct MarketsInitialMessage {
541 pub connection_id: String,
543 pub contents: MarketsInitialMessageContents,
545 pub message_id: u64,
547}
548
549#[derive(Debug, Deserialize)]
551pub struct CandlesInitialMessage {
552 pub connection_id: String,
554 pub contents: CandlesInitialMessageContents,
556 pub id: String,
558 pub message_id: u64,
560}
561
562#[derive(Debug, Deserialize)]
564pub struct BlockHeightInitialMessage {
565 pub connection_id: String,
567 pub contents: BlockHeightInitialMessageContents,
569 pub message_id: u64,
571}
572
573macro_rules! generate_contents_deserialize_function {
575 ($fn_name:ident, $result_type:ty) => {
576 fn $fn_name<'de, D>(deserializer: D) -> Result<Vec<$result_type>, D::Error>
577 where
578 D: serde::Deserializer<'de>,
579 {
580 let value = Value::deserialize(deserializer)?;
581
582 match value {
583 Value::Array(arr) => arr
585 .into_iter()
586 .map(|v| serde_json::from_value(v))
587 .collect::<Result<Vec<$result_type>, _>>()
588 .map_err(serde::de::Error::custom),
589 Value::Object(obj) => {
591 let item = serde_json::from_value::<$result_type>(Value::Object(obj.clone()))
592 .map_err(serde::de::Error::custom)?;
593 Ok(vec![item])
594 }
595 _ => Err(serde::de::Error::custom("Expected array or object")),
596 }
597 }
598 };
599}
600
601#[derive(Debug, Deserialize)]
603pub struct SubaccountsUpdateMessage {
604 pub connection_id: String,
606 #[serde(deserialize_with = "deserialize_subaccounts_contents")]
608 pub contents: Vec<SubaccountUpdateMessageContents>,
609 pub id: String,
611 pub message_id: u64,
613 pub version: String,
615}
616generate_contents_deserialize_function!(
617 deserialize_subaccounts_contents,
618 SubaccountUpdateMessageContents
619);
620
621#[derive(Clone, Debug, Deserialize)]
623#[serde(rename_all = "camelCase")]
624#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
625pub struct SubaccountUpdateMessageContents {
626 pub perpetual_positions: Option<Vec<PerpetualPositionSubaccountMessageContents>>,
628 pub asset_positions: Option<Vec<AssetPositionSubaccountMessageContents>>,
630 pub orders: Option<Vec<OrderSubaccountMessageContents>>,
632 pub fills: Option<Vec<FillSubaccountMessageContents>>,
634 pub transfers: Option<TransferSubaccountMessageContents>,
636 pub trading_reward: Option<TradingRewardSubaccountMessageContents>,
638 pub block_height: Option<Height>,
640}
641
642#[derive(Clone, Debug, Deserialize)]
644#[serde(rename_all = "camelCase")]
645#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
646pub struct PerpetualPositionSubaccountMessageContents {
647 pub address: Address,
649 pub subaccount_number: SubaccountNumber,
651 pub position_id: String,
653 pub market: Ticker,
655 pub side: PositionSide,
657 pub status: PerpetualPositionStatus,
659 pub size: Quantity,
661 pub max_size: Quantity,
663 pub net_funding: BigDecimal,
665 pub entry_price: Price,
667 pub exit_price: Option<Price>,
669 pub sum_open: BigDecimal,
671 pub sum_close: BigDecimal,
673 pub realized_pnl: Option<BigDecimal>,
675 pub unrealized_pnl: Option<BigDecimal>,
677}
678
679#[derive(Clone, Debug, Deserialize)]
681#[serde(rename_all = "camelCase")]
682#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
683pub struct AssetPositionSubaccountMessageContents {
684 pub address: Address,
686 pub subaccount_number: SubaccountNumber,
688 pub position_id: String,
690 pub asset_id: AssetId,
692 pub symbol: Symbol,
694 pub side: PositionSide,
696 pub size: Quantity,
698}
699
700#[derive(Clone, Debug, Deserialize)]
702#[serde(rename_all = "camelCase")]
703#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
704pub struct OrderSubaccountMessageContents {
705 pub id: String,
707 pub subaccount_id: SubaccountId,
709 pub client_id: ClientId,
711 pub clob_pair_id: Option<ClobPairId>,
713 pub side: Option<OrderSide>,
715 pub size: Option<Quantity>,
717 pub ticker: Option<Ticker>,
719 pub price: Option<Price>,
721 #[serde(rename = "type")]
722 pub order_type: Option<OrderType>,
724 pub time_in_force: Option<ApiTimeInForce>,
726 pub post_only: Option<bool>,
728 pub reduce_only: Option<bool>,
730 pub status: ApiOrderStatus,
732 pub order_flags: OrderFlags,
734 pub total_filled: Option<BigDecimal>,
736 pub total_optimistic_filled: Option<BigDecimal>,
738 pub good_til_block: Option<Height>,
740 pub good_til_block_time: Option<DateTime<Utc>>,
742 pub trigger_price: Option<Price>,
744 pub updated_at: Option<DateTime<Utc>>,
746 pub updated_at_height: Option<Height>,
748 pub removal_reason: Option<String>,
750 pub created_at_height: Option<Height>,
752 pub client_metadata: Option<ClientMetadata>,
754}
755
756#[derive(Clone, Debug, Deserialize)]
758#[serde(rename_all = "camelCase")]
759#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
760pub struct FillSubaccountMessageContents {
761 pub id: FillId,
763 pub subaccount_id: SubaccountId,
765 pub side: OrderSide,
767 pub liquidity: Liquidity,
769 #[serde(rename = "type")]
771 pub fill_type: FillType,
772 pub clob_pair_id: ClobPairId,
774 pub size: Quantity,
776 pub price: Price,
778 pub quote_amount: String,
780 pub event_id: String,
782 pub transaction_hash: String,
784 pub created_at: DateTime<Utc>,
786 pub created_at_height: Height,
788 pub ticker: Ticker,
790 pub order_id: Option<OrderId>,
792 pub client_metadata: Option<ClientMetadata>,
794}
795
796#[derive(Clone, Debug, Deserialize)]
798#[serde(rename_all = "camelCase")]
799#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
800pub struct TransferSubaccountMessageContents {
801 pub sender: Account,
803 pub recipient: Account,
805 pub symbol: Symbol,
807 pub size: Quantity,
809 #[serde(rename = "type")]
811 pub transfer_type: TransferType,
812 pub transaction_hash: String,
814 pub created_at: DateTime<Utc>,
816 pub created_at_height: Height,
818}
819
820#[derive(Clone, Debug, Deserialize)]
822#[serde(rename_all = "camelCase")]
823#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
824pub struct TradingRewardSubaccountMessageContents {
825 pub trading_reward: BigDecimal,
827 pub created_at: DateTime<Utc>,
829 pub created_at_height: Height,
831}
832
833#[derive(Debug, Deserialize)]
835pub struct ParentSubaccountsUpdateMessage {
836 pub connection_id: String,
838 #[serde(deserialize_with = "deserialize_subaccounts_contents")]
840 pub contents: Vec<SubaccountUpdateMessageContents>,
841 pub id: String,
843 pub message_id: u64,
845 pub version: String,
847}
848
849#[derive(Clone, Debug, Deserialize)]
851#[serde(rename_all = "camelCase")]
852#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
853pub struct ParentSubaccountUpdateMessageContents {
854 pub perpetual_positions: Option<Vec<PerpetualPositionSubaccountMessageContents>>,
856 pub asset_positions: Option<Vec<AssetPositionSubaccountMessageContents>>,
858 pub orders: Option<Vec<OrderSubaccountMessageContents>>,
860 pub fills: Option<Vec<FillSubaccountMessageContents>>,
862 pub transfers: Option<TransferSubaccountMessageContents>,
864 pub trading_reward: Option<TradingRewardSubaccountMessageContents>,
866 pub block_height: Option<Height>,
868}
869
870#[derive(Debug, Deserialize)]
872pub struct OrdersUpdateMessage {
873 pub connection_id: String,
875 #[serde(deserialize_with = "deserialize_orders_contents")]
877 pub contents: OrdersUpdateMessageContents,
878 pub id: String,
880 pub message_id: u64,
882 pub version: String,
884}
885
886fn deserialize_orders_contents<'de, D>(
887 deserializer: D,
888) -> Result<OrdersUpdateMessageContents, D::Error>
889where
890 D: serde::Deserializer<'de>,
891{
892 let value = Value::deserialize(deserializer)?;
893
894 match value {
895 Value::Array(arr) => {
897 let mut bids = Vec::new();
898 let mut asks = Vec::new();
899
900 for v in arr {
901 let item: OrdersUpdateMessageContents =
902 serde_json::from_value(v).map_err(serde::de::Error::custom)?;
903
904 if let Some(item_bids) = item.bids {
905 bids.extend(item_bids);
906 }
907 if let Some(item_asks) = item.asks {
908 asks.extend(item_asks);
909 }
910 }
911
912 Ok(OrdersUpdateMessageContents {
913 bids: if bids.is_empty() { None } else { Some(bids) },
914 asks: if asks.is_empty() { None } else { Some(asks) },
915 })
916 }
917 Value::Object(obj) => {
919 let item =
920 serde_json::from_value::<OrdersUpdateMessageContents>(Value::Object(obj.clone()))
921 .map_err(serde::de::Error::custom)?;
922 Ok(item)
923 }
924 _ => Err(serde::de::Error::custom("Expected array or object")),
925 }
926}
927
928#[derive(Deserialize, Debug, Clone)]
930pub struct OrdersUpdateMessageContents {
931 pub bids: Option<Vec<OrderbookResponsePriceLevel>>,
933 pub asks: Option<Vec<OrderbookResponsePriceLevel>>,
935}
936
937#[derive(Deserialize, Debug, Clone)]
939pub struct TradesUpdateMessage {
940 pub connection_id: String,
942 #[serde(deserialize_with = "deserialize_trades_contents")]
944 pub contents: Vec<TradesUpdateMessageContents>,
945 pub id: String,
947 pub message_id: u64,
949 pub version: String,
951}
952generate_contents_deserialize_function!(deserialize_trades_contents, TradesUpdateMessageContents);
953
954#[derive(Deserialize, Debug, Clone)]
956#[serde(rename_all = "camelCase")]
957#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
958pub struct TradesUpdateMessageContents {
959 pub trades: Vec<TradeUpdate>,
961}
962
963#[derive(Deserialize, Debug, Clone)]
965#[serde(rename_all = "camelCase")]
966#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
967pub struct TradeUpdate {
968 pub id: TradeId,
970 pub created_at: DateTime<Utc>,
972 pub side: OrderSide,
974 pub price: Price,
976 pub size: Quantity,
978 #[serde(rename = "type")]
980 pub trade_type: TradeType,
981}
982
983#[derive(Debug, Deserialize)]
985pub struct MarketsUpdateMessage {
986 pub connection_id: String,
988 #[serde(deserialize_with = "deserialize_markets_contents")]
990 pub contents: Vec<MarketsUpdateMessageContents>,
991 pub message_id: u64,
993 pub version: String,
995}
996generate_contents_deserialize_function!(deserialize_markets_contents, MarketsUpdateMessageContents);
997
998#[derive(Debug, Deserialize)]
1000#[serde(rename_all = "camelCase")]
1001#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
1002pub struct MarketsUpdateMessageContents {
1003 pub trading: Option<HashMap<Ticker, TradingPerpetualMarket>>,
1005 pub oracle_prices: Option<HashMap<Ticker, OraclePriceMarket>>,
1007}
1008
1009#[derive(Deserialize, Debug, Clone)]
1011#[serde(rename_all = "camelCase")]
1012#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
1013pub struct TradingPerpetualMarket {
1014 pub atomic_resolution: Option<i32>,
1016 pub base_asset: Option<String>,
1018 pub base_open_interest: Option<BigDecimal>,
1020 pub base_position_size: Option<Quantity>,
1022 pub clob_pair_id: Option<ClobPairId>,
1024 pub id: Option<String>,
1026 pub market_id: Option<u64>,
1028 pub incremental_position_size: Option<Quantity>,
1030 pub initial_margin_fraction: Option<BigDecimal>,
1032 pub maintenance_margin_fraction: Option<BigDecimal>,
1034 pub max_position_size: Option<Quantity>,
1036 pub open_interest: Option<BigDecimal>,
1038 pub quantum_conversion_exponent: Option<i32>,
1040 pub quote_asset: Option<String>,
1042 pub status: Option<PerpetualMarketStatus>,
1044 pub step_base_quantums: Option<i32>,
1046 pub subticks_per_tick: Option<i32>,
1048 pub ticker: Option<Ticker>,
1050 #[serde(rename = "priceChange24H")]
1052 pub price_change_24h: Option<BigDecimal>,
1053 #[serde(rename = "trades24H")]
1055 pub trades_24h: Option<u64>,
1056 #[serde(rename = "volume24H")]
1058 pub volume_24h: Option<Quantity>,
1059 pub next_funding_rate: Option<BigDecimal>,
1061}
1062
1063#[derive(Debug, Deserialize)]
1065#[serde(rename_all = "camelCase")]
1066#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
1067pub struct OraclePriceMarket {
1068 pub oracle_price: Price,
1070 pub effective_at: DateTime<Utc>,
1072 pub effective_at_height: Height,
1074 pub market_id: u64,
1076}
1077
1078#[derive(Debug, Deserialize)]
1080pub struct CandlesUpdateMessage {
1081 pub connection_id: String,
1083 #[serde(deserialize_with = "deserialize_candles_contents")]
1085 pub contents: Vec<Candle>,
1086 pub id: String,
1088 pub message_id: u64,
1090 pub version: String,
1092}
1093generate_contents_deserialize_function!(deserialize_candles_contents, Candle);
1094
1095#[derive(Debug, Deserialize)]
1097pub struct BlockHeightUpdateMessage {
1098 pub connection_id: String,
1100 #[serde(deserialize_with = "deserialize_block_height_contents")]
1102 pub contents: Vec<BlockHeightUpdateMessageContents>,
1103 pub message_id: u64,
1105 pub version: String,
1107}
1108generate_contents_deserialize_function!(
1109 deserialize_block_height_contents,
1110 BlockHeightUpdateMessageContents
1111);
1112
1113#[derive(Debug, Deserialize)]
1115#[serde(rename_all = "camelCase")]
1116#[cfg_attr(any(test, feature = "strict-serde"), serde(deny_unknown_fields))]
1117pub struct BlockHeightUpdateMessageContents {
1118 pub block_height: Height,
1120 pub time: DateTime<Utc>,
1122}