nash_protocol/protocol/place_order/
request.rs

1use crate::errors::{ProtocolError, Result};
2use crate::graphql;
3use crate::graphql::place_limit_order;
4use crate::graphql::place_market_order;
5use crate::types::neo::PublicKey as NeoPublicKey;
6use crate::types::PublicKey;
7use crate::types::{
8    Asset, AssetAmount, Blockchain, BuyOrSell, Nonce, OrderCancellationPolicy, OrderRate, Rate
9};
10use crate::utils::pad_zeros;
11use graphql_client::GraphQLQuery;
12use std::convert::TryInto;
13
14use super::super::signer::Signer;
15use super::super::{general_canonical_string, RequestPayloadSignature, State};
16use super::blockchain::{btc, eth, neo, FillOrder};
17use super::types::{
18    LimitOrderConstructor, LimitOrderRequest,
19    MarketOrderConstructor, MarketOrderRequest,
20    PayloadNonces
21};
22
23use tokio::sync::RwLock;
24use std::sync::Arc;
25
26type LimitOrderMutation = graphql_client::QueryBody<place_limit_order::Variables>;
27type MarketOrderMutation = graphql_client::QueryBody<place_market_order::Variables>;
28type LimitBlockchainSignatures = Vec<Option<place_limit_order::BlockchainSignature>>;
29type MarketBlockchainSignatures = Vec<Option<place_market_order::BlockchainSignature>>;
30
31impl LimitOrderRequest {
32    // Buy or sell `amount` of `A` in price of `B` for an A/B market. Returns a builder struct
33    // of `LimitOrderConstructor` that can be used to create smart contract and graphql payloads
34    pub async fn make_constructor(&self, state: Arc<RwLock<State>>) -> Result<LimitOrderConstructor> {
35        let state = state.read().await;
36        let market = state.get_market(&self.market)?;
37
38        // Amount of order always in asset A in ME. This will handle precision conversion also...
39        let amount_of_a = market.asset_a.with_amount(&self.amount)?;
40
41        // Price is always in terms of asset B in ME
42        // TODO: add precision to rate and handle this better
43        let format_user_price = pad_zeros(&self.price, market.asset_b.precision)?;
44        let b_per_a: Rate = OrderRate::new(&format_user_price)?.into();
45        
46        let a_per_b = b_per_a.invert_rate(None)?;
47
48        let amount_of_b = amount_of_a.exchange_at(&b_per_a, market.asset_b)?;
49
50        let (source, rate, destination) = match self.buy_or_sell {
51            BuyOrSell::Buy => {
52                // Buying: in SC, source is B, rate is B, and moving to asset A
53                (amount_of_b, a_per_b.clone(), market.asset_a)
54            }
55            BuyOrSell::Sell => {
56                // Selling: in SC, source is A, rate is A, and moving to asset B
57                (amount_of_a.clone(), b_per_a.clone(), market.asset_b)
58            }
59        };
60
61        Ok(LimitOrderConstructor {
62            client_order_id: self.client_order_id.clone(),
63            me_amount: amount_of_a,
64            me_rate: b_per_a,
65            market: market.clone(),
66            buy_or_sell: self.buy_or_sell,
67            cancellation_policy: self.cancellation_policy,
68            allow_taker: self.allow_taker,
69            source,
70            destination,
71            rate,
72        })
73    }
74}
75
76
77impl MarketOrderRequest {
78    // Buy or sell `amount` of `A` in price of `B` for an A/B market. Returns a builder struct
79    // of `LimitOrderConstructor` that can be used to create smart contract and graphql payloads
80    pub async fn make_constructor(&self, state: Arc<RwLock<State>>) -> Result<MarketOrderConstructor> {
81        let state = state.read().await;
82
83        let market = match state.get_market(&self.market) {
84            Ok(market) => market,
85            Err(_) => {
86                let reverse_market: Vec<&str> = self.market.split('_').rev().collect();
87                let reverse_market = reverse_market.join("_");
88                match state.get_market(&reverse_market) {
89                    Ok(market) => market.invert(),
90                    Err(err) => return Err(err)
91                }
92            }
93        };
94
95        let source = market.asset_a.with_amount(&self.amount)?;
96        let destination =  market.asset_b;
97
98        Ok(MarketOrderConstructor {
99            client_order_id: self.client_order_id.clone(),
100            me_amount: source.clone(),
101            market: market.clone(),
102            source,
103            destination,
104        })
105    }
106}
107
108// If an asset is on another chain, convert it into a crosschain nonce
109// FIXME: maybe Nonce should also keep track of asset type to make this easier?
110fn map_crosschain(nonce: Nonce, chain: Blockchain, asset: Asset) -> Nonce {
111    if asset.blockchain() == chain {
112        nonce
113    } else {
114        Nonce::Crosschain
115    }
116}
117
118impl LimitOrderConstructor {
119    /// Helper to transform a limit order into signed fillorder data on every blockchain
120    pub fn make_fill_order(
121        &self,
122        chain: Blockchain,
123        pub_key: &PublicKey,
124        nonces: &PayloadNonces,
125        order_precision: u32
126    ) -> Result<FillOrder> {
127        // Rate is in "dest per source", so a higher rate is always beneficial to a user
128        // Here we insure the minimum rate is the rate they specified
129        let min_order = self.rate.clone();
130        let max_order = Rate::MaxOrderRate;
131        // Amount is specified in the "source" asset
132        let amount = self.source.amount.clone();
133        let min_order = min_order
134            .subtract_fee(Rate::MaxFeeRate.to_bigdecimal()?)?
135            .round(order_precision as i64 + 2)
136            .into();
137        let fee_rate = Rate::MinFeeRate; // 0
138
139        match chain {
140            Blockchain::Ethereum => Ok(FillOrder::Ethereum(eth::FillOrder::new(
141                pub_key.to_address()?.try_into()?,
142                self.source.asset.into(),
143                self.destination.into(),
144                map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
145                map_crosschain(nonces.nonce_to, chain, self.destination.into()),
146                amount,
147                min_order,
148                max_order,
149                fee_rate,
150                nonces.order_nonce,
151            ))),
152            Blockchain::Bitcoin => Ok(FillOrder::Bitcoin(btc::FillOrder::new(
153                map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
154                map_crosschain(nonces.nonce_to, chain, self.destination.into()),
155            ))),
156            Blockchain::NEO => {
157                // FIXME: this can still be improved...
158                let neo_pub_key: NeoPublicKey = pub_key.clone().try_into()?;
159                let neo_order = neo::FillOrder::new(
160                    neo_pub_key,
161                    self.source.asset.into(),
162                    self.destination.into(),
163                    map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
164                    map_crosschain(nonces.nonce_to, chain, self.destination.into()),
165                    amount,
166                    min_order,
167                    max_order,
168                    fee_rate,
169                    nonces.order_nonce,
170                );
171                Ok(FillOrder::NEO(neo_order))
172            }
173        }
174    }
175
176    /// Create a signed blockchain payload in the format expected by GraphQL when
177    /// given `nonces` and a `Client` as `signer`. FIXME: handle other chains
178    pub fn blockchain_signatures(
179        &self,
180        signer: &Signer,
181        nonces: &[PayloadNonces],
182        order_precision: u32,
183        fee_precision: u32
184    ) -> Result<LimitBlockchainSignatures> {
185        let mut order_payloads = Vec::new();
186        let blockchains = self.market.blockchains();
187        for blockchain in blockchains {
188            let pub_key = signer.child_public_key(blockchain)?;
189            for nonce_group in nonces {
190                let fill_order = self.make_fill_order(blockchain, &pub_key, nonce_group, order_precision)?;
191                order_payloads.push(Some(fill_order.to_blockchain_signature(signer, order_precision, fee_precision)?))
192            }
193        }
194        Ok(order_payloads)
195    }
196
197    /// Create a GraphQL request with everything filled in besides blockchain order payloads
198    /// and signatures (for both the overall request and blockchain payloads)
199    pub fn graphql_request(
200        &self,
201        current_time: i64,
202        affiliate: Option<String>,
203    ) -> Result<place_limit_order::Variables> {
204        let cancel_at = match self.cancellation_policy {
205            OrderCancellationPolicy::GoodTilTime(time) => Some(format!("{:?}", time)),
206            _ => None,
207        };
208        let order_args = place_limit_order::Variables {
209            payload: place_limit_order::PlaceLimitOrderParams {
210                client_order_id: self.client_order_id.clone(),
211                allow_taker: self.allow_taker,
212                buy_or_sell: self.buy_or_sell.into(),
213                cancel_at: cancel_at,
214                cancellation_policy: self.cancellation_policy.into(),
215                market_name: self.market.market_name(),
216                amount: self.me_amount.clone().try_into()?,
217                // These two nonces are deprecated...
218                nonce_from: 1234,
219                nonce_to: 1234,
220                nonce_order: (current_time as u32) as i64, // 4146194029, // Fixme: what do we validate on this?
221                timestamp: current_time,
222                limit_price: place_limit_order::CurrencyPriceParams {
223                    // This format is confusing, but prices are always in
224                    // B for an A/B market, so reverse the normal thing
225                    currency_a: self.market.asset_b.asset.name().to_string(),
226                    currency_b: self.market.asset_a.asset.name().to_string(),
227                    amount: self.me_rate.to_bigdecimal()?.to_string(),
228                },
229                blockchain_signatures: vec![],
230            },
231            affiliate,
232            signature: RequestPayloadSignature::empty().into(),
233        };
234        Ok(order_args)
235    }
236
237    pub fn sign_graphql_request(
238        &self,
239        mut variables: place_limit_order::Variables,
240        nonces: Vec<PayloadNonces>,
241        signer: &Signer,
242        order_precision: u32,
243        fee_precision: u32) -> Result<place_limit_order::Variables>
244    {
245        let client_order_id = variables.payload.client_order_id;
246        variables.payload.client_order_id = None;
247        let bc_sigs = self.blockchain_signatures(signer, &nonces, order_precision, fee_precision)?;
248        variables.payload.blockchain_signatures = bc_sigs;
249        // now compute overall request payload signature
250        let canonical_string = limit_order_canonical_string(&variables)?;
251        let sig: place_limit_order::Signature =
252            signer.sign_canonical_string(&canonical_string).into();
253        variables.signature = sig;
254        variables.payload.client_order_id = client_order_id;
255        Ok(variables)
256    }
257
258    /// Create a signed GraphQL request with blockchain payloads that can be submitted
259    /// to Nash
260    pub fn signed_graphql_request(
261        &self,
262        nonces: Vec<PayloadNonces>,
263        current_time: i64,
264        affiliate: Option<String>,
265        signer: &Signer,
266        order_precision: u32,
267        fee_precision: u32
268    ) -> Result<LimitOrderMutation> {
269        let request = self.sign_graphql_request(self.graphql_request(current_time, affiliate)?, nonces, signer, order_precision, fee_precision)?;
270        Ok(graphql::PlaceLimitOrder::build_query(request))
271    }
272
273    // Construct payload nonces with source as `from` asset name and destination as
274    // `to` asset name. Nonces will be retrieved from current values in `State`
275    pub async fn make_payload_nonces(
276        &self,
277        state: Arc<RwLock<State>>,
278        current_time: i64,
279    ) -> Result<Vec<PayloadNonces>> {
280        let state = state.read().await;
281        let asset_nonces = state.asset_nonces.as_ref()
282            .ok_or(ProtocolError("Asset nonce map does not exist"))?;
283        let (from, to) = match self.buy_or_sell {
284            BuyOrSell::Buy => (
285                self.market.asset_b.asset.name(),
286                self.market.asset_a.asset.name(),
287            ),
288            BuyOrSell::Sell => (
289                self.market.asset_a.asset.name(),
290                self.market.asset_b.asset.name(),
291            ),
292        };
293        let nonce_froms: Vec<Nonce> = asset_nonces
294            .get(from)
295            .ok_or(ProtocolError("Asset nonce for source does not exist"))?
296            .iter()
297            .map(|nonce| Nonce::Value(*nonce))
298            .collect();
299        let nonce_tos: Vec<Nonce> = asset_nonces
300            .get(to)
301            .ok_or(ProtocolError(
302                "Asset nonce for destination a does not exist",
303            ))?
304            .iter()
305            .map(|nonce| Nonce::Value(*nonce))
306            .collect();
307        let mut nonce_combinations = Vec::new();
308        for nonce_from in &nonce_froms {
309            for nonce_to in &nonce_tos {
310                nonce_combinations.push(PayloadNonces {
311                    nonce_from: *nonce_from,
312                    nonce_to: *nonce_to,
313                    order_nonce: Nonce::Value(current_time as u32),
314                })
315            }
316        }
317        Ok(nonce_combinations)
318    }
319}
320
321impl MarketOrderConstructor {
322    /// Helper to transform a limit order into signed fillorder data on every blockchain
323    pub fn make_fill_order(
324        &self,
325        chain: Blockchain,
326        pub_key: &PublicKey,
327        nonces: &PayloadNonces,
328    ) -> Result<FillOrder> {
329        // Rate is in "dest per source", so a higher rate is always beneficial to a user
330        // Here we insure the minimum rate is the rate they specified
331        let min_order = Rate::MinOrderRate;
332        let max_order = Rate::MaxOrderRate;
333        // Amount is specified in the "source" asset
334        let amount = self.source.amount.clone();
335        let fee_rate = Rate::MinOrderRate; // 0
336
337        match chain {
338            Blockchain::Ethereum => Ok(FillOrder::Ethereum(eth::FillOrder::new(
339                pub_key.to_address()?.try_into()?,
340                self.source.asset.into(),
341                self.destination.into(),
342                map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
343                map_crosschain(nonces.nonce_to, chain, self.destination.into()),
344                amount,
345                min_order,
346                max_order,
347                fee_rate,
348                nonces.order_nonce,
349            ))),
350            Blockchain::Bitcoin => Ok(FillOrder::Bitcoin(btc::FillOrder::new(
351                map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
352                map_crosschain(nonces.nonce_to, chain, self.destination.into()),
353            ))),
354            Blockchain::NEO => {
355                // FIXME: this can still be improved...
356                let neo_pub_key: NeoPublicKey = pub_key.clone().try_into()?;
357                let neo_order = neo::FillOrder::new(
358                    neo_pub_key,
359                    self.source.asset.into(),
360                    self.destination.into(),
361                    map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
362                    map_crosschain(nonces.nonce_to, chain, self.destination.into()),
363                    amount,
364                    min_order,
365                    max_order,
366                    fee_rate,
367                    nonces.order_nonce,
368                );
369                Ok(FillOrder::NEO(neo_order))
370            }
371        }
372    }
373
374    /// Create a signed blockchain payload in the format expected by GraphQL when
375    /// given `nonces` and a `Client` as `signer`. FIXME: handle other chains
376    pub fn blockchain_signatures(
377        &self,
378        signer: &Signer,
379        nonces: &[PayloadNonces],
380        order_precision: u32,
381        fee_precision: u32
382    ) -> Result<MarketBlockchainSignatures> {
383        let mut order_payloads = Vec::new();
384        let blockchains = self.market.blockchains();
385        for blockchain in blockchains {
386            let pub_key = signer.child_public_key(blockchain)?;
387            for nonce_group in nonces {
388                let fill_order = self.make_fill_order(blockchain, &pub_key, nonce_group)?;
389                order_payloads.push(Some(fill_order.to_market_blockchain_signature(signer, order_precision, fee_precision)?))
390            }
391        }
392        Ok(order_payloads)
393    }
394
395    /// Create a GraphQL request with everything filled in besides blockchain order payloads
396    /// and signatures (for both the overall request and blockchain payloads)
397    pub fn graphql_request(
398        &self,
399        current_time: i64,
400        affiliate: Option<String>,
401    ) -> Result<place_market_order::Variables> {
402        let order_args = place_market_order::Variables {
403            payload: place_market_order::PlaceMarketOrderParams {
404                buy_or_sell: BuyOrSell::Sell.into(),
405                client_order_id: self.client_order_id.clone(),
406                market_name: self.market.market_name(),
407                amount: self.me_amount.clone().try_into()?,
408                // These two nonces are deprecated...
409                nonce_from: Some(0),
410                nonce_to: Some(0),
411                nonce_order: (current_time as u32) as i64, // 4146194029, // Fixme: what do we validate on this?
412                timestamp: current_time,
413                blockchain_signatures: vec![],
414            },
415            affiliate,
416            signature: RequestPayloadSignature::empty().into(),
417        };
418        Ok(order_args)
419    }
420
421    pub fn sign_graphql_request(
422        &self,
423        mut variables: place_market_order::Variables,
424        nonces: Vec<PayloadNonces>,
425        signer: &Signer,
426        order_precision: u32,
427        fee_precision: u32
428    ) -> Result<place_market_order::Variables> {
429        // compute and add blockchain signatures
430        let bc_sigs = self.blockchain_signatures(signer, &nonces, order_precision, fee_precision)?;
431        variables.payload.blockchain_signatures = bc_sigs;
432        // now compute overall request payload signature
433        let canonical_string = market_order_canonical_string(&variables)?;
434        let sig: place_market_order::Signature =
435            signer.sign_canonical_string(&canonical_string).into();
436        variables.signature = sig;
437        Ok(variables)
438    }
439
440    /// Create a signed GraphQL request with blockchain payloads that can be submitted
441    /// to Nash
442    pub fn signed_graphql_request(
443        &self,
444        nonces: Vec<PayloadNonces>,
445        current_time: i64,
446        affiliate: Option<String>,
447        signer: &Signer,
448        order_precision: u32,
449        fee_precision: u32
450    ) -> Result<MarketOrderMutation> {
451        let request = self.sign_graphql_request(self.graphql_request(current_time, affiliate)?, nonces, signer, order_precision, fee_precision)?;
452        Ok(graphql::PlaceMarketOrder::build_query(request))
453    }
454
455    // Construct payload nonces with source as `from` asset name and destination as
456    // `to` asset name. Nonces will be retrieved from current values in `State`
457    pub async fn make_payload_nonces(
458        &self,
459        state: Arc<RwLock<State>>,
460        current_time: i64,
461    ) -> Result<Vec<PayloadNonces>> {
462        let state = state.read().await;
463        let asset_nonces = state.asset_nonces.as_ref()
464            .ok_or(ProtocolError("Asset nonce map does not exist"))?;
465        let (from, to) = (
466            self.market.asset_a.asset.name(),
467            self.market.asset_b.asset.name(),
468        );
469        let nonce_froms: Vec<Nonce> = asset_nonces
470            .get(from)
471            .ok_or(ProtocolError("Asset nonce for source does not exist"))?
472            .iter()
473            .map(|nonce| Nonce::Value(*nonce))
474            .collect();
475        let nonce_tos: Vec<Nonce> = asset_nonces
476            .get(to)
477            .ok_or(ProtocolError(
478                "Asset nonce for destination a does not exist",
479            ))?
480            .iter()
481            .map(|nonce| Nonce::Value(*nonce))
482            .collect();
483        let mut nonce_combinations = Vec::new();
484        for nonce_from in &nonce_froms {
485            for nonce_to in &nonce_tos {
486                nonce_combinations.push(PayloadNonces {
487                    nonce_from: *nonce_from,
488                    nonce_to: *nonce_to,
489                    order_nonce: Nonce::Value(current_time as u32),
490                })
491            }
492        }
493        Ok(nonce_combinations)
494    }
495}
496
497pub fn limit_order_canonical_string(variables: &place_limit_order::Variables) -> Result<String> {
498    let serialized_all = serde_json::to_string(variables).map_err(|_|ProtocolError("Failed to serialize limit order into canonical string"))?;
499
500    Ok(general_canonical_string(
501        "place_limit_order".to_string(),
502        serde_json::from_str(&serialized_all).map_err(|_|ProtocolError("Failed to deserialize limit order into canonical string"))?,
503        vec!["blockchain_signatures".to_string()],
504    ))
505}
506
507pub fn market_order_canonical_string(variables: &place_market_order::Variables) -> Result<String> {
508    let serialized_all = serde_json::to_string(variables).map_err(|_|ProtocolError("Failed to serialize market order into canonical string"))?;
509
510    Ok(general_canonical_string(
511        "place_market_order".to_string(),
512        serde_json::from_str(&serialized_all).map_err(|_|ProtocolError("Failed to deserialize market order into canonical string"))?,
513        vec!["blockchain_signatures".to_string()],
514    ))
515}
516
517impl Into<place_market_order::OrderBuyOrSell> for BuyOrSell {
518    fn into(self) -> place_market_order::OrderBuyOrSell {
519        match self {
520            BuyOrSell::Buy => place_market_order::OrderBuyOrSell::BUY,
521            BuyOrSell::Sell => place_market_order::OrderBuyOrSell::SELL,
522        }
523    }
524}
525
526impl From<RequestPayloadSignature> for place_market_order::Signature {
527    fn from(sig: RequestPayloadSignature) -> Self {
528        place_market_order::Signature {
529            signed_digest: sig.signed_digest,
530            public_key: sig.public_key,
531        }
532    }
533}
534
535impl TryInto<place_market_order::CurrencyAmountParams> for AssetAmount {
536    type Error = ProtocolError;
537    fn try_into(self) -> Result<place_market_order::CurrencyAmountParams> {
538        Ok(place_market_order::CurrencyAmountParams {
539            amount: pad_zeros(
540                &self.amount.to_bigdecimal().to_string(),
541                self.amount.precision,
542            )?,
543            // FIXME: asset.asset is ugly
544            currency: self.asset.asset.name().to_string(),
545        })
546    }
547}
548
549
550
551impl Into<place_limit_order::OrderBuyOrSell> for BuyOrSell {
552    fn into(self) -> place_limit_order::OrderBuyOrSell {
553        match self {
554            BuyOrSell::Buy => place_limit_order::OrderBuyOrSell::BUY,
555            BuyOrSell::Sell => place_limit_order::OrderBuyOrSell::SELL,
556        }
557    }
558}
559
560impl From<RequestPayloadSignature> for place_limit_order::Signature {
561    fn from(sig: RequestPayloadSignature) -> Self {
562        place_limit_order::Signature {
563            signed_digest: sig.signed_digest,
564            public_key: sig.public_key,
565        }
566    }
567}
568
569impl From<OrderCancellationPolicy> for place_limit_order::OrderCancellationPolicy {
570    fn from(policy: OrderCancellationPolicy) -> Self {
571        match policy {
572            OrderCancellationPolicy::FillOrKill => {
573                place_limit_order::OrderCancellationPolicy::FILL_OR_KILL
574            }
575            OrderCancellationPolicy::GoodTilCancelled => {
576                place_limit_order::OrderCancellationPolicy::GOOD_TIL_CANCELLED
577            }
578            OrderCancellationPolicy::GoodTilTime(_) => {
579                place_limit_order::OrderCancellationPolicy::GOOD_TIL_TIME
580            }
581            OrderCancellationPolicy::ImmediateOrCancel => {
582                place_limit_order::OrderCancellationPolicy::IMMEDIATE_OR_CANCEL
583            }
584        }
585    }
586}
587
588impl TryInto<place_limit_order::CurrencyAmountParams> for AssetAmount {
589    type Error = ProtocolError;
590    fn try_into(self) -> Result<place_limit_order::CurrencyAmountParams> {
591        Ok(place_limit_order::CurrencyAmountParams {
592            amount: pad_zeros(
593                &self.amount.to_bigdecimal().to_string(),
594                self.amount.precision,
595            )?,
596            // FIXME: asset.asset is ugly
597            currency: self.asset.asset.name().to_string(),
598        })
599    }
600}