use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
use crate::decimal::UnsignedDecimal;
macro_rules! newtype_id {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct $name(String);
impl $name {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for $name {
fn from(s: String) -> Self {
Self(s)
}
}
impl From<&str> for $name {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl std::ops::Deref for $name {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
};
}
newtype_id!(
MarketSymbol
);
newtype_id!(
MarketId
);
newtype_id!(
OrderId
);
newtype_id!(
TradeAccountId
);
newtype_id!(
AssetId
);
fn deserialize_string_or_u64<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
struct StringOrU64;
impl<'de> de::Visitor<'de> for StringOrU64 {
type Value = u64;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a u64 or a string containing a u64")
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<u64, E> {
Ok(v)
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<u64, E> {
v.parse().map_err(de::Error::custom)
}
}
deserializer.deserialize_any(StringOrU64)
}
fn deserialize_optional_string_or_number<'de, D>(
deserializer: D,
) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let value: Option<serde_json::Value> = Option::deserialize(deserializer)?;
match value {
Some(serde_json::Value::String(s)) => Ok(Some(s)),
Some(serde_json::Value::Number(n)) => Ok(Some(n.to_string())),
Some(serde_json::Value::Null) | None => Ok(None),
Some(v) => Ok(Some(v.to_string())),
}
}
#[derive(Debug, Clone)]
pub enum Side {
Buy,
Sell,
}
impl Side {
pub fn as_str(&self) -> &str {
match self {
Side::Buy => "Buy",
Side::Sell => "Sell",
}
}
}
#[derive(Debug, Clone)]
pub enum OrderType {
Spot,
Market,
FillOrKill,
PostOnly,
Limit {
price: UnsignedDecimal,
timestamp: u64,
},
BoundedMarket {
max_price: UnsignedDecimal,
min_price: UnsignedDecimal,
},
}
#[derive(Debug, Clone)]
pub enum Action {
CreateOrder {
side: Side,
price: UnsignedDecimal,
quantity: UnsignedDecimal,
order_type: OrderType,
},
CancelOrder {
order_id: OrderId,
},
SettleBalance,
RegisterReferer {
to: Identity,
},
}
impl OrderType {
pub fn to_encoding(
&self,
market: &Market,
) -> (crate::encoding::OrderTypeEncoding, serde_json::Value) {
use crate::encoding::OrderTypeEncoding;
match self {
OrderType::Spot => (OrderTypeEncoding::Spot, serde_json::json!("Spot")),
OrderType::Market => (OrderTypeEncoding::Market, serde_json::json!("Market")),
OrderType::FillOrKill => (
OrderTypeEncoding::FillOrKill,
serde_json::json!("FillOrKill"),
),
OrderType::PostOnly => (OrderTypeEncoding::PostOnly, serde_json::json!("PostOnly")),
OrderType::Limit { price, timestamp } => {
let scaled_price = market.scale_price(price);
(
OrderTypeEncoding::Limit {
price: scaled_price,
timestamp: *timestamp,
},
serde_json::json!({ "Limit": [scaled_price.to_string(), timestamp.to_string()] }),
)
}
OrderType::BoundedMarket {
max_price,
min_price,
} => {
let scaled_max = market.scale_price(max_price);
let scaled_min = market.scale_price(min_price);
(
OrderTypeEncoding::BoundedMarket {
max_price: scaled_max,
min_price: scaled_min,
},
serde_json::json!({ "BoundedMarket": { "max_price": scaled_max.to_string(), "min_price": scaled_min.to_string() } }),
)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum Identity {
Address(String),
ContractId(String),
}
impl Identity {
pub fn address_value(&self) -> &str {
match self {
Identity::Address(a) => a,
Identity::ContractId(c) => c,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Signature {
Secp256k1(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketAsset {
pub symbol: String,
pub asset: AssetId,
pub decimals: u32,
pub max_precision: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Market {
pub contract_id: String,
pub market_id: MarketId,
pub maker_fee: String,
pub taker_fee: String,
pub min_order: String,
pub dust: String,
#[serde(deserialize_with = "deserialize_string_or_u64")]
pub price_window: u64,
pub base: MarketAsset,
pub quote: MarketAsset,
}
impl Market {
pub fn format_price(&self, chain_value: u64) -> UnsignedDecimal {
let d = Decimal::from(chain_value) / Decimal::from(10u64.pow(self.quote.decimals));
UnsignedDecimal::new(d).unwrap()
}
pub fn scale_price(&self, human_value: &UnsignedDecimal) -> u64 {
let factor = Decimal::from(10u64.pow(self.quote.decimals));
let scaled = (*human_value.inner() * factor)
.floor()
.to_string()
.parse::<u64>()
.unwrap_or(0);
let truncate_factor = 10u64.pow(self.quote.decimals - self.quote.max_precision);
(scaled / truncate_factor) * truncate_factor
}
pub fn format_quantity(&self, chain_value: u64) -> UnsignedDecimal {
let d = Decimal::from(chain_value) / Decimal::from(10u64.pow(self.base.decimals));
UnsignedDecimal::new(d).unwrap()
}
pub fn scale_quantity(&self, human_value: &UnsignedDecimal) -> u64 {
let factor = Decimal::from(10u64.pow(self.base.decimals));
let scaled = (*human_value.inner() * factor)
.floor()
.to_string()
.parse::<u64>()
.unwrap_or(0);
let truncate_factor = 10u64.pow(self.base.decimals - self.base.max_precision);
(scaled / truncate_factor) * truncate_factor
}
pub fn symbol_pair(&self) -> MarketSymbol {
MarketSymbol::new(format!("{}/{}", self.base.symbol, self.quote.symbol))
}
pub fn adjust_quantity(&self, price: u64, quantity: u64) -> u64 {
let base_factor = 10u128.pow(self.base.decimals);
let product = price as u128 * quantity as u128;
let remainder = product % base_factor;
if remainder == 0 {
return quantity;
}
let adjusted_product = product - remainder;
(adjusted_product / price as u128) as u64
}
pub fn validate_order(&self, price: u64, quantity: u64) -> Result<(), String> {
let base_factor = 10u128.pow(self.base.decimals);
let quote_value = (price as u128 * quantity as u128) / base_factor;
let min_order: u128 = self.min_order.parse().unwrap_or(0);
if quote_value < min_order {
return Err(format!(
"Quote value {} below min_order {}",
quote_value, min_order
));
}
if (price as u128 * quantity as u128) % base_factor != 0 {
return Err("FractionalPrice: (price * quantity) % 10^base_decimals != 0".into());
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketsResponse {
pub books_registry_id: Option<String>,
pub accounts_registry_id: Option<String>,
pub trade_account_oracle_id: Option<String>,
pub chain_id: Option<String>,
pub base_asset_id: Option<String>,
pub markets: Vec<Market>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketSummary {
pub market_id: Option<String>,
pub high: Option<String>,
pub low: Option<String>,
pub volume: Option<String>,
pub price_change: Option<String>,
pub last_price: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketTicker {
pub market_id: Option<String>,
pub best_bid: Option<String>,
pub best_ask: Option<String>,
pub last_price: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeAccount {
pub last_modification: Option<u64>,
pub nonce: Option<String>,
pub owner: Option<Identity>,
#[serde(default)]
pub synced_with_network: Option<bool>,
#[serde(default)]
pub sync_state: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccountResponse {
pub trade_account_id: Option<TradeAccountId>,
pub trade_account: Option<TradeAccount>,
pub session: Option<SessionInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
pub session_id: Option<Identity>,
pub expiry: Option<String>,
pub contract_ids: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateAccountResponse {
pub trade_account_id: Option<TradeAccountId>,
pub nonce: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionRequest {
pub contract_id: String,
pub session_id: Identity,
pub signature: Signature,
pub contract_ids: Vec<String>,
pub nonce: String,
pub expiry: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionResponse {
pub tx_id: Option<String>,
pub trade_account_id: Option<String>,
pub contract_ids: Option<Vec<String>>,
pub session_id: Option<Identity>,
pub session_expiry: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Session {
pub owner_address: [u8; 32],
pub session_private_key: [u8; 32],
pub session_address: [u8; 32],
pub trade_account_id: TradeAccountId,
pub contract_ids: Vec<String>,
pub expiry: u64,
pub nonce: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub order_id: Option<OrderId>,
pub side: Option<String>,
pub order_type: Option<serde_json::Value>,
#[serde(default, deserialize_with = "deserialize_optional_string_or_number")]
pub quantity: Option<String>,
#[serde(default, deserialize_with = "deserialize_optional_string_or_number")]
pub quantity_fill: Option<String>,
#[serde(default, deserialize_with = "deserialize_optional_string_or_number")]
pub price: Option<String>,
#[serde(default, deserialize_with = "deserialize_optional_string_or_number")]
pub price_fill: Option<String>,
pub timestamp: Option<serde_json::Value>,
pub close: Option<bool>,
pub partially_filled: Option<bool>,
pub cancel: Option<bool>,
#[serde(default)]
pub desired_quantity: Option<serde_json::Value>,
#[serde(default)]
pub base_decimals: Option<u32>,
#[serde(default)]
pub account: Option<Identity>,
#[serde(default)]
pub fill: Option<serde_json::Value>,
#[serde(default)]
pub order_tx_history: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub market_id: Option<MarketId>,
#[serde(default)]
pub owner: Option<Identity>,
#[serde(default)]
pub history: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub fills: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrdersResponse {
pub identity: Option<Identity>,
pub market_id: Option<String>,
pub orders: Option<Vec<Order>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Trade {
pub trade_id: Option<String>,
pub side: Option<String>,
pub total: Option<String>,
pub quantity: Option<String>,
pub price: Option<String>,
pub timestamp: Option<String>,
#[serde(default)]
pub maker: Option<Identity>,
#[serde(default)]
pub taker: Option<Identity>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradesResponse {
pub trades: Option<Vec<Trade>>,
pub market_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBookBalance {
pub locked: Option<String>,
pub unlocked: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceResponse {
pub order_books: Option<HashMap<String, OrderBookBalance>>,
pub total_locked: Option<String>,
pub total_unlocked: Option<String>,
pub trading_account_balance: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepthLevel {
pub price: String,
pub quantity: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepthSnapshot {
pub buys: Option<Vec<DepthLevel>>,
pub sells: Option<Vec<DepthLevel>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DepthUpdate {
pub action: Option<String>,
pub changes: Option<DepthSnapshot>,
#[serde(alias = "view")]
pub view: Option<DepthSnapshot>,
pub market_id: Option<String>,
pub onchain_timestamp: Option<String>,
pub seen_timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bar {
pub open: Option<String>,
pub high: Option<String>,
pub low: Option<String>,
pub close: Option<String>,
pub volume: Option<String>,
pub timestamp: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateOrderAction {
pub side: String,
pub price: String,
pub quantity: String,
pub order_type: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CancelOrderAction {
pub order_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettleBalanceAction {
pub to: Identity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ActionItem {
CreateOrder {
#[serde(rename = "CreateOrder")]
create_order: CreateOrderAction,
},
CancelOrder {
#[serde(rename = "CancelOrder")]
cancel_order: CancelOrderAction,
},
SettleBalance {
#[serde(rename = "SettleBalance")]
settle_balance: SettleBalanceAction,
},
RegisterReferer {
#[serde(rename = "RegisterReferer")]
register_referer: SettleBalanceAction,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct MarketActions {
pub market_id: MarketId,
pub actions: Vec<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SessionActionsRequest {
pub actions: Vec<MarketActions>,
pub signature: Signature,
pub nonce: String,
pub trade_account_id: TradeAccountId,
pub session_id: Identity,
#[serde(skip_serializing_if = "Option::is_none")]
pub collect_orders: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variable_outputs: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionActionsResponse {
pub tx_id: Option<String>,
pub orders: Option<Vec<Order>>,
pub code: Option<u32>,
pub message: Option<String>,
pub reason: Option<String>,
pub receipts: Option<serde_json::Value>,
}
impl SessionActionsResponse {
pub fn is_success(&self) -> bool {
self.tx_id.is_some()
}
pub fn is_preflight_error(&self) -> bool {
self.code.is_some() && self.tx_id.is_none()
}
pub fn is_onchain_error(&self) -> bool {
self.message.is_some() && self.code.is_none() && self.tx_id.is_none()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WithdrawRequest {
pub trade_account_id: TradeAccountId,
pub signature: Signature,
pub nonce: String,
pub to: Identity,
pub asset_id: AssetId,
pub amount: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WithdrawResponse {
pub tx_id: Option<String>,
pub code: Option<u32>,
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhitelistRequest {
#[serde(rename = "tradeAccount")]
pub trade_account: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhitelistResponse {
pub success: Option<bool>,
#[serde(rename = "tradeAccount")]
pub trade_account: Option<String>,
#[serde(rename = "alreadyWhitelisted")]
pub already_whitelisted: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FaucetResponse {
pub message: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReferralInfo {
pub valid: Option<bool>,
#[serde(rename = "ownerAddress")]
pub owner_address: Option<String>,
#[serde(rename = "isActive")]
pub is_active: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AggregatedAsset {
pub id: Option<String>,
pub symbol: Option<String>,
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AggregatedOrderbook {
pub asks: Option<Vec<Vec<String>>>,
pub bids: Option<Vec<Vec<String>>>,
pub timestamp: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PairSummary {
pub trading_pairs: Option<String>,
pub last_price: Option<String>,
pub lowest_ask: Option<String>,
pub highest_bid: Option<String>,
pub base_volume: Option<String>,
pub quote_volume: Option<String>,
pub price_change_percent_24h: Option<String>,
pub highest_price_24h: Option<String>,
pub lowest_price_24h: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PairTicker {
pub ticker_id: Option<String>,
pub base_currency: Option<String>,
pub target_currency: Option<String>,
pub last_price: Option<String>,
pub base_volume: Option<String>,
pub target_volume: Option<String>,
pub bid: Option<String>,
pub ask: Option<String>,
pub high: Option<String>,
pub low: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderUpdate {
pub action: Option<String>,
pub orders: Option<Vec<Order>>,
pub onchain_timestamp: Option<String>,
pub seen_timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeUpdate {
pub action: Option<String>,
pub trades: Option<Vec<Trade>>,
pub market_id: Option<String>,
pub onchain_timestamp: Option<String>,
pub seen_timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceEntry {
pub identity: Option<Identity>,
pub asset_id: Option<String>,
pub total_locked: Option<String>,
pub total_unlocked: Option<String>,
pub trading_account_balance: Option<String>,
pub order_books: Option<HashMap<String, OrderBookBalance>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceUpdate {
pub action: Option<String>,
pub balance: Option<Vec<BalanceEntry>>,
pub onchain_timestamp: Option<String>,
pub seen_timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NonceUpdate {
pub action: Option<String>,
pub contract_id: Option<String>,
pub nonce: Option<String>,
pub onchain_timestamp: Option<String>,
pub seen_timestamp: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WsMessage {
pub action: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct TxResult {
pub tx_id: String,
pub orders: Vec<Order>,
}