1use crate::msgs::account::{
63 CommissionApproval, Fill, LeverageSetting, Margin, OrderState, PositionInfo,
64};
65use crate::msgs::responses::Response;
66use crate::msgs::subscription::SubscriptionRequest;
67use eyre::bail;
68use serde_json::{json, Value};
69use std::collections::HashMap;
70use std::str::FromStr;
71use std::sync::atomic::{AtomicBool, Ordering};
72use std::sync::{Arc, Mutex};
73use std::time::Duration;
74
75use crate::api::parts::command::Command;
76use crate::api::parts::config::WSConfig;
77use crate::api::parts::{make_nonce, Event, Topic};
78use crate::common::side::Side;
79use crate::common::tif::TimeInForce;
80use crate::msgs::md::{Candle, L2Snapshot, Ticker};
81use crate::msgs::{
82 ApproveCommissionFee, CancelAll, CancelOrder, LimitOrder, MarketOrder, Price,
83 RevokeCommissionFee,
84};
85use crate::transaction::{Action, ActionMeta, Transaction, TransactionSigner};
86use futures_util::stream::SplitSink;
87use futures_util::{SinkExt, StreamExt};
88use serde::Deserialize;
89use solana_hash::Hash;
90use solana_pubkey::Pubkey;
91use tokio::net::TcpStream;
92use tokio::sync::{broadcast, mpsc, oneshot, watch};
93use tokio::time;
94use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
95use tracing::{debug, error, info, warn};
96#[derive(Debug, Clone, Default, Deserialize)]
103#[allow(unused)]
104pub struct AccountState {
105 pub margin: Margin,
106 pub positions: HashMap<String, PositionInfo>,
107 pub open_orders: HashMap<String, OrderState>,
108 pub leverage_settings: HashMap<String, LeverageSetting>,
109 pub commission_approvals: Vec<CommissionApproval>,
110}
111
112#[allow(unused)]
119pub type EventHandler = Box<dyn Fn(&Event) + Send + Sync>;
120
121#[allow(unused)]
134#[derive(Clone)]
135pub struct BulkWsClient {
136 cmd_tx: mpsc::Sender<Command>,
138 handlers: Arc<Mutex<HashMap<Topic, Vec<EventHandler>>>>,
140
141 ticker_rx: watch::Receiver<HashMap<String, Ticker>>,
144 account_rx: watch::Receiver<AccountState>,
146
147 signer: Option<TransactionSigner>,
149 default_timeout: Duration,
150
151 next_request_id: std::sync::Arc<std::sync::atomic::AtomicU64>,
153
154 actor_handle: std::sync::Arc<tokio::sync::Mutex<Option<tokio::task::JoinHandle<()>>>>,
156
157 connected: Arc<AtomicBool>,
160
161 disconnect_tx: broadcast::Sender<String>,
165}
166
167#[allow(unused)]
168impl BulkWsClient {
169 pub async fn connect(config: WSConfig) -> eyre::Result<Self> {
179 info!("Connecting to {}", config.url);
180 let (ws_stream, _) = connect_async(&config.url).await?;
181 let (ws_write, ws_read) = ws_stream.split();
182 info!("Connected to Bulk Exchange WebSocket");
183
184 let (ticker_tx, ticker_rx) = watch::channel(HashMap::new());
186 let (account_tx, account_rx) = watch::channel(AccountState::default());
187
188 let (cmd_tx, cmd_rx) = mpsc::channel::<Command>(512);
190
191 let handlers: Arc<Mutex<HashMap<Topic, Vec<EventHandler>>>> = Arc::default();
193 let handlers_task = Arc::clone(&handlers);
194 let (event_tx, mut event_rx) = mpsc::channel::<(Topic, Event)>(32768);
195
196 tokio::spawn(async move {
197 while let Some((topic, event)) = event_rx.recv().await {
198 let map = handlers_task.lock().unwrap();
199 if let Some(hs) = map.get(&topic) {
200 for h in hs {
201 h(&event);
202 }
203 }
204 }
205 });
206
207 let connected = Arc::new(AtomicBool::new(true));
209 let (disconnect_tx, _) = broadcast::channel::<String>(4);
210
211 let actor = Actor {
212 ws_write,
213 event_tx,
214 cmd_rx,
215 ticker_tx,
216 account_tx,
217 tickers: HashMap::new(),
218 prices: HashMap::new(),
219 account_state: AccountState::default(),
220 pending: HashMap::new(),
221 subscriptions: Vec::new(),
222 connected: Arc::clone(&connected),
223 disconnect_tx: disconnect_tx.clone(),
224 };
225
226 let mut initial_subs = Vec::new();
228 if config.track_account {
229 if let Some(ref signer) = config.signer {
230 let pk_str = signer.public_key_b58();
231 initial_subs.push(SubscriptionRequest::new(
232 "account",
233 json!({ "user": pk_str }),
234 ));
235 }
236 }
237 if config.track_ticker {
238 for sym in &config.symbols {
239 initial_subs.push(SubscriptionRequest::new("ticker", json!({ "symbol": sym })));
240 }
241 }
242
243 let actor_handle = tokio::spawn(actor.run(ws_read, initial_subs));
245
246 Ok(Self {
247 cmd_tx,
248 handlers,
249 ticker_rx,
250 account_rx,
251 signer: config.signer,
252 default_timeout: config.default_timeout,
253 next_request_id: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)),
254 actor_handle: std::sync::Arc::new(tokio::sync::Mutex::new(Some(actor_handle))),
255 connected,
256 disconnect_tx,
257 })
258 }
259
260 pub async fn shutdown(&self) {
262 let _ = self.cmd_tx.send(Command::Shutdown).await;
263 if let Some(h) = self.actor_handle.lock().await.take() {
264 let _ = h.await;
265 }
266 }
267
268 pub async fn closed(&self) {
270 if let Some(h) = self.actor_handle.lock().await.take() {
271 let _ = h.await;
272 }
273 }
274
275 pub fn is_connected(&self) -> bool {
281 self.connected.load(Ordering::Relaxed)
282 }
283
284 pub fn get_ticker(&self, symbol: &str) -> Option<Ticker> {
296 self.ticker_rx.borrow().get(symbol).cloned()
297 }
298
299 pub fn get_price(&self, symbol: &str) -> Option<f64> {
307 self.ticker_rx.borrow().get(symbol).map(|x| x.mark_price)
308 }
309
310 pub fn get_tickers(&self) -> HashMap<String, Ticker> {
315 self.ticker_rx.borrow().clone()
316 }
317
318 pub fn get_margin(&self) -> Margin {
320 self.account_rx.borrow().margin.clone()
321 }
322
323 pub fn get_position(&self, symbol: &str) -> Option<PositionInfo> {
331 self.account_rx.borrow().positions.get(symbol).cloned()
332 }
333
334 pub fn get_positions(&self) -> HashMap<String, PositionInfo> {
336 self.account_rx.borrow().positions.clone()
337 }
338
339 pub fn get_leverage(&self, symbol: &str) -> Option<f64> {
347 self.account_rx
348 .borrow()
349 .leverage_settings
350 .get(symbol)
351 .map(|l| l.leverage)
352 }
353
354 pub async fn wait_tickers_changed(&mut self) -> eyre::Result<HashMap<String, Ticker>> {
360 self.ticker_rx.changed().await?;
361 Ok(self.ticker_rx.borrow().clone())
362 }
363
364 pub async fn wait_account_changed(&mut self) -> eyre::Result<AccountState> {
366 self.account_rx.changed().await?;
367 Ok(self.account_rx.borrow().clone())
368 }
369
370 pub async fn open_orders(&self, symbol: Option<&str>) -> eyre::Result<Vec<OrderState>> {
382 let (tx, rx) = oneshot::channel();
383 self.cmd_tx
384 .send(Command::GetOrders {
385 symbol: symbol.map(Into::into),
386 respond: tx,
387 })
388 .await
389 .map_err(|_| eyre::eyre!("actor gone"))?;
390 Ok(rx.await?)
391 }
392
393 pub async fn place_orders(
408 &self,
409 actions: Vec<Action>,
410 account: Option<Pubkey>,
411 nonce: Option<u64>,
412 ) -> eyre::Result<Vec<Response>> {
413 let signer = self
414 .signer
415 .as_ref()
416 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
417
418 let account = if let Some(account) = account {
419 account
420 } else {
421 signer.public_key()
422 };
423
424 let nonce = nonce.unwrap_or_else(make_nonce);
425 let pk = signer.public_key();
426
427 let mut tx = Transaction {
429 actions,
430 nonce,
431 account,
432 signer: signer.public_key(),
433 signature: Default::default(),
434 };
435 tx.sign(signer)?;
436
437 let request_id = self
438 .next_request_id
439 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
440
441 let body = serde_json::to_string(&tx)?;
443 let json = format!(
444 r#"{{"method":"post","request":{{"type":"action","payload":{}}},"id":{}}}"#,
445 body, request_id
446 );
447
448 let (resp_tx, resp_rx) = oneshot::channel();
449
450 self.cmd_tx
451 .send(Command::Tx {
452 request_id,
453 json,
454 respond: resp_tx,
455 })
456 .await
457 .map_err(|_| eyre::eyre!("client is disconnected — call connect() to reconnect"))?;
458
459 match time::timeout(self.default_timeout, resp_rx).await {
460 Ok(Ok(result)) => result,
461 Ok(Err(_)) => bail!("response channel dropped"),
462 Err(_) => bail!("order request {request_id} timed out"),
463 }
464 }
465
466 pub async fn update_oracle(
482 &self,
483 actions: Vec<Price>,
484 account: Option<Pubkey>,
485 nonce: Option<u64>,
486 ) -> eyre::Result<Vec<Response>> {
487 let signer = self
488 .signer
489 .as_ref()
490 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
491
492 let account = if let Some(account) = account {
493 account
494 } else {
495 signer.public_key()
496 };
497
498 let nonce = nonce.unwrap_or_else(make_nonce);
499
500 let mut tx = Transaction {
502 actions: actions.iter().map(|a| a.clone().into()).collect(),
503 nonce,
504 account,
505 signer: signer.public_key(),
506 signature: Default::default(),
507 };
508 tx.sign(signer)?;
509
510 let request_id = self
511 .next_request_id
512 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
513
514 let body = serde_json::to_string(&tx)?;
516 let json = format!(
517 r#"{{"method":"post","request":{{"type":"action","payload":{}}},"id":{}}}"#,
518 body, request_id
519 );
520
521 let (resp_tx, resp_rx) = oneshot::channel();
522
523 self.cmd_tx
524 .send(Command::Tx {
525 request_id,
526 json,
527 respond: resp_tx,
528 })
529 .await
530 .map_err(|_| eyre::eyre!("client is disconnected — call connect() to reconnect"))?;
531
532 match time::timeout(self.default_timeout, resp_rx).await {
533 Ok(Ok(result)) => result,
534 Ok(Err(_)) => bail!("oracle update response channel dropped"),
535 Err(_) => bail!("oracle update request {request_id} timed out"),
536 }
537 }
538
539 pub async fn place_limit_order(
554 &self,
555 symbol: &str,
556 side: Side,
557 price: f64,
558 size: f64,
559 tif: TimeInForce,
560 reduce_only: bool,
561 account: Option<Pubkey>,
562 nonce: Option<u64>,
563 ) -> eyre::Result<Response> {
564 let signer = self
565 .signer
566 .as_ref()
567 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
568
569 let account = if let Some(account) = account {
570 account
571 } else {
572 signer.public_key()
573 };
574
575 let nonce = nonce.unwrap_or_else(make_nonce);
576 let order = LimitOrder {
577 symbol: Arc::from(symbol),
578 is_buy: side == Side::Buy,
579 price,
580 size,
581 tif,
582 reduce_only,
583 iso: false,
584 builder_code: None,
585 meta: ActionMeta {
586 account,
587 nonce,
588 seqno: 0,
589 hash: None,
590 },
591 };
592 let resps = self.place_orders(vec![order.into()], None, None).await?;
593 resps
594 .into_iter()
595 .next()
596 .ok_or_else(|| eyre::eyre!("empty response"))
597 }
598
599 pub async fn place_market_order(
610 &self,
611 symbol: &str,
612 side: Side,
613 size: f64,
614 reduce_only: bool,
615 account: Option<Pubkey>,
616 nonce: Option<u64>,
617 ) -> eyre::Result<Response> {
618 let signer = self
619 .signer
620 .as_ref()
621 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
622
623 let account = if let Some(account) = account {
624 account
625 } else {
626 signer.public_key()
627 };
628
629 let nonce = nonce.unwrap_or_else(make_nonce);
630 let order = MarketOrder {
631 symbol: Arc::from(symbol),
632 is_buy: side == Side::Buy,
633 size,
634 reduce_only,
635 iso: false,
636 builder_code: None,
637 meta: ActionMeta {
638 account,
639 nonce,
640 seqno: 0,
641 hash: None,
642 },
643 };
644
645 let resps = self.place_orders(vec![order.into()], None, None).await?;
646 resps
647 .into_iter()
648 .next()
649 .ok_or_else(|| eyre::eyre!("empty response"))
650 }
651
652 pub async fn cancel_order(
661 &self,
662 symbol: &str,
663 order_id: &str,
664 account: Option<Pubkey>,
665 nonce: Option<u64>,
666 ) -> eyre::Result<Response> {
667 let signer = self
668 .signer
669 .as_ref()
670 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
671
672 let account = if let Some(account) = account {
673 account
674 } else {
675 signer.public_key()
676 };
677
678 let nonce = nonce.unwrap_or_else(make_nonce);
679 let cancel = CancelOrder {
680 symbol: symbol.to_string(),
681 oid: Hash::from_str(&order_id)?,
682 meta: ActionMeta {
683 account,
684 nonce,
685 seqno: 0,
686 hash: None,
687 },
688 };
689
690 let resps = self.place_orders(vec![cancel.into()], None, None).await?;
691 resps
692 .into_iter()
693 .next()
694 .ok_or_else(|| eyre::eyre!("empty response"))
695 }
696
697 pub async fn cancel_all(
705 &self,
706 symbols: Vec<String>,
707 account: Option<Pubkey>,
708 nonce: Option<u64>,
709 ) -> eyre::Result<Response> {
710 let signer = self
711 .signer
712 .as_ref()
713 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
714
715 let account = if let Some(account) = account {
716 account
717 } else {
718 signer.public_key()
719 };
720
721 let nonce = nonce.unwrap_or_else(make_nonce);
722 let cancel = CancelAll {
723 symbols,
724 meta: ActionMeta {
725 account,
726 nonce,
727 seqno: 0,
728 hash: None,
729 },
730 };
731 let resps = self.place_orders(vec![cancel.into()], None, None).await?;
732 resps
733 .into_iter()
734 .next()
735 .ok_or_else(|| eyre::eyre!("empty response"))
736 }
737
738 pub async fn approve_builder_code(
742 &self,
743 to: Pubkey,
744 fee: u8,
745 account: Option<Pubkey>,
746 nonce: Option<u64>,
747 ) -> eyre::Result<Response> {
748 let signer = self
749 .signer
750 .as_ref()
751 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
752
753 let account = if let Some(account) = account {
754 account
755 } else {
756 signer.public_key()
757 };
758
759 let nonce = nonce.unwrap_or_else(make_nonce);
760 let action = ApproveCommissionFee {
761 to,
762 max_fee: fee,
763 meta: ActionMeta {
764 account,
765 nonce,
766 seqno: 0,
767 hash: None,
768 },
769 };
770
771 let resps = self.place_orders(vec![action.into()], None, None).await?;
772 resps
773 .into_iter()
774 .next()
775 .ok_or_else(|| eyre::eyre!("empty response"))
776 }
777
778 pub async fn revoke_builder_code(
780 &self,
781 to: Pubkey,
782 account: Option<Pubkey>,
783 nonce: Option<u64>,
784 ) -> eyre::Result<Response> {
785 let signer = self
786 .signer
787 .as_ref()
788 .ok_or_else(|| eyre::eyre!("Private key required for trading operations"))?;
789
790 let account = if let Some(account) = account {
791 account
792 } else {
793 signer.public_key()
794 };
795
796 let nonce = nonce.unwrap_or_else(make_nonce);
797 let action = RevokeCommissionFee {
798 to,
799 meta: ActionMeta {
800 account,
801 nonce,
802 seqno: 0,
803 hash: None,
804 },
805 };
806
807 let resps = self.place_orders(vec![action.into()], None, None).await?;
808 resps
809 .into_iter()
810 .next()
811 .ok_or_else(|| eyre::eyre!("empty response"))
812 }
813
814 pub fn subscribe_disconnect(&self) -> broadcast::Receiver<String> {
835 self.disconnect_tx.subscribe()
836 }
837
838 pub async fn subscribe_ticker(&self, symbol: &str) -> eyre::Result<()> {
843 self.subscribe(vec![SubscriptionRequest::new(
844 "ticker",
845 json!({ "symbol": symbol }),
846 )])
847 .await
848 }
849
850 pub async fn subscribe_trades(&self, symbols: &[&str]) -> eyre::Result<()> {
855 let subs = symbols
856 .iter()
857 .map(|s| SubscriptionRequest::new("trades", json!({ "symbol": s })))
858 .collect();
859 self.subscribe(subs).await
860 }
861
862 pub async fn subscribe_l2_snapshot(
867 &self,
868 symbol: &str,
869 nlevels: Option<u32>,
870 ) -> eyre::Result<()> {
871 let mut params = json!({ "symbol": symbol });
872 if let Some(n) = nlevels {
873 params["nlevels"] = json!(n);
874 }
875 self.subscribe(vec![SubscriptionRequest::new("l2Snapshot", params)])
876 .await
877 }
878
879 pub async fn subscribe_l2_delta(&self, symbol: &str) -> eyre::Result<()> {
884 self.subscribe(vec![SubscriptionRequest::new(
885 "l2Delta",
886 json!({ "symbol": symbol }),
887 )])
888 .await
889 }
890
891 pub async fn subscribe_candles(&self, symbol: &str, interval: &str) -> eyre::Result<()> {
897 self.subscribe(vec![SubscriptionRequest::new(
898 "candle",
899 json!({ "symbol": symbol, "interval": interval }),
900 )])
901 .await
902 }
903
904 async fn subscribe(&self, subs: Vec<SubscriptionRequest>) -> eyre::Result<()> {
909 self.cmd_tx
910 .send(Command::Subscribe(subs))
911 .await
912 .map_err(|_| eyre::eyre!("actor gone"))?;
913 Ok(())
914 }
915
916 pub async fn on(&self, topic: Topic, handler: impl Fn(&Event) + Send + Sync + 'static) {
927 self.handlers
928 .lock()
929 .unwrap()
930 .entry(topic)
931 .or_default()
932 .push(Box::new(handler));
933 }
934}
935
936type WsWriter = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
941type WsReader = futures_util::stream::SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>;
942
943struct Actor {
944 ws_write: WsWriter,
946 event_tx: mpsc::Sender<(Topic, Event)>,
948
949 cmd_rx: mpsc::Receiver<Command>,
951
952 ticker_tx: watch::Sender<HashMap<String, Ticker>>,
954 account_tx: watch::Sender<AccountState>,
955
956 tickers: HashMap<String, Ticker>,
958 prices: HashMap<String, f64>,
959 account_state: AccountState,
960 pending: HashMap<u64, oneshot::Sender<eyre::Result<Vec<Response>>>>,
961
962 subscriptions: Vec<SubscriptionRequest>,
964
965 connected: Arc<AtomicBool>,
968 disconnect_tx: broadcast::Sender<String>,
970}
971
972impl Actor {
973 async fn run(mut self, mut ws_read: WsReader, initial_subs: Vec<SubscriptionRequest>) {
975 if !initial_subs.is_empty() {
977 if let Err(e) = self.send_subscribe(&initial_subs).await {
978 error!("Initial subscription failed: {e}");
979 return;
980 }
981 self.subscriptions = initial_subs;
982 }
983
984 self.emit(Topic::Status, &Event::Connected);
986
987 let disconnect_reason: String = 'actor: loop {
989 tokio::select! {
990 msg = ws_read.next() => {
992 match msg {
993 Some(Ok(Message::Text(text))) => {
994 debug!("msg {}: {}", text.len(), &text[0..512.min(text.len())]);
995 match serde_json::from_str::<Value>(&text) {
996 Ok(data) => self.handle_message(data, &text).await,
997 Err(e) => error!("JSON decode error: {e}"),
998 }
999 }
1000 Some(Ok(Message::Close(_))) => {
1001 warn!("WebSocket closed by server");
1002 break 'actor "server closed the connection".into();
1003 }
1004 Some(Err(e)) => {
1005 error!("WebSocket read error: {e}");
1006 break 'actor format!("WebSocket read error: {e}");
1007 }
1008 None => {
1009 warn!("WebSocket stream ended");
1010 break 'actor "WebSocket stream ended".into();
1011 }
1012 _ => {} }
1014 }
1015
1016 cmd = self.cmd_rx.recv() => {
1018 match cmd {
1019 Some(Command::Subscribe(subs)) => {
1020 if let Err(e) = self.send_subscribe(&subs).await {
1021 error!("Subscription send error: {e}");
1022 }
1023 self.subscriptions.extend(subs);
1024 }
1025
1026 Some(Command::Tx { request_id, json, respond }) => {
1027 self.pending.insert(request_id, respond);
1028 if let Err(e) = self.ws_send_text(&json).await {
1029 error!("Order send error: {e}");
1030 if let Some(tx) = self.pending.remove(&request_id) {
1031 let _ = tx.send(Err(e));
1032 }
1033 }
1034 }
1035
1036 Some(Command::AsyncTx { json}) => {
1037 if let Err(e) = self.ws_send_text(&json).await {
1038 error!("Order send error: {e}");
1039 }
1040 }
1041
1042 Some(Command::SendRaw(json)) => {
1043 if let Err(e) = self.ws_send_text(&json).await {
1044 error!("Raw send error: {e}");
1045 }
1046 }
1047
1048 Some(Command::GetOrders { symbol, respond }) => {
1049 let orders = match symbol {
1050 Some(s) => self.account_state.open_orders
1051 .values()
1052 .filter(|o| o.symbol == s)
1053 .cloned()
1054 .collect(),
1055 None => self.account_state.open_orders.values().cloned().collect(),
1056 };
1057 let _ = respond.send(orders);
1058 }
1059
1060 Some(Command::Shutdown) | None => {
1061 info!("Actor shutting down (requested)");
1062 break 'actor "shutdown requested".into();
1063 }
1064 }
1065 }
1066 }
1067 }; self.handle_disconnect(disconnect_reason).await;
1070 }
1071
1072 async fn handle_disconnect(&mut self, reason: String) {
1082 self.connected.store(false, Ordering::Release);
1084
1085 let err_msg = format!("disconnected: {reason}");
1087 for (_, tx) in self.pending.drain() {
1088 let _ = tx.send(Err(eyre::eyre!("{}", err_msg)));
1089 }
1090
1091 self.emit(Topic::Status, &Event::Disconnected(reason.clone()));
1093
1094 let _ = self.disconnect_tx.send(reason.clone());
1096
1097 let _ = self.ws_write.close().await;
1099
1100 info!("Actor stopped: {reason}");
1101 }
1102
1103 async fn ws_send_text(&mut self, text: &str) -> eyre::Result<()> {
1105 let len = text.len();
1106 debug!("sending msg len: {}", len);
1107 self.ws_write
1108 .send(Message::Text(text.into()))
1109 .await
1110 .map_err(|e| eyre::eyre!("ws write: {e}"))?;
1111 Ok(())
1112 }
1113
1114 async fn handle_message(&mut self, data: Value, json: &str) {
1119 let msg_type = data["type"].as_str().unwrap_or("");
1120
1121 match msg_type {
1122 "subscriptionResponse" => {
1123 info!(
1124 "Subscription confirmed: {:?}",
1125 data["topics"].as_array().map(|a| a.len())
1126 );
1127 }
1128
1129 "ticker" => {
1130 let ticker_v = &data["data"]["ticker"];
1131 if let Ok(ticker) = serde_json::from_value::<Ticker>(ticker_v.clone()) {
1132 self.prices.insert(ticker.symbol.clone(), ticker.mark_price);
1133 self.tickers.insert(ticker.symbol.clone(), ticker.clone());
1134
1135 let _ = self.ticker_tx.send(self.tickers.clone());
1137
1138 self.emit(Topic::Ticker, &Event::Ticker(ticker.clone()));
1139 debug!("Ticker: {} mark={:.2}", ticker.symbol, ticker.mark_price);
1140 } else {
1141 error!("Could not parse ticker event: {:?}", ticker_v);
1142 }
1143 }
1144
1145 "trades" => {
1146 if let Ok(trades) = serde_json::from_value::<Vec<Fill>>(data["data"].clone()) {
1147 self.emit(Topic::Trades, &Event::Trades(trades));
1148 } else {
1149 error!("Could not parse trades event: {:?}", data["data"]);
1150 }
1151 }
1152
1153 "l2Snapshot" => {
1154 if let Ok(l2_snapshot) =
1155 serde_json::from_value::<L2Snapshot>(data["data"]["book"].clone())
1156 {
1157 self.emit(Topic::L2Snapshot, &Event::L2Snapshot(l2_snapshot));
1158 } else {
1159 error!("Could not parse l2_snapshot event: msg: {:?}", data["data"]);
1160 }
1161 }
1162
1163 "l2Delta" => {
1164 if let Ok(l2_delta) =
1165 serde_json::from_value::<L2Snapshot>(data["data"]["book"].clone())
1166 {
1167 self.emit(Topic::L2Delta, &Event::L2Delta(l2_delta));
1168 } else {
1169 error!("Could not parse l2_delta event: {:?}", data["data"]);
1170 }
1171 }
1172
1173 "candle" => {
1174 if let Ok(candle) = serde_json::from_value::<Candle>(data["data"].clone()) {
1175 self.emit(Topic::Candle, &Event::Candle(candle));
1176 } else {
1177 error!("Could not parse candle event: {:?}", data["data"]);
1178 }
1179 }
1180
1181 "account" => {
1182 self.handle_account(&data["data"]).await;
1183 }
1184
1185 "post" => {
1186 self.handle_post_response(&data, json);
1187 }
1188
1189 other => {
1190 debug!("Unhandled message type: {other}");
1191 }
1192 }
1193 }
1194
1195 async fn handle_account(&mut self, data: &Value) {
1200 let update_type = data["type"].as_str().unwrap_or("");
1201
1202 match update_type {
1203 "accountSnapshot" => {
1204 if let Ok(margin) = serde_json::from_value::<Margin>(data["margin"].clone()) {
1205 self.account_state.margin = margin.clone();
1206 self.emit(Topic::Margin, &Event::Margin(margin))
1207 }
1208
1209 if let Ok(positions) =
1210 serde_json::from_value::<Vec<PositionInfo>>(data["positions"].clone())
1211 {
1212 for position in &positions {
1213 self.emit(Topic::Position, &Event::Position(position.clone()))
1214 }
1215 self.account_state.positions = positions
1216 .into_iter()
1217 .map(|p| (p.symbol.clone(), p.clone()))
1218 .collect();
1219 }
1220
1221 if let Ok(orders) =
1222 serde_json::from_value::<Vec<OrderState>>(data["openOrders"].clone())
1223 {
1224 for order in &orders {
1225 self.emit(Topic::Order, &Event::Order(order.clone()))
1226 }
1227 self.account_state.open_orders = orders
1228 .into_iter()
1229 .map(|o| (o.order_id.clone(), o))
1230 .collect();
1231 }
1232
1233 if let Ok(leverages) =
1234 serde_json::from_value::<Vec<LeverageSetting>>(data["leverageSettings"].clone())
1235 {
1236 self.emit(Topic::Leverage, &Event::Leverage(leverages.clone()));
1237 for l in leverages {
1238 self.account_state
1239 .leverage_settings
1240 .insert(l.symbol.clone(), l);
1241 }
1242 }
1243
1244 if let Ok(approvals) = serde_json::from_value::<Vec<CommissionApproval>>(
1245 data["builderCodeApprovals"].clone(),
1246 ) {
1247 self.account_state.commission_approvals = approvals;
1248 }
1249
1250 info!(
1251 "Account snapshot: balance={:.2}, positions={}, orders={}",
1252 self.account_state.margin.total_balance,
1253 self.account_state.positions.len(),
1254 self.account_state.open_orders.len(),
1255 );
1256 }
1257
1258 "orderUpdate" => {
1259 if let Ok(order) = serde_json::from_value::<OrderState>(data.clone()) {
1260 let oid = order.order_id.clone();
1261 if order.status.is_terminal() {
1262 self.account_state.open_orders.remove(&oid);
1263 } else {
1264 self.account_state.open_orders.insert(oid, order.clone());
1265 }
1266 self.emit(Topic::Order, &Event::Order(order));
1267 } else {
1268 error!("Could not parse order event: {:?}", data);
1269 }
1270 }
1271
1272 "marginUpdate" => {
1273 if let Ok(margin) = serde_json::from_value::<Margin>(data.clone()) {
1274 self.account_state.margin = margin.clone();
1275 self.publish_account();
1276 self.emit(Topic::Margin, &Event::Margin(margin));
1277 } else {
1278 error!("Could not parse margin event: {:?}", data);
1279 }
1280 }
1281
1282 "positionUpdate" => {
1283 if let Ok(pos) = serde_json::from_value::<PositionInfo>(data.clone()) {
1284 self.account_state
1285 .positions
1286 .insert(pos.symbol.clone(), pos.clone());
1287 self.emit(Topic::Position, &Event::Position(pos));
1288 self.publish_account();
1289 } else {
1290 error!("Could not parse position event: {:?}", data);
1291 }
1292 }
1293
1294 "fill" => {
1295 if let Ok(fill) = serde_json::from_value::<Fill>(data.clone()) {
1296 let dir = fill.side.dir();
1297 if let Some(order) = self.account_state.open_orders.get_mut(&fill.order_id) {
1298 order.filled_size += fill.size;
1299 order.signed_size -= dir * fill.size;
1300 if order.signed_size * dir <= 0.0 {
1301 self.account_state.open_orders.remove(&fill.order_id);
1302 }
1303 }
1304 self.publish_account();
1305 self.emit(Topic::Fill, &Event::Fill(fill.clone()));
1306 info!(
1307 "Fill: {} {:?} {} @ {} maker={}",
1308 fill.symbol, fill.side, fill.size, fill.price, fill.is_maker,
1309 );
1310 } else {
1311 error!("Could not parse fill event: {:?}", data);
1312 }
1313 }
1314
1315 "leverageUpdate" => {
1316 if let Ok(leverages) =
1317 serde_json::from_value::<Vec<LeverageSetting>>(data["leverage"].clone())
1318 {
1319 for lev in &leverages {
1320 self.account_state
1321 .leverage_settings
1322 .insert(lev.symbol.clone(), lev.clone());
1323 }
1324 self.emit(Topic::Leverage, &Event::Leverage(leverages.clone()));
1325 self.publish_account();
1326 } else {
1327 error!("Could not parse leverage event: {:?}", data);
1328 }
1329 }
1330
1331 _ => {
1332 debug!("Unknown account update: {update_type}");
1333 }
1334 }
1335 }
1336
1337 fn handle_post_response(&mut self, data: &Value, _json: &str) {
1342 let request_id = data["id"].as_u64().unwrap_or(0);
1343 let inner = &data["data"];
1344 let rtype = inner["type"].as_str().unwrap_or("");
1345 let sender = self.pending.remove(&request_id);
1346
1347 match rtype {
1348 "action" => {
1349 let payload = &inner["payload"];
1350 let status = payload["status"].as_str().unwrap_or("");
1351
1352 if status != "ok" {
1353 error!("Order request {request_id} failed: {status}");
1354 if let Some(tx) = sender {
1355 let _ = tx.send(Err(eyre::eyre!("order request failed: {}", data)));
1356 }
1357 self.emit(Topic::Error, &Event::Error(data.clone()));
1358 } else {
1359 let responses = Response::parse_responses(data);
1360 if let Some(tx) = sender {
1361 let _ = tx.send(Ok(responses));
1362 }
1363 }
1364 }
1365 "ack" => {
1366 let ok = inner["ok"].as_bool().unwrap_or(false);
1367 let response = if ok {
1368 Response {
1369 order_id: None,
1370 status: "OK".to_string(),
1371 message: None,
1372 raw: inner.clone(),
1373 }
1374 } else {
1375 let message = inner["message"].as_str().unwrap_or("");
1376 Response {
1377 order_id: None,
1378 status: "Error".to_string(),
1379 message: Some(message.to_string()),
1380 raw: inner.clone(),
1381 }
1382 };
1383 if let Some(tx) = sender {
1384 let _ = tx.send(Ok(vec![response]));
1385 }
1386 }
1387 _ => panic!("unknown response type: {}", rtype),
1388 }
1389 }
1390
1391 fn publish_account(&self) {
1397 let _ = self.account_tx.send(self.account_state.clone());
1398 }
1399
1400 fn emit(&self, topic: Topic, data: &Event) {
1402 let _ = self.event_tx.try_send((topic, data.clone()));
1403 }
1404
1405 async fn ws_send_json(&mut self, value: &Value) -> eyre::Result<()> {
1407 let text = serde_json::to_string(value)?;
1408 self.ws_write
1409 .send(Message::Text(text.into()))
1410 .await
1411 .map_err(|e| eyre::eyre!("ws write: {e}"))?;
1412 Ok(())
1413 }
1414
1415 async fn send_subscribe(&mut self, subs: &[SubscriptionRequest]) -> eyre::Result<()> {
1417 let request = json!({
1418 "method": "subscribe",
1419 "subscription": subs.iter().map(|s| s.to_json()).collect::<Vec<_>>(),
1420 });
1421 self.ws_send_json(&request).await?;
1422 info!("Subscribed to {} topics", subs.len());
1423 Ok(())
1424 }
1425}