nash_protocol/protocol/place_order/
types.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5use tokio::sync::{Mutex, RwLock};
6use tracing::error;
7use serde::{Serialize, Deserialize};
8
9use crate::errors::Result;
10use crate::graphql::place_limit_order;
11use crate::graphql::place_market_order;
12use crate::protocol::ErrorResponse;
13use crate::protocol::{
14    asset_nonces::AssetNoncesRequest, list_markets::ListMarketsRequest, serializable_to_json,
15    sign_all_states::SignAllStates, try_response_from_json, NashProtocol, NashProtocolRequest,
16    ProtocolHook, ResponseOrError, State,
17};
18use crate::types::{
19    AssetAmount, AssetofPrecision, BuyOrSell, Market, Nonce, OrderCancellationPolicy, OrderStatus,
20    OrderType, Rate,
21};
22use crate::utils::current_time_as_i64;
23
24/// Request to place limit orders on Nash exchange. On an A/B market
25/// price amount will always be in terms of A and price in terms of B.
26#[derive(Clone, Debug)]
27pub struct LimitOrderRequest {
28    pub market: String,
29    pub client_order_id: Option<String>,
30    pub buy_or_sell: BuyOrSell,
31    pub amount: String,
32    pub price: String,
33    pub cancellation_policy: OrderCancellationPolicy,
34    pub allow_taker: bool,
35}
36
37#[derive(Clone, Debug)]
38pub struct MarketOrderRequest {
39    pub client_order_id: Option<String>,
40    pub market: String,
41    pub amount: String,
42}
43
44impl LimitOrderRequest {
45    pub fn new(
46        market: String,
47        buy_or_sell: BuyOrSell,
48        amount_a: &str,
49        price_b: &str,
50        cancellation_policy: OrderCancellationPolicy,
51        allow_taker: bool,
52        client_order_id: Option<String>,
53    ) -> Result<Self> {
54        Ok(Self {
55            market,
56            buy_or_sell,
57            amount: amount_a.to_string(),
58            price: price_b.to_string(),
59            cancellation_policy,
60            allow_taker,
61            client_order_id,
62        })
63    }
64}
65
66impl MarketOrderRequest {
67    pub fn new(market: String, amount_a: &str, client_order_id: Option<String>) -> Result<Self> {
68        Ok(Self {
69            market,
70            amount: amount_a.to_string(),
71            client_order_id,
72        })
73    }
74}
75
76/// A helper type for constructing blockchain payloads and GraphQL requests
77pub struct LimitOrderConstructor {
78    // These fields are for GraphQL
79    pub buy_or_sell: BuyOrSell,
80    pub client_order_id: Option<String>,
81    pub market: Market,
82    pub me_amount: AssetAmount,
83    pub me_rate: Rate,
84    pub cancellation_policy: OrderCancellationPolicy,
85    pub allow_taker: bool,
86    // These fields are for the smart contracts
87    pub source: AssetAmount,
88    pub destination: AssetofPrecision,
89    pub rate: Rate,
90}
91
92pub struct MarketOrderConstructor {
93    // These fields are for GraphQL
94    pub market: Market,
95    pub client_order_id: Option<String>,
96    pub me_amount: AssetAmount,
97    // These fields are for the smart contracts
98    pub source: AssetAmount,
99    pub destination: AssetofPrecision,
100}
101
102/// Helper type to hold all nonces for payload construction and make
103/// passing them as arguments more descriptive.
104#[derive(Clone, Debug, Copy)]
105pub struct PayloadNonces {
106    pub nonce_from: Nonce,
107    pub nonce_to: Nonce,
108    pub order_nonce: Nonce,
109}
110
111#[derive(Serialize, Deserialize, Clone, Debug)]
112pub struct MarketName {
113    pub name: String
114}
115
116/// Response from server once we have placed a limit order
117#[derive(Serialize, Deserialize, Clone, Debug)]
118#[serde(rename_all = "camelCase")]
119pub struct PlaceOrderResponse {
120    #[serde(rename = "ordersTillSignState")]
121    pub remaining_orders: u64,
122    #[serde(rename = "id")]
123    pub order_id: String,
124    pub status: OrderStatus,
125    pub placed_at: DateTime<Utc>,
126    #[serde(rename = "type")]
127    pub order_type: OrderType,
128    pub buy_or_sell: BuyOrSell,
129    pub market: MarketName,
130}
131
132async fn get_required_hooks(state: Arc<RwLock<State>>, market: &str) -> Result<Vec<ProtocolHook>> {
133    let state = state.read().await;
134
135    let mut hooks = Vec::new();
136    // If we need assets or markets list, pull them
137    match (&state.assets, &state.markets) {
138        (None, _) | (_, None) => {
139            hooks.push(ProtocolHook::Protocol(NashProtocolRequest::ListMarkets(
140                ListMarketsRequest,
141            )));
142        }
143        _ => {}
144    }
145    // If have run out of r values, get more before running this pipeline
146    let chains = state.get_market(market)?.blockchains();
147    let fill_pool_schedules = state
148        .acquire_fill_pool_schedules(Some(&chains), Some(10))
149        .await?;
150    for (request, permit) in fill_pool_schedules {
151        // A bit too complicated but we have to satisfy the compiler
152        let permit = Some(Arc::new(Mutex::new(Some(permit))));
153        hooks.push(ProtocolHook::Protocol(NashProtocolRequest::DhFill(
154            request, permit,
155        )));
156    }
157    // Retrieve asset nonces if we don't have them or an error triggered need to refresh
158    match (state.asset_nonces.as_ref(), state.assets_nonces_refresh) {
159        (None, _) | (_, true) => {
160            hooks.push(ProtocolHook::Protocol(NashProtocolRequest::AssetNonces(
161                AssetNoncesRequest::new(),
162            )));
163        }
164        _ => {}
165    }
166    // If we are about to run out of orders...
167    if !state.dont_sign_states && state.get_remaining_orders() < 10 {
168        // Need to sign states
169        hooks.push(ProtocolHook::SignAllState(SignAllStates::new()));
170        // After signing states, need to update nonces again
171        hooks.push(ProtocolHook::Protocol(NashProtocolRequest::AssetNonces(
172            AssetNoncesRequest::new(),
173        )));
174    }
175    Ok(hooks)
176}
177
178#[async_trait]
179impl NashProtocol for LimitOrderRequest {
180    type Response = PlaceOrderResponse;
181
182    async fn acquire_permit(
183        &self,
184        state: Arc<RwLock<State>>,
185    ) -> Option<tokio::sync::OwnedSemaphorePermit> {
186        state
187            .read()
188            .await
189            .place_order_semaphore
190            .clone()
191            .acquire_owned()
192            .await
193            .ok()
194    }
195
196    async fn graphql(&self, state: Arc<RwLock<State>>) -> Result<serde_json::Value> {
197        let builder = self.make_constructor(state.clone()).await?;
198        let time = current_time_as_i64();
199        let nonces = builder.make_payload_nonces(state.clone(), time).await?;
200        let state = state.read().await;
201        let affiliate = state.affiliate_code.clone();
202        let market = state.get_market(&self.market)?;
203        let order_precision = 8;
204        let fee_precision = market.min_trade_size_b.asset.precision;
205        let query = builder.signed_graphql_request(nonces, time, affiliate, state.signer()?, order_precision, fee_precision)?;
206        let json = serializable_to_json(&query);
207        json
208    }
209
210    async fn response_from_json(
211        &self,
212        response: serde_json::Value,
213        _state: Arc<RwLock<State>>,
214    ) -> Result<ResponseOrError<Self::Response>> {
215        try_response_from_json::<PlaceOrderResponse, place_limit_order::ResponseData>(response)
216    }
217
218    /// Update the number of orders remaining before state sync
219    async fn process_response(
220        &self,
221        response: &Self::Response,
222        state: Arc<RwLock<State>>,
223    ) -> Result<()> {
224        let state = state.read().await;
225        state.set_remaining_orders(response.remaining_orders);
226        Ok(())
227    }
228
229    async fn process_error(
230        &self,
231        response: &ErrorResponse,
232        state: Arc<RwLock<State>>,
233    ) -> Result<()> {
234        // TODO: Do we need to decrement for errors?
235        state.read().await.decr_remaining_orders();
236        for err in &response.errors {
237            if err.message.find("invalid blockchain signature").is_some() {
238                error!(err = %err.message, request = ?self, "invalid blockchain signature");
239            }
240        }
241        Ok(())
242    }
243
244    /// Potentially get more r values or sign states before placing an order
245    async fn run_before(&self, state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
246        get_required_hooks(state, &self.market).await.map(Some)
247    }
248}
249
250#[async_trait]
251impl NashProtocol for MarketOrderRequest {
252    type Response = PlaceOrderResponse;
253
254    async fn acquire_permit(
255        &self,
256        state: Arc<RwLock<State>>,
257    ) -> Option<tokio::sync::OwnedSemaphorePermit> {
258        state
259            .read()
260            .await
261            .place_order_semaphore
262            .clone()
263            .acquire_owned()
264            .await
265            .ok()
266    }
267
268    async fn graphql(&self, state: Arc<RwLock<State>>) -> Result<serde_json::Value> {
269        let builder = self.make_constructor(state.clone()).await?;
270        let time = current_time_as_i64();
271        let nonces = builder.make_payload_nonces(state.clone(), time).await?;
272        let state = state.read().await;
273        let affiliate = state.affiliate_code.clone();
274        let market = state.get_market(&self.market)?;
275        let order_precision = 8;
276        let fee_precision = market.min_trade_size_b.asset.precision;
277        let query = builder.signed_graphql_request(nonces, time, affiliate, state.signer()?, order_precision, fee_precision)?;
278        serializable_to_json(&query)
279    }
280
281    async fn response_from_json(
282        &self,
283        response: serde_json::Value,
284        _state: Arc<RwLock<State>>,
285    ) -> Result<ResponseOrError<Self::Response>> {
286        try_response_from_json::<PlaceOrderResponse, place_market_order::ResponseData>(response)
287    }
288
289    /// Update the number of orders remaining before state sync
290    async fn process_response(
291        &self,
292        response: &Self::Response,
293        state: Arc<RwLock<State>>,
294    ) -> Result<()> {
295        let state = state.read().await;
296        // TODO: Incorporate error into process response
297        state.set_remaining_orders(response.remaining_orders);
298        Ok(())
299    }
300
301    async fn process_error(
302        &self,
303        _response: &ErrorResponse,
304        state: Arc<RwLock<State>>,
305    ) -> Result<()> {
306        // TODO: Do we need to decrement for errors?
307        state.read().await.decr_remaining_orders();
308        Ok(())
309    }
310
311    /// Potentially get more r values or sign states before placing an order
312    async fn run_before(&self, state: Arc<RwLock<State>>) -> Result<Option<Vec<ProtocolHook>>> {
313        get_required_hooks(state, &self.market).await.map(Some)
314    }
315}