Skip to main content

acta_maker_sdk/ws/maker/
quote.rs

1use crate::ws::managed::{
2    MakerWsEndpoint, ManagedMessageReceiver, ManagedWsConfig, ManagedWsError, ManagedWsEvent,
3    ManagedWsHandle, ManagedWsState, ReferenceChangeReceiver, SendTicket, WaitUntilReadyError,
4    spawn_managed_ws,
5};
6use crate::ws::types::{
7    AddChannelsData, AddMintsData, BatchQuotesMessage, CancelAllQuotesMessage, CancelQuoteData,
8    ClientMessage, IndicativePricesResponseMessage, QuoteMessage, RemoveChannelsData,
9    RemoveMintsData, ReplaceQuoteMessage, SubscribeData, UnsubscribeData,
10};
11use tokio::sync::broadcast;
12
13use super::MakerClientError;
14
15/// Maker quote-plane client.
16///
17/// `try_*` methods return immediately when the outbound queue is full. A
18/// successful [`SendTicket`] reports a socket write, not a server acknowledgement.
19#[derive(Clone)]
20pub struct MakerQuoteClient {
21    inner: ManagedWsHandle,
22}
23
24impl MakerQuoteClient {
25    pub fn spawn(config: ManagedWsConfig) -> Result<Self, MakerClientError> {
26        if config.endpoint != MakerWsEndpoint::Quote {
27            return Err(MakerClientError::WrongEndpoint {
28                client: "MakerQuoteClient",
29                required: MakerWsEndpoint::Quote,
30                actual: config.endpoint,
31            });
32        }
33        Ok(Self {
34            inner: spawn_managed_ws(config)?,
35        })
36    }
37
38    /// Send only in `connection_epoch`, the epoch whose recovery the strategy
39    /// applied; a reconnect in between rejects the quote with `NotReady`.
40    /// Success confirms only the socket write. Subscribe before sending and feed
41    /// messages to [`super::QuoteOutcomeTracker`] to track the server outcome.
42    pub async fn quote_in_epoch(
43        &self,
44        payload: QuoteMessage,
45        connection_epoch: u64,
46    ) -> Result<(), ManagedWsError> {
47        self.inner
48            .send_in_epoch(ClientMessage::Quote(payload), connection_epoch)
49            .await
50    }
51
52    pub fn try_quote_in_epoch(
53        &self,
54        payload: QuoteMessage,
55        connection_epoch: u64,
56    ) -> Result<SendTicket, ManagedWsError> {
57        self.inner
58            .try_send_in_epoch(ClientMessage::Quote(payload), connection_epoch)
59    }
60
61    pub async fn replace_in_epoch(
62        &self,
63        payload: ReplaceQuoteMessage,
64        connection_epoch: u64,
65    ) -> Result<(), ManagedWsError> {
66        self.inner
67            .send_in_epoch(ClientMessage::ReplaceQuote(payload), connection_epoch)
68            .await
69    }
70
71    pub fn try_replace_in_epoch(
72        &self,
73        payload: ReplaceQuoteMessage,
74        connection_epoch: u64,
75    ) -> Result<SendTicket, ManagedWsError> {
76        self.inner
77            .try_send_in_epoch(ClientMessage::ReplaceQuote(payload), connection_epoch)
78    }
79
80    pub async fn batch_quotes_in_epoch(
81        &self,
82        payload: BatchQuotesMessage,
83        connection_epoch: u64,
84    ) -> Result<(), ManagedWsError> {
85        self.inner
86            .send_in_epoch(ClientMessage::BatchQuotes(payload), connection_epoch)
87            .await
88    }
89
90    pub fn try_batch_quotes_in_epoch(
91        &self,
92        payload: BatchQuotesMessage,
93        connection_epoch: u64,
94    ) -> Result<SendTicket, ManagedWsError> {
95        self.inner
96            .try_send_in_epoch(ClientMessage::BatchQuotes(payload), connection_epoch)
97    }
98
99    pub async fn cancel_quote(&self, payload: CancelQuoteData) -> Result<(), ManagedWsError> {
100        self.inner.ensure_ready()?;
101        self.inner.send(ClientMessage::CancelQuote(payload)).await
102    }
103
104    pub fn try_cancel_quote(&self, payload: CancelQuoteData) -> Result<SendTicket, ManagedWsError> {
105        self.inner.ensure_ready()?;
106        self.inner.try_send(ClientMessage::CancelQuote(payload))
107    }
108
109    pub async fn cancel_all_quotes(
110        &self,
111        payload: CancelAllQuotesMessage,
112    ) -> Result<(), ManagedWsError> {
113        self.inner.ensure_ready()?;
114        self.inner
115            .send(ClientMessage::CancelAllQuotes(payload))
116            .await
117    }
118
119    pub fn try_cancel_all_quotes(
120        &self,
121        payload: CancelAllQuotesMessage,
122    ) -> Result<SendTicket, ManagedWsError> {
123        self.inner.ensure_ready()?;
124        self.inner.try_send(ClientMessage::CancelAllQuotes(payload))
125    }
126
127    pub async fn indicative_prices_response(
128        &self,
129        payload: IndicativePricesResponseMessage,
130    ) -> Result<(), ManagedWsError> {
131        self.inner.ensure_ready()?;
132        self.inner
133            .send(ClientMessage::IndicativePricesResponse(payload))
134            .await
135    }
136
137    pub fn try_indicative_prices_response(
138        &self,
139        payload: IndicativePricesResponseMessage,
140    ) -> Result<SendTicket, ManagedWsError> {
141        self.inner.ensure_ready()?;
142        self.inner
143            .try_send(ClientMessage::IndicativePricesResponse(payload))
144    }
145
146    pub async fn subscribe(&self, payload: SubscribeData) -> Result<(), ManagedWsError> {
147        self.inner.ensure_ready()?;
148        self.inner.send(ClientMessage::Subscribe(payload)).await
149    }
150
151    pub async fn unsubscribe(&self, payload: UnsubscribeData) -> Result<(), ManagedWsError> {
152        self.inner.ensure_ready()?;
153        self.inner.send(ClientMessage::Unsubscribe(payload)).await
154    }
155
156    pub async fn add_mints(&self, payload: AddMintsData) -> Result<(), ManagedWsError> {
157        self.inner.ensure_ready()?;
158        self.inner.send(ClientMessage::AddMints(payload)).await
159    }
160
161    pub async fn remove_mints(&self, payload: RemoveMintsData) -> Result<(), ManagedWsError> {
162        self.inner.ensure_ready()?;
163        self.inner.send(ClientMessage::RemoveMints(payload)).await
164    }
165
166    pub async fn add_channels(&self, payload: AddChannelsData) -> Result<(), ManagedWsError> {
167        self.inner.ensure_ready()?;
168        self.inner.send(ClientMessage::AddChannels(payload)).await
169    }
170
171    pub async fn remove_channels(&self, payload: RemoveChannelsData) -> Result<(), ManagedWsError> {
172        self.inner.ensure_ready()?;
173        self.inner
174            .send(ClientMessage::RemoveChannels(payload))
175            .await
176    }
177
178    pub fn subscribe_messages(&self) -> ManagedMessageReceiver {
179        self.inner.subscribe_messages()
180    }
181
182    pub fn subscribe_reference_changes(&self) -> ReferenceChangeReceiver {
183        self.inner.subscribe_reference_changes()
184    }
185
186    pub fn subscribe_events(&self) -> broadcast::Receiver<ManagedWsEvent> {
187        self.inner.subscribe_events()
188    }
189
190    #[must_use]
191    pub fn state(&self) -> ManagedWsState {
192        self.inner.state()
193    }
194
195    pub async fn wait_until_ready(&self) -> Result<u64, WaitUntilReadyError> {
196        self.inner.wait_until_ready().await
197    }
198
199    #[must_use]
200    pub const fn raw(&self) -> &ManagedWsHandle {
201        &self.inner
202    }
203
204    #[must_use]
205    pub fn into_raw(self) -> ManagedWsHandle {
206        self.inner
207    }
208
209    pub async fn close(&self) -> Result<(), ManagedWsError> {
210        self.inner.close().await
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use std::sync::Arc;
217
218    use super::*;
219    use crate::ws::types::HelloData;
220
221    #[test]
222    fn quote_client_rejects_data_endpoint_before_spawning() {
223        let config = ManagedWsConfig::new(
224            "ws://localhost",
225            HelloData {
226                protocol_version: crate::WS_PROTOCOL_VERSION.to_string(),
227                on_disconnect: crate::ws::types::OnDisconnect::KeepQuotes,
228                client_name: "test".to_string(),
229                client_version: "test".to_string(),
230            },
231            Arc::new(crate::orders::BytesSigner::from_secret([1u8; 32])),
232            crate::Address32::new([76; 32]),
233        )
234        .with_endpoint(MakerWsEndpoint::Data);
235
236        assert!(matches!(
237            MakerQuoteClient::spawn(config),
238            Err(MakerClientError::WrongEndpoint {
239                required: MakerWsEndpoint::Quote,
240                actual: MakerWsEndpoint::Data,
241                ..
242            })
243        ));
244    }
245}