use alloy_primitives::{Address, B256, U256};
use chrono::{DateTime, Utc};
use rust_decimal::prelude::ToPrimitive;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
pub type Price = u32;
pub type Qty = i64;
pub const SCALE_FACTOR: i64 = 10_000;
pub const MAX_PRICE_TICKS: Price = Price::MAX;
pub const MIN_PRICE_TICKS: Price = 1;
pub const MAX_QTY: Qty = Qty::MAX / 2;
pub fn decimal_to_price(decimal: Decimal) -> std::result::Result<Price, &'static str> {
let scaled = decimal * Decimal::from(SCALE_FACTOR);
let rounded = scaled.round();
let as_u64 = rounded.to_u64().ok_or("Price too large or negative")?;
if as_u64 < MIN_PRICE_TICKS as u64 {
return Ok(MIN_PRICE_TICKS); }
if as_u64 > MAX_PRICE_TICKS as u64 {
return Err("Price exceeds maximum");
}
Ok(as_u64 as Price)
}
pub fn price_to_decimal(ticks: Price) -> Decimal {
Decimal::from(ticks) / Decimal::from(SCALE_FACTOR)
}
pub fn decimal_to_qty(decimal: Decimal) -> std::result::Result<Qty, &'static str> {
let scaled = decimal * Decimal::from(SCALE_FACTOR);
let rounded = scaled.round();
let as_i64 = rounded.to_i64().ok_or("Quantity too large")?;
if as_i64.abs() > MAX_QTY {
return Err("Quantity exceeds maximum");
}
Ok(as_i64)
}
pub fn qty_to_decimal(units: Qty) -> Decimal {
Decimal::from(units) / Decimal::from(SCALE_FACTOR)
}
pub fn is_price_tick_aligned(decimal: Decimal, tick_size_decimal: Decimal) -> bool {
let tick_size_ticks = match decimal_to_price(tick_size_decimal) {
Ok(ticks) => ticks,
Err(_) => return false,
};
let price_ticks = match decimal_to_price(decimal) {
Ok(ticks) => ticks,
Err(_) => return false,
};
if tick_size_ticks == 0 {
return true;
}
price_ticks % tick_size_ticks == 0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[allow(clippy::upper_case_acronyms)]
pub enum Side {
BUY = 0,
SELL = 1,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TraderSide {
Taker,
Maker,
#[serde(untagged)]
Unknown(String),
}
impl Default for TraderSide {
fn default() -> Self {
Self::Unknown("UNKNOWN".to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TradeMessageStatus {
#[serde(alias = "matched", alias = "MATCHED")]
Matched,
#[serde(alias = "mined", alias = "MINED")]
Mined,
#[serde(alias = "confirmed", alias = "CONFIRMED")]
Confirmed,
#[serde(alias = "retrying", alias = "RETRYING")]
Retrying,
#[serde(alias = "failed", alias = "FAILED")]
Failed,
#[serde(untagged)]
Unknown(String),
}
impl Default for TradeMessageStatus {
fn default() -> Self {
Self::Unknown("UNKNOWN".to_string())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TradeMessageType {
#[serde(alias = "trade", alias = "TRADE")]
Trade,
#[serde(untagged)]
Unknown(String),
}
impl Side {
pub fn as_str(&self) -> &'static str {
match self {
Side::BUY => "BUY",
Side::SELL => "SELL",
}
}
pub fn opposite(&self) -> Self {
match self {
Side::BUY => Side::SELL,
Side::SELL => Side::BUY,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[allow(clippy::upper_case_acronyms)]
pub enum OrderType {
#[default]
GTC,
FOK,
FAK,
GTD,
}
impl OrderType {
pub fn as_str(&self) -> &'static str {
match self {
OrderType::GTC => "GTC",
OrderType::FOK => "FOK",
OrderType::FAK => "FAK",
OrderType::GTD => "GTD",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OrderStatus {
#[serde(rename = "LIVE")]
Live,
#[serde(rename = "CANCELLED")]
Cancelled,
#[serde(rename = "FILLED")]
Filled,
#[serde(rename = "PARTIAL")]
Partial,
#[serde(rename = "EXPIRED")]
Expired,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketSnapshot {
pub token_id: String,
pub market_id: String,
pub timestamp: DateTime<Utc>,
pub bid: Option<Decimal>,
pub ask: Option<Decimal>,
pub mid: Option<Decimal>,
pub spread: Option<Decimal>,
pub last_price: Option<Decimal>,
pub volume_24h: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookLevel {
#[serde(with = "rust_decimal::serde::str")]
pub price: Decimal,
#[serde(with = "rust_decimal::serde::str")]
pub size: Decimal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FastBookLevel {
pub price: Price, pub size: Qty, }
impl FastBookLevel {
pub fn new(price: Price, size: Qty) -> Self {
Self { price, size }
}
pub fn to_book_level(self) -> BookLevel {
BookLevel {
price: price_to_decimal(self.price),
size: qty_to_decimal(self.size),
}
}
pub fn from_book_level(level: &BookLevel) -> std::result::Result<Self, &'static str> {
let price = decimal_to_price(level.price)?;
let size = decimal_to_qty(level.size)?;
Ok(Self::new(price, size))
}
pub fn notional(self) -> i64 {
let price_i64 = self.price as i64;
(price_i64 * self.size) / SCALE_FACTOR
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderBook {
pub token_id: String,
pub timestamp: DateTime<Utc>,
pub bids: Vec<BookLevel>,
pub asks: Vec<BookLevel>,
pub sequence: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderDelta {
pub token_id: String,
pub timestamp: DateTime<Utc>,
pub side: Side,
pub price: Decimal,
pub size: Decimal, pub sequence: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FastOrderDelta {
pub token_id_hash: u64, pub timestamp: DateTime<Utc>,
pub side: Side,
pub price: Price, pub size: Qty, pub sequence: u64,
}
impl FastOrderDelta {
pub fn from_order_delta(
delta: &OrderDelta,
tick_size: Option<Decimal>,
) -> std::result::Result<Self, &'static str> {
if let Some(tick_size) = tick_size {
if !is_price_tick_aligned(delta.price, tick_size) {
return Err("Price not aligned to tick size");
}
}
let price = decimal_to_price(delta.price)?;
let size = decimal_to_qty(delta.size)?;
let token_id_hash = {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
delta.token_id.hash(&mut hasher);
hasher.finish()
};
Ok(Self {
token_id_hash,
timestamp: delta.timestamp,
side: delta.side,
price,
size,
sequence: delta.sequence,
})
}
pub fn to_order_delta(self, token_id: String) -> OrderDelta {
OrderDelta {
token_id,
timestamp: self.timestamp,
side: self.side,
price: price_to_decimal(self.price),
size: qty_to_decimal(self.size),
sequence: self.sequence,
}
}
pub fn is_removal(self) -> bool {
self.size == 0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FillEvent {
pub id: String,
pub order_id: String,
pub token_id: String,
pub side: Side,
pub price: Decimal,
pub size: Decimal,
pub timestamp: DateTime<Utc>,
pub maker_address: Address,
pub taker_address: Address,
pub fee: Decimal,
}
#[derive(Debug, Clone)]
pub struct OrderRequest {
pub token_id: String,
pub side: Side,
pub price: Decimal,
pub size: Decimal,
pub order_type: OrderType,
pub expiration: Option<DateTime<Utc>>,
pub client_id: Option<String>,
}
#[derive(Debug, Clone)]
pub struct MarketOrderRequest {
pub token_id: String,
pub side: Side,
pub amount: Decimal, pub slippage_tolerance: Option<Decimal>,
pub client_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Order {
pub id: String,
pub token_id: String,
pub side: Side,
pub price: Decimal,
pub original_size: Decimal,
pub filled_size: Decimal,
pub remaining_size: Decimal,
pub status: OrderStatus,
pub order_type: OrderType,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub expiration: Option<DateTime<Utc>>,
pub client_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ApiCredentials {
#[serde(rename = "apiKey")]
pub api_key: String,
pub secret: String,
pub passphrase: String,
}
#[derive(Debug, Clone)]
pub struct OrderOptions {
pub tick_size: Option<Decimal>,
pub neg_risk: Option<bool>,
pub fee_rate_bps: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct ExtraOrderArgs {
pub metadata: B256,
pub builder: B256,
}
impl Default for ExtraOrderArgs {
fn default() -> Self {
Self {
metadata: B256::ZERO,
builder: B256::ZERO,
}
}
}
#[derive(Debug, Clone)]
pub struct ExtraOrderArgsV1 {
pub fee_rate_bps: u32,
pub nonce: U256,
pub taker: String,
}
impl Default for ExtraOrderArgsV1 {
fn default() -> Self {
Self {
fee_rate_bps: 0,
nonce: U256::ZERO,
taker: "0x0000000000000000000000000000000000000000".to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct MarketOrderArgs {
pub token_id: String,
pub side: Side,
pub amount: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedOrderRequest {
pub salt: u64,
pub maker: String,
pub signer: String,
pub taker: String,
pub token_id: String,
pub maker_amount: String,
pub taker_amount: String,
pub side: String,
pub signature_type: u8,
pub timestamp: String,
pub expiration: String,
pub metadata: String,
pub builder: String,
pub signature: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PostOrder {
pub order: SignedOrderRequest,
pub owner: String,
pub order_type: OrderType,
pub defer_exec: bool,
pub post_only: bool,
}
impl PostOrder {
pub fn new(order: SignedOrderRequest, owner: String, order_type: OrderType) -> Self {
Self {
order,
owner,
order_type,
defer_exec: false,
post_only: false,
}
}
#[must_use]
pub fn with_flags(mut self, post_only: bool, defer_exec: bool) -> Self {
self.post_only = post_only;
self.defer_exec = defer_exec;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Market {
pub condition_id: String,
pub tokens: [Token; 2],
pub rewards: Rewards,
pub min_incentive_size: Option<String>,
pub max_incentive_spread: Option<String>,
pub active: bool,
pub closed: bool,
pub question_id: String,
pub minimum_order_size: Decimal,
pub minimum_tick_size: Decimal,
pub description: String,
pub category: Option<String>,
pub end_date_iso: Option<String>,
pub game_start_time: Option<String>,
pub question: String,
pub market_slug: String,
pub seconds_delay: Decimal,
pub icon: String,
pub fpmm: String,
#[serde(default)]
pub enable_order_book: bool,
#[serde(default)]
pub archived: bool,
#[serde(default)]
pub accepting_orders: bool,
#[serde(default)]
pub accepting_order_timestamp: Option<String>,
#[serde(default)]
pub maker_base_fee: Decimal,
#[serde(default)]
pub taker_base_fee: Decimal,
#[serde(default)]
pub notifications_enabled: bool,
#[serde(default)]
pub neg_risk: bool,
#[serde(default)]
pub neg_risk_market_id: String,
#[serde(default)]
pub neg_risk_request_id: String,
#[serde(default)]
pub image: String,
#[serde(default)]
pub is_50_50_outcome: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
pub token_id: String,
pub outcome: String,
pub price: Decimal,
#[serde(default)]
pub winner: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientConfig {
pub base_url: String,
pub chain_id: u64,
pub private_key: Option<String>,
pub api_credentials: Option<ApiCredentials>,
pub max_slippage: Option<Decimal>,
pub fee_rate: Option<Decimal>,
pub timeout: Option<std::time::Duration>,
pub max_connections: Option<usize>,
}
impl Default for ClientConfig {
fn default() -> Self {
Self {
base_url: "https://clob.polymarket.com".to_string(),
chain_id: 137, private_key: None,
api_credentials: None,
timeout: Some(std::time::Duration::from_secs(30)),
max_connections: Some(100),
max_slippage: None,
fee_rate: None,
}
}
}
pub type WssAuth = ApiCredentials;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WssSubscription {
#[serde(rename = "type")]
pub channel_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation: Option<String>,
#[serde(default)]
pub markets: Vec<String>,
#[serde(rename = "assets_ids", default)]
pub asset_ids: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_dump: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_feature_enabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<WssAuth>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event_type")]
pub enum StreamMessage {
#[serde(rename = "book")]
Book(BookUpdate),
#[serde(rename = "price_change")]
PriceChange(PriceChange),
#[serde(rename = "tick_size_change")]
TickSizeChange(TickSizeChange),
#[serde(rename = "last_trade_price")]
LastTradePrice(LastTradePrice),
#[serde(rename = "best_bid_ask")]
BestBidAsk(BestBidAsk),
#[serde(rename = "new_market")]
NewMarket(NewMarket),
#[serde(rename = "market_resolved")]
MarketResolved(MarketResolved),
#[serde(rename = "trade")]
Trade(TradeMessage),
#[serde(rename = "order")]
Order(OrderMessage),
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BookUpdate {
pub asset_id: String,
pub market: String,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub bids: Vec<OrderSummary>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub asks: Vec<OrderSummary>,
#[serde(default)]
pub hash: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceChange {
pub market: String,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub price_changes: Vec<PriceChangeEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceChangeEntry {
pub asset_id: String,
pub price: Decimal,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub size: Option<Decimal>,
pub side: Side,
#[serde(default)]
pub hash: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub best_bid: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub best_ask: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TickSizeChange {
pub asset_id: String,
pub market: String,
pub old_tick_size: Decimal,
pub new_tick_size: Decimal,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LastTradePrice {
pub asset_id: String,
pub market: String,
pub price: Decimal,
#[serde(default)]
pub side: Option<Side>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub size: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub fee_rate_bps: Option<Decimal>,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BestBidAsk {
pub market: String,
pub asset_id: String,
pub best_bid: Decimal,
pub best_ask: Decimal,
pub spread: Decimal,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewMarket {
pub id: String,
pub question: String,
pub market: String,
pub slug: String,
pub description: String,
#[serde(rename = "assets_ids", alias = "asset_ids")]
pub asset_ids: Vec<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub outcomes: Vec<String>,
#[serde(default)]
pub event_message: Option<EventMessage>,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketResolved {
pub id: String,
#[serde(default)]
pub question: Option<String>,
pub market: String,
#[serde(default)]
pub slug: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(rename = "assets_ids", alias = "asset_ids")]
pub asset_ids: Vec<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub outcomes: Vec<String>,
pub winning_asset_id: String,
pub winning_outcome: String,
#[serde(default)]
pub event_message: Option<EventMessage>,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventMessage {
pub id: String,
pub ticker: String,
pub slug: String,
pub title: String,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeMessage {
pub id: String,
pub market: String,
pub asset_id: String,
pub side: Side,
pub size: Decimal,
pub price: Decimal,
#[serde(default)]
pub status: TradeMessageStatus,
#[serde(rename = "type", default)]
pub msg_type: Option<TradeMessageType>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub last_update: Option<u64>,
#[serde(
default,
alias = "match_time",
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub matchtime: Option<u64>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub timestamp: Option<u64>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub trade_owner: Option<String>,
#[serde(default)]
pub taker_order_id: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub maker_orders: Vec<MakerOrder>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub fee_rate_bps: Option<Decimal>,
#[serde(default)]
pub transaction_hash: Option<String>,
#[serde(default)]
pub trader_side: Option<TraderSide>,
#[serde(default)]
pub bucket_index: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderMessage {
pub id: String,
pub market: String,
pub asset_id: String,
pub side: Side,
pub price: Decimal,
#[serde(rename = "type", default)]
pub msg_type: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub original_size: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub size_matched: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub timestamp: Option<u64>,
#[serde(default)]
pub associate_trades: Option<Vec<String>>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub order_owner: Option<String>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(default)]
pub created_at: Option<String>,
#[serde(default)]
pub expiration: Option<String>,
#[serde(default)]
pub order_type: Option<String>,
#[serde(default)]
pub maker_address: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subscription {
pub token_ids: Vec<String>,
pub channels: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum WssChannelType {
#[serde(rename = "USER")]
User,
#[serde(rename = "MARKET")]
Market,
}
impl WssChannelType {
pub fn as_str(&self) -> &'static str {
match self {
WssChannelType::User => "USER",
WssChannelType::Market => "MARKET",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Quote {
pub token_id: String,
pub side: Side,
#[serde(with = "rust_decimal::serde::str")]
pub price: Decimal,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Balance {
pub token_id: String,
pub available: Decimal,
pub locked: Decimal,
pub total: Decimal,
}
#[derive(Debug, Clone)]
pub struct Metrics {
pub orders_per_second: f64,
pub avg_latency_ms: f64,
pub error_rate: f64,
pub uptime_pct: f64,
}
pub type TokenId = String;
pub type OrderId = String;
pub type MarketId = String;
pub type ClientId = String;
#[derive(Debug, Clone)]
pub struct OpenOrderParams {
pub id: Option<String>,
pub asset_id: Option<String>,
pub market: Option<String>,
}
impl OpenOrderParams {
pub fn to_query_params(&self) -> Vec<(&str, &String)> {
let mut params = Vec::with_capacity(3);
if let Some(x) = &self.id {
params.push(("id", x));
}
if let Some(x) = &self.asset_id {
params.push(("asset_id", x));
}
if let Some(x) = &self.market {
params.push(("market", x));
}
params
}
}
#[derive(Debug, Clone)]
pub struct TradeParams {
pub id: Option<String>,
pub maker_address: Option<String>,
pub market: Option<String>,
pub asset_id: Option<String>,
pub before: Option<u64>,
pub after: Option<u64>,
}
impl TradeParams {
pub fn to_query_params(&self) -> Vec<(&str, String)> {
let mut params = Vec::with_capacity(6);
if let Some(x) = &self.id {
params.push(("id", x.clone()));
}
if let Some(x) = &self.asset_id {
params.push(("asset_id", x.clone()));
}
if let Some(x) = &self.market {
params.push(("market", x.clone()));
}
if let Some(x) = &self.maker_address {
params.push(("maker_address", x.clone()));
}
if let Some(x) = &self.before {
params.push(("before", x.to_string()));
}
if let Some(x) = &self.after {
params.push(("after", x.to_string()));
}
params
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenOrder {
pub associate_trades: Vec<String>,
pub id: String,
pub status: String,
pub market: String,
#[serde(with = "rust_decimal::serde::str")]
pub original_size: Decimal,
pub outcome: String,
pub maker_address: String,
pub owner: String,
#[serde(with = "rust_decimal::serde::str")]
pub price: Decimal,
pub side: Side,
#[serde(with = "rust_decimal::serde::str")]
pub size_matched: Decimal,
pub asset_id: String,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub expiration: u64,
#[serde(rename = "type", alias = "order_type", alias = "orderType", default)]
pub order_type: OrderType,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub created_at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostOrderResponse {
#[serde(default)]
pub error_msg: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
)]
pub making_amount: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
)]
pub taking_amount: Option<Decimal>,
#[serde(rename = "orderID")]
pub order_id: String,
#[serde(default)]
pub status: Option<String>,
pub success: bool,
#[serde(default, alias = "transactionsHashes")]
pub transaction_hashes: Vec<String>,
#[serde(default)]
pub trade_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelOrdersResponse {
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub canceled: Vec<String>,
#[serde(default, alias = "not_canceled")]
pub not_canceled: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TradeResponse {
pub id: String,
#[serde(default)]
pub taker_order_id: Option<String>,
pub market: String,
pub asset_id: String,
pub side: Side,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub size: Decimal,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub fee_rate_bps: Option<Decimal>,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub price: Decimal,
#[serde(default)]
pub status: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub match_time: Option<u64>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub last_update: Option<u64>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub bucket_index: Option<u64>,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub maker_address: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub maker_orders: Vec<MakerOrder>,
#[serde(default)]
pub transaction_hash: Option<String>,
#[serde(default)]
pub trader_side: TraderSide,
#[serde(default, alias = "err_msg")]
pub error_msg: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MakerOrder {
pub order_id: String,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub maker_address: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub matched_amount: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub price: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub fee_rate_bps: Option<Decimal>,
#[serde(default)]
pub asset_id: Option<String>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(default)]
pub side: Option<Side>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceAllowance {
pub asset_id: String,
#[serde(with = "rust_decimal::serde::str")]
pub balance: Decimal,
#[serde(with = "rust_decimal::serde::str")]
pub allowance: Decimal,
}
#[derive(Default)]
pub struct BalanceAllowanceParams {
pub asset_type: Option<AssetType>,
pub token_id: Option<String>,
pub signature_type: Option<u8>,
}
impl BalanceAllowanceParams {
pub fn to_query_params(&self) -> Vec<(&str, String)> {
let mut params = Vec::with_capacity(3);
if let Some(x) = &self.asset_type {
params.push(("asset_type", x.to_string()));
}
if let Some(x) = &self.token_id {
params.push(("token_id", x.to_string()));
}
if let Some(x) = &self.signature_type {
params.push(("signature_type", x.to_string()));
}
params
}
pub fn set_signature_type(&mut self, s: u8) {
self.signature_type = Some(s);
}
}
#[allow(clippy::upper_case_acronyms)]
pub enum AssetType {
COLLATERAL,
CONDITIONAL,
}
impl std::fmt::Display for AssetType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AssetType::COLLATERAL => write!(f, "COLLATERAL"),
AssetType::CONDITIONAL => write!(f, "CONDITIONAL"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationParams {
pub signature: String,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchMidpointRequest {
pub token_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchMidpointResponse {
pub midpoints: std::collections::HashMap<String, Option<Decimal>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchPriceRequest {
pub token_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenPrice {
pub token_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bid: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ask: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mid: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchPriceResponse {
pub prices: Vec<TokenPrice>,
}
#[derive(Debug, Deserialize)]
pub struct ApiKeysResponse {
#[serde(rename = "apiKeys")]
pub api_keys: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct MidpointResponse {
#[serde(with = "rust_decimal::serde::str")]
pub mid: Decimal,
}
#[derive(Debug, Deserialize)]
pub struct PriceResponse {
#[serde(with = "rust_decimal::serde::str")]
pub price: Decimal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PricesHistoryInterval {
OneMinute,
OneHour,
SixHours,
OneDay,
OneWeek,
}
impl PricesHistoryInterval {
pub const fn as_str(self) -> &'static str {
match self {
Self::OneMinute => "1m",
Self::OneHour => "1h",
Self::SixHours => "6h",
Self::OneDay => "1d",
Self::OneWeek => "1w",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceHistoryPoint {
pub t: i64,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub p: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricesHistoryResponse {
pub history: Vec<PriceHistoryPoint>,
}
#[derive(Debug, Deserialize)]
pub struct SpreadResponse {
pub spread: Decimal,
}
#[derive(Debug, Deserialize)]
pub struct TickSizeResponse {
pub minimum_tick_size: Decimal,
}
#[derive(Debug, Deserialize)]
pub struct NegRiskResponse {
pub neg_risk: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderScoringResponse {
pub scoring: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BookParams {
pub token_id: String,
pub side: Side,
}
#[derive(Debug, Deserialize)]
pub struct OrderBookSummary {
pub market: String,
pub asset_id: String,
#[serde(default)]
pub hash: Option<String>,
#[serde(deserialize_with = "crate::decode::deserializers::number_from_string")]
pub timestamp: u64,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub bids: Vec<OrderSummary>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub asks: Vec<OrderSummary>,
pub min_order_size: Decimal,
pub neg_risk: bool,
pub tick_size: Decimal,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
)]
pub last_trade_price: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderSummary {
#[serde(with = "rust_decimal::serde::str")]
pub price: Decimal,
#[serde(with = "rust_decimal::serde::str")]
pub size: Decimal,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MarketsResponse {
pub limit: usize,
pub count: usize,
pub next_cursor: Option<String>,
pub data: Vec<Market>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SimplifiedMarketsResponse {
pub limit: usize,
pub count: usize,
pub next_cursor: Option<String>,
pub data: Vec<SimplifiedMarket>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SimplifiedMarket {
pub condition_id: String,
pub tokens: [Token; 2],
pub rewards: Rewards,
pub min_incentive_size: Option<String>,
pub max_incentive_spread: Option<String>,
pub active: bool,
pub closed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Rewards {
pub rates: Option<serde_json::Value>,
pub min_size: Decimal,
pub max_spread: Decimal,
#[serde(default)]
pub event_start_date: Option<String>,
#[serde(default)]
pub event_end_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub in_game_multiplier: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub reward_epoch: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeRateResponse {
pub base_fee: u32,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqCreateRequest {
pub asset_in: String,
pub asset_out: String,
pub amount_in: String,
pub amount_out: String,
pub user_type: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqCreateRequestResponse {
pub request_id: String,
pub expiry: u64,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqCancelRequest {
pub request_id: String,
}
#[derive(Debug, Clone, Default)]
pub struct RfqRequestsParams {
pub offset: Option<String>,
pub limit: Option<u32>,
pub state: Option<String>,
pub request_ids: Vec<String>,
pub markets: Vec<String>,
pub size_min: Option<Decimal>,
pub size_max: Option<Decimal>,
pub size_usdc_min: Option<Decimal>,
pub size_usdc_max: Option<Decimal>,
pub price_min: Option<Decimal>,
pub price_max: Option<Decimal>,
pub sort_by: Option<String>,
pub sort_dir: Option<String>,
}
impl RfqRequestsParams {
pub fn to_query_params(&self) -> Vec<(String, String)> {
let mut params = Vec::new();
if let Some(x) = &self.offset {
params.push(("offset".to_string(), x.clone()));
}
if let Some(x) = self.limit {
params.push(("limit".to_string(), x.to_string()));
}
if let Some(x) = &self.state {
params.push(("state".to_string(), x.clone()));
}
for x in &self.request_ids {
params.push(("requestIds[]".to_string(), x.clone()));
}
for x in &self.markets {
params.push(("markets[]".to_string(), x.clone()));
}
if let Some(x) = self.size_min {
params.push(("sizeMin".to_string(), x.to_string()));
}
if let Some(x) = self.size_max {
params.push(("sizeMax".to_string(), x.to_string()));
}
if let Some(x) = self.size_usdc_min {
params.push(("sizeUsdcMin".to_string(), x.to_string()));
}
if let Some(x) = self.size_usdc_max {
params.push(("sizeUsdcMax".to_string(), x.to_string()));
}
if let Some(x) = self.price_min {
params.push(("priceMin".to_string(), x.to_string()));
}
if let Some(x) = self.price_max {
params.push(("priceMax".to_string(), x.to_string()));
}
if let Some(x) = &self.sort_by {
params.push(("sortBy".to_string(), x.clone()));
}
if let Some(x) = &self.sort_dir {
params.push(("sortDir".to_string(), x.clone()));
}
params
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqRequestData {
pub request_id: String,
pub user_address: String,
pub proxy_address: String,
pub condition: String,
pub token: String,
pub complement: String,
pub side: Side,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub size_in: Decimal,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub size_out: Decimal,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub price: Decimal,
pub state: String,
pub expiry: u64,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqCreateQuote {
pub request_id: String,
pub asset_in: String,
pub asset_out: String,
pub amount_in: String,
pub amount_out: String,
pub user_type: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqCreateQuoteResponse {
pub quote_id: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqCancelQuote {
pub quote_id: String,
}
#[derive(Debug, Clone, Default)]
pub struct RfqQuotesParams {
pub offset: Option<String>,
pub limit: Option<u32>,
pub state: Option<String>,
pub quote_ids: Vec<String>,
pub request_ids: Vec<String>,
pub markets: Vec<String>,
pub size_min: Option<Decimal>,
pub size_max: Option<Decimal>,
pub size_usdc_min: Option<Decimal>,
pub size_usdc_max: Option<Decimal>,
pub price_min: Option<Decimal>,
pub price_max: Option<Decimal>,
pub sort_by: Option<String>,
pub sort_dir: Option<String>,
}
impl RfqQuotesParams {
pub fn to_query_params(&self) -> Vec<(String, String)> {
let mut params = Vec::new();
if let Some(x) = &self.offset {
params.push(("offset".to_string(), x.clone()));
}
if let Some(x) = self.limit {
params.push(("limit".to_string(), x.to_string()));
}
if let Some(x) = &self.state {
params.push(("state".to_string(), x.clone()));
}
for x in &self.quote_ids {
params.push(("quoteIds[]".to_string(), x.clone()));
}
for x in &self.request_ids {
params.push(("requestIds[]".to_string(), x.clone()));
}
for x in &self.markets {
params.push(("markets[]".to_string(), x.clone()));
}
if let Some(x) = self.size_min {
params.push(("sizeMin".to_string(), x.to_string()));
}
if let Some(x) = self.size_max {
params.push(("sizeMax".to_string(), x.to_string()));
}
if let Some(x) = self.size_usdc_min {
params.push(("sizeUsdcMin".to_string(), x.to_string()));
}
if let Some(x) = self.size_usdc_max {
params.push(("sizeUsdcMax".to_string(), x.to_string()));
}
if let Some(x) = self.price_min {
params.push(("priceMin".to_string(), x.to_string()));
}
if let Some(x) = self.price_max {
params.push(("priceMax".to_string(), x.to_string()));
}
if let Some(x) = &self.sort_by {
params.push(("sortBy".to_string(), x.clone()));
}
if let Some(x) = &self.sort_dir {
params.push(("sortDir".to_string(), x.clone()));
}
params
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqQuoteData {
pub quote_id: String,
pub request_id: String,
pub user_address: String,
pub proxy_address: String,
pub condition: String,
pub token: String,
pub complement: String,
pub side: Side,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub size_in: Decimal,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub size_out: Decimal,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub price: Decimal,
pub match_type: String,
pub state: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RfqListResponse<T> {
pub data: Vec<T>,
pub next_cursor: Option<String>,
pub limit: u32,
pub count: u32,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqOrderExecutionRequest {
pub request_id: String,
pub quote_id: String,
pub maker: String,
pub signer: String,
pub taker: String,
pub expiration: u64,
pub nonce: String,
pub fee_rate_bps: String,
pub side: String,
pub token_id: String,
pub maker_amount: String,
pub taker_amount: String,
pub signature_type: u8,
pub signature: String,
pub salt: u64,
pub owner: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RfqApproveOrderResponse {
pub trade_ids: Vec<String>,
}
pub type ClientResult<T> = anyhow::Result<T>;
pub type Result<T> = std::result::Result<T, crate::errors::PolyfillError>;
pub type ApiCreds = ApiCredentials;
pub type CreateOrderOptions = OrderOptions;
pub type OrderArgs = OrderRequest;
#[cfg(test)]
mod tests {
use super::{
OrderMessage, OrderType, PostOrder, SignedOrderRequest, TradeMessage, TradeMessageStatus,
};
#[test]
fn test_order_type_fak_serde_and_string() {
assert_eq!(OrderType::FAK.as_str(), "FAK");
let json = serde_json::to_string(&OrderType::FAK).unwrap();
assert_eq!(json, "\"FAK\"");
let parsed: OrderType = serde_json::from_str("\"FAK\"").unwrap();
assert_eq!(parsed, OrderType::FAK);
}
#[test]
fn test_post_order_v2_json_shape() {
let order = SignedOrderRequest {
salt: 42,
maker: "0xAbC0000000000000000000000000000000000001".to_string(),
signer: "0xAbC0000000000000000000000000000000000001".to_string(),
taker: "0x0000000000000000000000000000000000000000".to_string(),
token_id: "123456789".to_string(),
maker_amount: "100000000".to_string(),
taker_amount: "50000000".to_string(),
side: "BUY".to_string(),
signature_type: 0,
timestamp: "1700000000000".to_string(),
expiration: "0".to_string(),
metadata: "0x0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
builder: "0x0000000000000000000000000000000000000000000000000000000000000000"
.to_string(),
signature: "0xdeadbeef".to_string(),
};
let body = PostOrder::new(order, "owner-uuid".to_string(), OrderType::GTC);
let v = serde_json::to_value(&body).unwrap();
let obj = v.as_object().expect("PostOrder must serialize as object");
assert!(obj.contains_key("order"));
assert!(obj.contains_key("owner"));
assert!(obj.contains_key("orderType"));
assert!(obj.contains_key("deferExec"));
assert!(obj.contains_key("postOnly"));
assert_eq!(obj["deferExec"], serde_json::Value::Bool(false));
assert_eq!(obj["postOnly"], serde_json::Value::Bool(false));
assert_eq!(obj["orderType"], serde_json::json!("GTC"));
let order = obj["order"].as_object().expect("order must be object");
for key in [
"salt",
"maker",
"signer",
"taker",
"tokenId",
"makerAmount",
"takerAmount",
"side",
"signatureType",
"timestamp",
"expiration",
"metadata",
"builder",
"signature",
] {
assert!(order.contains_key(key), "missing {key}");
}
assert!(!order.contains_key("nonce"), "V2 must not include nonce");
assert!(
!order.contains_key("feeRateBps"),
"V2 must not include feeRateBps"
);
}
#[test]
fn test_post_order_with_flags_sets_post_only_and_defer_exec() {
let order = SignedOrderRequest {
salt: 1,
maker: "0x0".to_string(),
signer: "0x0".to_string(),
taker: "0x0".to_string(),
token_id: "1".to_string(),
maker_amount: "0".to_string(),
taker_amount: "0".to_string(),
side: "BUY".to_string(),
signature_type: 0,
timestamp: "0".to_string(),
expiration: "0".to_string(),
metadata: "0x0".to_string(),
builder: "0x0".to_string(),
signature: "0x0".to_string(),
};
let body = PostOrder::new(order, "o".to_string(), OrderType::GTC).with_flags(true, true);
assert!(body.post_only);
assert!(body.defer_exec);
}
#[test]
fn test_trade_message_status_retrying_failed() {
let s: TradeMessageStatus = serde_json::from_str("\"RETRYING\"").unwrap();
assert_eq!(s, TradeMessageStatus::Retrying);
let s: TradeMessageStatus = serde_json::from_str("\"FAILED\"").unwrap();
assert_eq!(s, TradeMessageStatus::Failed);
let s: TradeMessageStatus = serde_json::from_str("\"retrying\"").unwrap();
assert_eq!(s, TradeMessageStatus::Retrying);
let s: TradeMessageStatus = serde_json::from_str("\"failed\"").unwrap();
assert_eq!(s, TradeMessageStatus::Failed);
}
#[test]
fn test_trade_message_with_bucket_index() {
let json_with = serde_json::json!({
"id": "t1",
"market": "0xmkt",
"asset_id": "123",
"side": "BUY",
"size": "1.0",
"price": "0.5",
"bucket_index": 42
});
let tm: TradeMessage = serde_json::from_value(json_with).unwrap();
assert_eq!(tm.bucket_index, Some(42));
let json_without = serde_json::json!({
"id": "t2",
"market": "0xmkt",
"asset_id": "123",
"side": "BUY",
"size": "1.0",
"price": "0.5"
});
let tm: TradeMessage = serde_json::from_value(json_without).unwrap();
assert_eq!(tm.bucket_index, None);
}
#[test]
fn test_order_message_v2_fields() {
let json = serde_json::json!({
"id": "o1",
"market": "0xmkt",
"asset_id": "123",
"side": "BUY",
"price": "0.5",
"owner": "owner-uuid",
"expiration": "1700000000",
"order_type": "GTD",
"maker_address": "0xabc0000000000000000000000000000000000001"
});
let om: OrderMessage = serde_json::from_value(json).unwrap();
assert_eq!(om.owner.as_deref(), Some("owner-uuid"));
assert_eq!(om.expiration.as_deref(), Some("1700000000"));
assert_eq!(om.order_type.as_deref(), Some("GTD"));
assert_eq!(
om.maker_address.as_deref(),
Some("0xabc0000000000000000000000000000000000001")
);
}
}