use crate::errors::{ProtocolError, Result};
use crate::graphql;
use crate::graphql::place_limit_order;
use crate::types::neo::PublicKey as NeoPublicKey;
use crate::types::PublicKey;
use crate::types::{
Asset, AssetAmount, Blockchain, BuyOrSell, Nonce, OrderCancellationPolicy, OrderRate, Rate,
};
use crate::utils::pad_zeros;
use graphql_client::GraphQLQuery;
use std::convert::TryInto;
use super::super::signer::Signer;
use super::super::{general_canonical_string, RequestPayloadSignature, State};
use super::blockchain::{btc, eth, neo, FillOrder};
use super::types::{LimitOrderConstructor, LimitOrderRequest, PayloadNonces};
use futures::lock::Mutex;
use std::sync::Arc;
type LimitOrderMutation = graphql_client::QueryBody<place_limit_order::Variables>;
type BlockchainSignatures = Vec<Option<place_limit_order::BlockchainSignature>>;
impl LimitOrderRequest {
pub fn make_constructor(&self) -> Result<LimitOrderConstructor> {
let amount_of_a = self.market.asset_a.with_amount(&self.amount)?;
let b_per_a: Rate = OrderRate::new(&self.price)?.into();
let a_per_b = b_per_a.invert_rate(None)?;
let amount_of_b = amount_of_a.exchange_at(&b_per_a, self.market.asset_b)?;
let (source, rate, destination) = match self.buy_or_sell {
BuyOrSell::Buy => {
(amount_of_b, a_per_b.clone(), self.market.asset_a)
}
BuyOrSell::Sell => {
(amount_of_a.clone(), b_per_a.clone(), self.market.asset_b)
}
};
Ok(LimitOrderConstructor {
me_amount: amount_of_a,
me_rate: b_per_a,
market: self.market,
buy_or_sell: self.buy_or_sell,
cancellation_policy: self.cancellation_policy,
allow_taker: self.allow_taker,
source,
destination,
rate,
})
}
}
fn map_crosschain(nonce: Nonce, chain: Blockchain, asset: Asset) -> Nonce {
if asset.blockchain() == chain {
nonce
} else {
Nonce::Crosschain
}
}
impl LimitOrderConstructor {
pub fn make_fill_order(
&self,
chain: Blockchain,
pub_key: &PublicKey,
nonces: &PayloadNonces,
) -> Result<FillOrder> {
let min_order = self.rate.clone();
let max_order = Rate::MaxOrderRate;
let amount = self.source.amount.clone();
let min_order = min_order
.subtract_fee(Rate::MaxFeeRate.to_bigdecimal()?)?
.into();
let fee_rate = Rate::MinFeeRate;
match chain {
Blockchain::Ethereum => Ok(FillOrder::Ethereum(eth::FillOrder::new(
pub_key.to_address()?.try_into()?,
self.source.asset.into(),
self.destination.into(),
map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
map_crosschain(nonces.nonce_to, chain, self.destination.into()),
amount,
min_order,
max_order,
fee_rate,
nonces.order_nonce,
))),
Blockchain::Bitcoin => Ok(FillOrder::Bitcoin(btc::FillOrder::new(
map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
map_crosschain(nonces.nonce_to, chain, self.destination.into()),
))),
Blockchain::NEO => {
let neo_pub_key: NeoPublicKey = pub_key.clone().try_into()?;
let neo_order = neo::FillOrder::new(
neo_pub_key,
self.source.asset.into(),
self.destination.into(),
map_crosschain(nonces.nonce_from, chain, self.source.asset.into()),
map_crosschain(nonces.nonce_to, chain, self.destination.into()),
amount,
min_order,
max_order,
fee_rate,
nonces.order_nonce,
);
Ok(FillOrder::NEO(neo_order))
}
}
}
pub fn blockchain_signatures(
&self,
signer: &mut Signer,
nonces: &[PayloadNonces],
) -> Result<BlockchainSignatures> {
let mut order_payloads = Vec::new();
let blockchains = self.market.blockchains();
for blockchain in blockchains {
let pub_key = signer.child_public_key(blockchain)?;
for nonce_group in nonces {
let fill_order = self.make_fill_order(blockchain, &pub_key, nonce_group)?;
order_payloads.push(Some(fill_order.to_blockchain_signature(signer)?))
}
}
Ok(order_payloads)
}
pub fn graphql_request(
&self,
current_time: i64,
affiliate: Option<String>,
) -> Result<place_limit_order::Variables> {
let cancel_at = match self.cancellation_policy {
OrderCancellationPolicy::GoodTilTime(time) => Some(format!("{:?}", time)),
_ => None,
};
let order_args = place_limit_order::Variables {
payload: place_limit_order::PlaceLimitOrderParams {
allow_taker: self.allow_taker,
buy_or_sell: self.buy_or_sell.into(),
cancel_at: cancel_at,
cancellation_policy: self.cancellation_policy.into(),
market_name: self.market.market_name(),
amount: self.me_amount.clone().try_into()?,
nonce_from: 1234,
nonce_to: 1234,
nonce_order: (current_time as u32) as i64,
timestamp: current_time,
limit_price: place_limit_order::CurrencyPriceParams {
currency_a: self.market.asset_b.asset.name().to_string(),
currency_b: self.market.asset_a.asset.name().to_string(),
amount: self.me_rate.to_bigdecimal()?.to_string(),
},
blockchain_signatures: vec![],
},
affiliate,
signature: RequestPayloadSignature::empty().into(),
};
Ok(order_args)
}
pub fn signed_graphql_request(
&self,
nonces: Vec<PayloadNonces>,
current_time: i64,
affiliate: Option<String>,
signer: &mut Signer,
) -> Result<LimitOrderMutation> {
let mut request = self.graphql_request(current_time, affiliate)?;
let bc_sigs = self.blockchain_signatures(signer, &nonces)?;
request.payload.blockchain_signatures = bc_sigs;
let canonical_string = limit_order_canonical_string(&request);
let sig: place_limit_order::Signature =
signer.sign_canonical_string(&canonical_string).into();
request.signature = sig;
Ok(graphql::PlaceLimitOrder::build_query(request))
}
pub async fn make_payload_nonces(
&self,
state: Arc<Mutex<State>>,
current_time: i64,
) -> Result<Vec<PayloadNonces>> {
let state = state.lock().await;
let asset_nonces = &state.asset_nonces;
let (from, to) = match self.buy_or_sell {
BuyOrSell::Buy => (
self.market.asset_b.asset.name(),
self.market.asset_a.asset.name(),
),
BuyOrSell::Sell => (
self.market.asset_a.asset.name(),
self.market.asset_b.asset.name(),
),
};
let nonce_froms: Vec<Nonce> = asset_nonces
.get(from)
.ok_or(ProtocolError("Asset nonce for source does not exist"))?
.iter()
.map(|nonce| Nonce::Value(*nonce))
.collect();
let nonce_tos: Vec<Nonce> = asset_nonces
.get(to)
.ok_or(ProtocolError(
"Asset nonce for destination a does not exist",
))?
.iter()
.map(|nonce| Nonce::Value(*nonce))
.collect();
let mut nonce_combinations = Vec::new();
for nonce_from in &nonce_froms {
for nonce_to in &nonce_tos {
nonce_combinations.push(PayloadNonces {
nonce_from: *nonce_from,
nonce_to: *nonce_to,
order_nonce: Nonce::Value(current_time as u32),
})
}
}
Ok(nonce_combinations)
}
}
pub fn limit_order_canonical_string(variables: &place_limit_order::Variables) -> String {
let serialized_all = serde_json::to_string(variables).unwrap();
general_canonical_string(
"place_limit_order".to_string(),
serde_json::from_str(&serialized_all).unwrap(),
vec!["blockchain_signatures".to_string()],
)
}
impl Into<place_limit_order::OrderBuyOrSell> for BuyOrSell {
fn into(self) -> place_limit_order::OrderBuyOrSell {
match self {
BuyOrSell::Buy => place_limit_order::OrderBuyOrSell::BUY,
BuyOrSell::Sell => place_limit_order::OrderBuyOrSell::SELL,
}
}
}
impl From<RequestPayloadSignature> for place_limit_order::Signature {
fn from(sig: RequestPayloadSignature) -> Self {
place_limit_order::Signature {
signed_digest: sig.signed_digest,
public_key: sig.public_key,
}
}
}
impl From<OrderCancellationPolicy> for place_limit_order::OrderCancellationPolicy {
fn from(policy: OrderCancellationPolicy) -> Self {
match policy {
OrderCancellationPolicy::FillOrKill => {
place_limit_order::OrderCancellationPolicy::FILL_OR_KILL
}
OrderCancellationPolicy::GoodTilCancelled => {
place_limit_order::OrderCancellationPolicy::GOOD_TIL_CANCELLED
}
OrderCancellationPolicy::GoodTilTime(_) => {
place_limit_order::OrderCancellationPolicy::GOOD_TIL_TIME
}
OrderCancellationPolicy::ImmediateOrCancel => {
place_limit_order::OrderCancellationPolicy::IMMEDIATE_OR_CANCEL
}
}
}
}
impl TryInto<place_limit_order::CurrencyAmountParams> for AssetAmount {
type Error = ProtocolError;
fn try_into(self) -> Result<place_limit_order::CurrencyAmountParams> {
Ok(place_limit_order::CurrencyAmountParams {
amount: pad_zeros(
&self.amount.to_bigdecimal().to_string(),
self.amount.precision,
)?,
currency: self.asset.asset.name().to_string(),
})
}
}