#[cfg(all(feature = "onchain-cosmos", feature = "grpc"))]
pub use inner::*;
#[cfg(all(feature = "onchain-cosmos", feature = "grpc"))]
mod inner {
use cosmrs::crypto::secp256k1::SigningKey;
use cosmrs::tx::{self, Body, Fee, SignDoc, SignerInfo};
use cosmrs::Any;
use prost::Message as ProstMessage;
use crate::core::types::ExchangeError;
use super::super::proto::dydxprotocol::{
MsgPlaceOrder, MsgCancelOrder, Order, OrderId, SubaccountId,
OrderSide, OrderConditionType,
ORDER_FLAG_SHORT_TERM, ORDER_FLAG_LONG_TERM, ORDER_FLAG_CONDITIONAL,
};
#[allow(unused_imports)]
pub use super::super::proto::dydxprotocol::OrderTimeInForce;
const TYPE_URL_PLACE_ORDER: &str = "/dydxprotocol.clob.MsgPlaceOrder";
const TYPE_URL_CANCEL_ORDER: &str = "/dydxprotocol.clob.MsgCancelOrder";
#[derive(Debug, Clone)]
pub struct ShortTermOrderParams {
pub owner_address: String,
pub subaccount_number: u32,
pub client_id: u32,
pub clob_pair_id: u32,
pub is_buy: bool,
pub quantums: u64,
pub subticks: u64,
pub good_til_block: u32,
pub time_in_force: i32,
pub reduce_only: u32,
}
#[derive(Debug, Clone)]
pub struct LongTermOrderParams {
pub owner_address: String,
pub subaccount_number: u32,
pub client_id: u32,
pub clob_pair_id: u32,
pub is_buy: bool,
pub quantums: u64,
pub subticks: u64,
pub good_til_block_time: u32,
pub time_in_force: i32,
pub reduce_only: u32,
}
#[derive(Debug, Clone)]
pub struct ConditionalOrderParams {
pub owner_address: String,
pub subaccount_number: u32,
pub client_id: u32,
pub clob_pair_id: u32,
pub is_buy: bool,
pub quantums: u64,
pub subticks: u64,
pub good_til_block_time: u32,
pub time_in_force: i32,
pub reduce_only: u32,
pub condition_type: OrderConditionType,
pub trigger_subticks: u64,
}
#[derive(Debug, Clone)]
pub struct CancelOrderParams {
pub owner_address: String,
pub subaccount_number: u32,
pub client_id: u32,
pub clob_pair_id: u32,
pub order_flags: u32,
pub good_til_block: Option<u32>,
pub good_til_block_time: Option<u32>,
}
fn encode_as_any<M: ProstMessage>(type_url: &str, msg: &M) -> Any {
Any {
type_url: type_url.to_string(),
value: msg.encode_to_vec(),
}
}
pub fn build_place_order_tx(
params: &ShortTermOrderParams,
signing_key: &SigningKey,
account_number: u64,
sequence: u64,
chain_id: &str,
fee: Option<Fee>,
) -> Result<Vec<u8>, ExchangeError> {
let msg = MsgPlaceOrder {
order: Some(Order {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: params.owner_address.clone(),
number: params.subaccount_number,
}),
client_id: params.client_id,
order_flags: ORDER_FLAG_SHORT_TERM,
clob_pair_id: params.clob_pair_id,
}),
side: if params.is_buy {
OrderSide::Buy as i32
} else {
OrderSide::Sell as i32
},
quantums: params.quantums,
subticks: params.subticks,
good_til_block: Some(params.good_til_block),
good_til_block_time: None,
time_in_force: params.time_in_force,
reduce_only: params.reduce_only,
client_metadata: 0,
condition_type: OrderConditionType::Unspecified as i32,
conditional_order_trigger_subticks: 0,
}),
};
let any = encode_as_any(TYPE_URL_PLACE_ORDER, &msg);
build_and_sign_tx(any, signing_key, account_number, sequence, chain_id, fee)
}
pub fn build_place_long_term_order_tx(
params: &LongTermOrderParams,
signing_key: &SigningKey,
account_number: u64,
sequence: u64,
chain_id: &str,
fee: Option<Fee>,
) -> Result<Vec<u8>, ExchangeError> {
let msg = MsgPlaceOrder {
order: Some(Order {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: params.owner_address.clone(),
number: params.subaccount_number,
}),
client_id: params.client_id,
order_flags: ORDER_FLAG_LONG_TERM,
clob_pair_id: params.clob_pair_id,
}),
side: if params.is_buy {
OrderSide::Buy as i32
} else {
OrderSide::Sell as i32
},
quantums: params.quantums,
subticks: params.subticks,
good_til_block: None,
good_til_block_time: Some(params.good_til_block_time),
time_in_force: params.time_in_force,
reduce_only: params.reduce_only,
client_metadata: 0,
condition_type: OrderConditionType::Unspecified as i32,
conditional_order_trigger_subticks: 0,
}),
};
let any = encode_as_any(TYPE_URL_PLACE_ORDER, &msg);
build_and_sign_tx(any, signing_key, account_number, sequence, chain_id, fee)
}
pub fn build_place_conditional_order_tx(
params: &ConditionalOrderParams,
signing_key: &SigningKey,
account_number: u64,
sequence: u64,
chain_id: &str,
fee: Option<Fee>,
) -> Result<Vec<u8>, ExchangeError> {
let msg = MsgPlaceOrder {
order: Some(Order {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: params.owner_address.clone(),
number: params.subaccount_number,
}),
client_id: params.client_id,
order_flags: ORDER_FLAG_CONDITIONAL,
clob_pair_id: params.clob_pair_id,
}),
side: if params.is_buy {
OrderSide::Buy as i32
} else {
OrderSide::Sell as i32
},
quantums: params.quantums,
subticks: params.subticks,
good_til_block: None,
good_til_block_time: Some(params.good_til_block_time),
time_in_force: params.time_in_force,
reduce_only: params.reduce_only,
client_metadata: 0,
condition_type: params.condition_type as i32,
conditional_order_trigger_subticks: params.trigger_subticks,
}),
};
let any = encode_as_any(TYPE_URL_PLACE_ORDER, &msg);
build_and_sign_tx(any, signing_key, account_number, sequence, chain_id, fee)
}
pub fn build_cancel_order_tx(
params: &CancelOrderParams,
signing_key: &SigningKey,
account_number: u64,
sequence: u64,
chain_id: &str,
fee: Option<Fee>,
) -> Result<Vec<u8>, ExchangeError> {
let msg = MsgCancelOrder {
order_id: Some(OrderId {
subaccount_id: Some(SubaccountId {
owner: params.owner_address.clone(),
number: params.subaccount_number,
}),
client_id: params.client_id,
order_flags: params.order_flags,
clob_pair_id: params.clob_pair_id,
}),
good_til_block: params.good_til_block,
good_til_block_time: params.good_til_block_time,
};
let any = encode_as_any(TYPE_URL_CANCEL_ORDER, &msg);
build_and_sign_tx(any, signing_key, account_number, sequence, chain_id, fee)
}
fn build_and_sign_tx(
msg: Any,
signing_key: &SigningKey,
account_number: u64,
sequence: u64,
chain_id: &str,
fee: Option<Fee>,
) -> Result<Vec<u8>, ExchangeError> {
let body = Body::new(vec![msg], "", 0u32);
let public_key = signing_key.public_key();
let signer_info = SignerInfo::single_direct(Some(public_key.into()), sequence);
let fee = fee.unwrap_or_else(|| {
Fee::from_amount_and_gas(
cosmrs::Coin {
denom: "adydx".parse().expect("'adydx' is a valid Cosmos denom"),
amount: 0u128,
},
0u64,
)
});
let auth_info = signer_info.auth_info(fee);
let chain_id_parsed: cosmrs::tendermint::chain::Id = chain_id.parse().map_err(|e| {
ExchangeError::InvalidRequest(format!(
"DydxTxBuilder: invalid chain_id '{}': {}",
chain_id, e
))
})?;
let sign_doc = SignDoc::new(&body, &auth_info, &chain_id_parsed, account_number)
.map_err(|e| {
ExchangeError::InvalidRequest(format!(
"DydxTxBuilder: SignDoc construction failed: {}",
e
))
})?;
let raw: tx::Raw = sign_doc.sign(signing_key).map_err(|e| {
ExchangeError::Auth(format!(
"DydxTxBuilder: signing failed: {}",
e
))
})?;
let tx_bytes = raw.to_bytes().map_err(|e| {
ExchangeError::InvalidRequest(format!(
"DydxTxBuilder: TxRaw serialisation failed: {}",
e
))
})?;
Ok(tx_bytes)
}
pub fn signing_key_from_bytes(key_bytes: &[u8]) -> Result<SigningKey, ExchangeError> {
SigningKey::from_slice(key_bytes).map_err(|e| {
ExchangeError::Auth(format!(
"DydxTxBuilder: invalid signing key bytes: {}",
e
))
})
}
#[cfg(test)]
mod tests {
use super::*;
fn test_signing_key() -> SigningKey {
let key_bytes = [1u8; 32];
SigningKey::from_slice(&key_bytes).expect("test key is valid")
}
#[test]
fn test_build_place_order_tx_produces_bytes() {
let params = ShortTermOrderParams {
owner_address: "dydx1eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeepkfq2m".to_string(),
subaccount_number: 0,
client_id: 1,
clob_pair_id: 0, is_buy: true,
quantums: 1_000_000,
subticks: 30_000_000_000,
good_til_block: 100,
time_in_force: OrderTimeInForce::Unspecified as i32,
reduce_only: 0,
};
let key = test_signing_key();
let result = build_place_order_tx(
¶ms,
&key,
42, 7, "dydx-mainnet-1",
None, );
assert!(result.is_ok(), "tx build should succeed: {:?}", result.err());
let bytes = result.unwrap();
assert!(!bytes.is_empty(), "serialised tx must not be empty");
}
#[test]
fn test_build_cancel_order_tx_produces_bytes() {
let params = CancelOrderParams {
owner_address: "dydx1eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeepkfq2m".to_string(),
subaccount_number: 0,
client_id: 1,
clob_pair_id: 0,
order_flags: ORDER_FLAG_SHORT_TERM,
good_til_block: Some(100),
good_til_block_time: None,
};
let key = test_signing_key();
let result = build_cancel_order_tx(
¶ms,
&key,
42,
8,
"dydx-mainnet-1",
None,
);
assert!(result.is_ok(), "cancel tx build should succeed: {:?}", result.err());
let bytes = result.unwrap();
assert!(!bytes.is_empty(), "serialised cancel tx must not be empty");
}
#[test]
fn test_build_place_long_term_order_tx_produces_bytes() {
let params = LongTermOrderParams {
owner_address: "dydx1eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeepkfq2m".to_string(),
subaccount_number: 0,
client_id: 2,
clob_pair_id: 1, is_buy: false,
quantums: 5_000_000,
subticks: 2_000_000_000,
good_til_block_time: 1_800_000_000u32,
time_in_force: OrderTimeInForce::Unspecified as i32,
reduce_only: 0,
};
let key = test_signing_key();
let result = build_place_long_term_order_tx(
¶ms,
&key,
42,
9,
"dydx-mainnet-1",
None,
);
assert!(result.is_ok(), "long-term tx build should succeed: {:?}", result.err());
}
#[test]
fn test_invalid_chain_id_returns_error() {
let params = ShortTermOrderParams {
owner_address: "dydx1eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeepkfq2m".to_string(),
subaccount_number: 0,
client_id: 1,
clob_pair_id: 0,
is_buy: true,
quantums: 1_000_000,
subticks: 30_000_000_000,
good_til_block: 100,
time_in_force: 0,
reduce_only: 0,
};
let key = test_signing_key();
let result = build_place_order_tx(¶ms, &key, 42, 7, "", None);
assert!(result.is_err(), "empty chain ID should produce an error");
}
#[test]
fn test_signing_key_from_bytes() {
let key_bytes = [2u8; 32];
let key = signing_key_from_bytes(&key_bytes);
assert!(key.is_ok(), "valid 32-byte key should parse");
}
#[test]
fn test_signing_key_from_invalid_bytes() {
let key_bytes = [1u8; 31];
let key = signing_key_from_bytes(&key_bytes);
assert!(key.is_err(), "31-byte key should fail");
}
}
}