Skip to main content

clob_client_rust/
types.rs

1use serde::de::Deserializer;
2use serde::ser::Serializer;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Deserialize a value that may be a JSON string, number, or null into `Option<String>`.
7/// Handles Polymarket API inconsistencies where fields like `minimum_tick_size` may
8/// return as a number (0.01) or a string ("0.01") depending on the endpoint.
9fn deserialize_string_or_number<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
10where
11    D: Deserializer<'de>,
12{
13    let v: Option<serde_json::Value> = Option::deserialize(deserializer)?;
14    match v {
15        None | Some(serde_json::Value::Null) => Ok(None),
16        Some(serde_json::Value::String(s)) => Ok(Some(s)),
17        Some(serde_json::Value::Number(n)) => Ok(Some(n.to_string())),
18        Some(other) => Ok(Some(other.to_string())),
19    }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ApiKeyCreds {
24    pub key: String,
25    pub secret: String,
26    pub passphrase: String,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct ApiKeyRaw {
32    pub api_key: String,
33    pub secret: String,
34    pub passphrase: String,
35}
36
37/// Response returned by `/auth/readonly-api-key`.
38///
39/// TypeScript SDK returns `{ apiKey: string }`.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct ReadonlyApiKeyResponse {
43    pub api_key: String,
44}
45
46/// Request body for DELETE `/auth/readonly-api-key`.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct DeleteReadonlyApiKeyRequest {
50    pub key: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54#[serde(rename_all = "camelCase")]
55pub struct ApiKeysResponse {
56    pub api_keys: Vec<ApiKeyCreds>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct BanStatus {
61    pub closed_only: bool,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct BalanceAllowanceResponse {
66    pub balance: String,
67    pub allowance: String,
68}
69
70// --------------------------------------------------------------------------------------
71// RFQ Types (TypeScript parity)
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct CancelRfqRequestParams {
76    pub request_id: String,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct CreateRfqRequestParams {
82    pub asset_in: String,
83    pub asset_out: String,
84    pub amount_in: String,
85    pub amount_out: String,
86    pub user_type: u8,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct RfqQuoteParams {
92    pub request_id: String,
93    pub asset_in: String,
94    pub asset_out: String,
95    pub amount_in: String,
96    pub amount_out: String,
97    pub user_type: u8,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct CreateRfqQuoteParams {
103    pub request_id: String,
104    pub asset_in: String,
105    pub asset_out: String,
106    pub amount_in: String,
107    pub amount_out: String,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct CancelRfqQuoteParams {
113    pub quote_id: String,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct AcceptQuoteParams {
119    pub request_id: String,
120    pub quote_id: String,
121    pub expiration: u64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct ApproveOrderParams {
127    pub request_id: String,
128    pub quote_id: String,
129    pub expiration: u64,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, Default)]
133#[serde(rename_all = "camelCase")]
134pub struct GetRfqQuotesParams {
135    pub quote_ids: Option<Vec<String>>,
136    pub state: Option<String>,
137    pub markets: Option<Vec<String>>,
138    pub request_ids: Option<Vec<String>>,
139    pub size_min: Option<f64>,
140    pub size_max: Option<f64>,
141    pub size_usdc_min: Option<f64>,
142    pub size_usdc_max: Option<f64>,
143    pub price_min: Option<f64>,
144    pub price_max: Option<f64>,
145    pub sort_by: Option<String>,
146    pub sort_dir: Option<String>,
147    pub limit: Option<u32>,
148    pub offset: Option<String>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
152#[serde(rename_all = "camelCase")]
153pub struct GetRfqBestQuoteParams {
154    pub request_id: Option<String>,
155}
156
157/// RFQ user order input (not sent directly to API).
158#[derive(Debug, Clone)]
159pub struct RfqUserOrder {
160    pub token_id: String,
161    pub price: f64,
162    pub size: f64,
163    pub side: Side,
164}
165
166/// RFQ user quote input (not sent directly to API).
167#[derive(Debug, Clone)]
168pub struct RfqUserQuote {
169    pub request_id: String,
170    pub token_id: String,
171    pub price: f64,
172    pub size: f64,
173    pub side: Side,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, Default)]
177#[serde(rename_all = "camelCase")]
178pub struct GetRfqRequestsParams {
179    pub request_ids: Option<Vec<String>>,
180    pub state: Option<String>,
181    pub markets: Option<Vec<String>>,
182    pub size_min: Option<f64>,
183    pub size_max: Option<f64>,
184    pub size_usdc_min: Option<f64>,
185    pub size_usdc_max: Option<f64>,
186    pub price_min: Option<f64>,
187    pub price_max: Option<f64>,
188    pub sort_by: Option<String>,
189    pub sort_dir: Option<String>,
190    pub limit: Option<u32>,
191    pub offset: Option<String>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(rename_all = "snake_case")]
196pub struct RfqPaginatedResponse<T> {
197    pub data: Vec<T>,
198    pub next_cursor: String,
199    pub limit: u32,
200    pub count: u32,
201    pub total_count: Option<u32>,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
205#[serde(rename_all = "camelCase")]
206pub struct RfqRequest {
207    pub request_id: String,
208    pub user_address: String,
209    pub proxy_address: String,
210    pub token: String,
211    pub complement: String,
212    pub condition: String,
213    pub side: String,
214    pub size_in: String,
215    pub size_out: String,
216    pub price: f64,
217    pub accepted_quote_id: String,
218    pub state: String,
219    pub expiry: String,
220    pub created_at: String,
221    pub updated_at: String,
222}
223
224/// RFQ 匹配类型枚举
225/// - COMPLEMENTARY: BUY <> SELL 或 SELL <> BUY 的互补匹配
226/// - MERGE: 同向匹配(两个相同方向的订单合并)
227/// - MINT: 铸造匹配(创建新的头寸)
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(rename_all = "UPPERCASE")]
230pub enum RfqMatchType {
231    Complementary,
232    Merge,
233    Mint,
234}
235
236impl std::fmt::Display for RfqMatchType {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        match self {
239            RfqMatchType::Complementary => write!(f, "COMPLEMENTARY"),
240            RfqMatchType::Merge => write!(f, "MERGE"),
241            RfqMatchType::Mint => write!(f, "MINT"),
242        }
243    }
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct RfqQuote {
249    pub quote_id: String,
250    pub request_id: String,
251    pub user_address: String,
252    pub proxy_address: String,
253    pub complement: String,
254    pub condition: String,
255    pub token: String,
256    pub side: String,
257    pub size_in: String,
258    pub size_out: String,
259    pub price: f64,
260    pub state: String,
261    pub expiry: String,
262    /// 匹配类型:COMPLEMENTARY、MERGE 或 MINT
263    #[serde(default)]
264    pub match_type: Option<String>,
265    pub created_at: String,
266    pub updated_at: String,
267}
268
269pub type RfqRequestsResponse = RfqPaginatedResponse<RfqRequest>;
270pub type RfqQuotesResponse = RfqPaginatedResponse<RfqQuote>;
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct RfqRequestResponse {
275    pub request_id: String,
276    #[serde(default)]
277    pub error: Option<String>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
281#[serde(rename_all = "camelCase")]
282pub struct RfqQuoteResponse {
283    pub quote_id: String,
284    #[serde(default)]
285    pub error: Option<String>,
286}
287
288/// RFQ 请求订单创建载荷
289/// 用于根据 RFQ Quote 的 matchType 生成对应的订单参数
290#[derive(Debug, Clone)]
291pub struct RfqRequestOrderCreationPayload {
292    /// 代币 ID
293    pub token: String,
294    /// 交易方向
295    pub side: Side,
296    /// 交易数量(字符串格式)
297    pub size: String,
298    /// 价格(字符串格式,与 TypeScript SDK 一致)
299    pub price: String,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct OrderScoring {
304    pub scoring: bool,
305}
306
307pub type OrdersScoring = std::collections::HashMap<String, bool>;
308
309#[derive(Copy, Debug, Clone, Serialize, Deserialize, PartialEq)]
310#[serde(rename_all = "UPPERCASE")]
311pub enum Side {
312    BUY,
313    SELL,
314}
315
316#[derive(Copy, Debug, Clone, Serialize, Deserialize, PartialEq)]
317#[serde(rename_all = "UPPERCASE")]
318pub enum OrderType {
319    GTC,
320    FOK,
321    GTD,
322    FAK,
323}
324
325pub type TickSize = String; // e.g. "0.01"
326
327pub type TickSizes = std::collections::HashMap<String, TickSize>;
328pub type NegRisk = std::collections::HashMap<String, bool>;
329pub type FeeRates = std::collections::HashMap<String, u32>;
330
331/// Token info nested in Market response.
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
333pub struct MarketToken {
334    pub token_id: String,
335    pub outcome: String,
336    #[serde(default, deserialize_with = "deserialize_string_or_number")]
337    pub price: Option<String>,
338    #[serde(default)]
339    pub winner: Option<bool>,
340}
341
342/// Market metadata returned by `/markets` and `/markets/{condition_id}`.
343/// All fields use snake_case (no camelCase rename).
344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
345pub struct Market {
346    pub condition_id: String,
347    #[serde(default)]
348    pub question: Option<String>,
349    #[serde(default)]
350    pub question_id: Option<String>,
351    #[serde(default)]
352    pub description: Option<String>,
353    #[serde(default)]
354    pub market_slug: Option<String>,
355    #[serde(default)]
356    pub tokens: Vec<MarketToken>,
357    #[serde(default)]
358    pub enable_order_book: Option<bool>,
359    #[serde(default)]
360    pub active: Option<bool>,
361    #[serde(default)]
362    pub closed: Option<bool>,
363    #[serde(default)]
364    pub archived: Option<bool>,
365    #[serde(default)]
366    pub accepting_orders: Option<bool>,
367    #[serde(default)]
368    pub accepting_order_timestamp: Option<String>,
369    #[serde(default, deserialize_with = "deserialize_string_or_number")]
370    pub minimum_order_size: Option<String>,
371    #[serde(default, deserialize_with = "deserialize_string_or_number")]
372    pub minimum_tick_size: Option<String>,
373    #[serde(default)]
374    pub neg_risk: Option<bool>,
375    #[serde(default)]
376    pub neg_risk_market_id: Option<String>,
377    #[serde(default)]
378    pub neg_risk_request_id: Option<String>,
379    #[serde(default, deserialize_with = "deserialize_string_or_number")]
380    pub maker_base_fee: Option<String>,
381    #[serde(default, deserialize_with = "deserialize_string_or_number")]
382    pub taker_base_fee: Option<String>,
383    #[serde(default)]
384    pub notifications_enabled: Option<bool>,
385    #[serde(default)]
386    pub is_50_50_outcome: Option<bool>,
387    #[serde(default)]
388    pub icon: Option<String>,
389    #[serde(default)]
390    pub image: Option<String>,
391    #[serde(default)]
392    pub fpmm: Option<String>,
393    #[serde(default)]
394    pub end_date_iso: Option<String>,
395    #[serde(default)]
396    pub game_start_time: Option<String>,
397    #[serde(default)]
398    pub seconds_delay: Option<f64>,
399    #[serde(default)]
400    pub tags: Option<Vec<String>>,
401    #[serde(default)]
402    pub rewards: Option<serde_json::Value>,
403}
404
405/// Paginated response wrapper used by `/markets`, `/simplified-markets`, `/rewards/markets/current`, etc.
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct PaginatedResponse<T> {
408    pub data: Vec<T>,
409    #[serde(default)]
410    pub next_cursor: Option<String>,
411    #[serde(default)]
412    pub limit: Option<u64>,
413    #[serde(default)]
414    pub count: Option<u64>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
418pub struct OrderSummary {
419    pub price: String,
420    pub size: String,
421}
422
423/// Order book response from `/book`. All fields are snake_case.
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct OrderBookSummary {
426    pub market: String,
427    pub asset_id: String,
428    pub timestamp: String,
429    pub bids: Vec<OrderSummary>,
430    pub asks: Vec<OrderSummary>,
431    pub min_order_size: String,
432    pub tick_size: String,
433    pub neg_risk: bool,
434    #[serde(default)]
435    pub last_trade_price: String,
436    pub hash: String,
437}
438
439/// Signature type for orders
440/// - EOA: Standard Externally Owned Account (default)
441/// - PolyProxy: Polymarket Proxy Wallet
442/// - PolyGnosisSafe: Gnosis Safe Multisig
443#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444#[repr(u8)]
445#[derive(Default)]
446pub enum SignatureType {
447    #[default]
448    EOA = 0,
449    PolyProxy = 1,
450    PolyGnosisSafe = 2,
451}
452
453impl From<SignatureType> for u8 {
454    fn from(sig_type: SignatureType) -> Self {
455        sig_type as u8
456    }
457}
458
459impl From<u8> for SignatureType {
460    fn from(value: u8) -> Self {
461        match value {
462            0 => SignatureType::EOA,
463            1 => SignatureType::PolyProxy,
464            2 => SignatureType::PolyGnosisSafe,
465            _ => SignatureType::EOA, // fallback to default
466        }
467    }
468}
469
470// Custom serialization: always serialize as number
471impl Serialize for SignatureType {
472    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
473    where
474        S: Serializer,
475    {
476        serializer.serialize_u8(*self as u8)
477    }
478}
479
480// Custom deserialization: accept both string and number
481impl<'de> Deserialize<'de> for SignatureType {
482    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
483    where
484        D: Deserializer<'de>,
485    {
486        struct SignatureTypeVisitor;
487
488        impl<'de> serde::de::Visitor<'de> for SignatureTypeVisitor {
489            type Value = SignatureType;
490
491            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
492                formatter
493                    .write_str("a string (EOA, POLY_PROXY, POLY_GNOSIS_SAFE) or a number (0, 1, 2)")
494            }
495
496            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
497            where
498                E: serde::de::Error,
499            {
500                match value {
501                    0 => Ok(SignatureType::EOA),
502                    1 => Ok(SignatureType::PolyProxy),
503                    2 => Ok(SignatureType::PolyGnosisSafe),
504                    _ => Ok(SignatureType::EOA), // fallback
505                }
506            }
507
508            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
509            where
510                E: serde::de::Error,
511            {
512                self.visit_u64(value as u64)
513            }
514
515            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
516            where
517                E: serde::de::Error,
518            {
519                match value {
520                    "EOA" => Ok(SignatureType::EOA),
521                    "POLY_PROXY" => Ok(SignatureType::PolyProxy),
522                    "POLY_GNOSIS_SAFE" => Ok(SignatureType::PolyGnosisSafe),
523                    _ => Ok(SignatureType::EOA), // fallback
524                }
525            }
526        }
527
528        deserializer.deserialize_any(SignatureTypeVisitor)
529    }
530}
531
532#[derive(Debug, Clone, Serialize, Deserialize)]
533#[serde(rename_all = "camelCase")]
534pub struct OrderData {
535    pub maker: String,
536    pub taker: String,
537    pub token_id: String,
538    pub maker_amount: String,
539    pub taker_amount: String,
540    pub side: Side,
541    pub fee_rate_bps: String,
542    pub nonce: String,
543    pub signer: String,
544    pub expiration: String,
545    pub signature_type: SignatureType,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
549#[serde(rename_all = "camelCase")]
550pub struct SignedOrder {
551    pub salt: String,
552    pub maker: String,
553    pub signer: String,
554    pub taker: String,
555    pub token_id: String,
556    pub maker_amount: String,
557    pub taker_amount: String,
558    pub expiration: String,
559    pub nonce: String,
560    pub fee_rate_bps: String,
561    pub side: Side,
562    pub signature_type: SignatureType,
563    pub signature: String,
564}
565
566/// NewOrder is the payload structure for posting orders to the API
567/// It wraps SignedOrder with orderType, owner, and deferExec fields
568#[derive(Debug, Clone, Serialize, Deserialize)]
569#[serde(rename_all = "camelCase")]
570pub struct NewOrder {
571    pub order: NewOrderData,
572    pub owner: String,
573    pub order_type: OrderType,
574    #[serde(default)]
575    pub defer_exec: bool,
576    /// Post-only orders will be rejected if they would immediately match (added in v5.2.0)
577    /// Only supported for GTC and GTD orders
578    #[serde(default, skip_serializing_if = "Option::is_none")]
579    pub post_only: Option<bool>,
580}
581
582#[derive(Debug, Clone, Serialize, Deserialize)]
583#[serde(rename_all = "camelCase")]
584pub struct NewOrderData {
585    // API 要求 salt 为 number;为避免 JS 精度丢失,生成时限制在 2^53-1 范围内
586    // 序列化时使用 i64 以确保 JSON 中是 number 而非字符串
587    pub salt: i64,
588    pub maker: String,
589    pub signer: String,
590    pub taker: String,
591    pub token_id: String,
592    pub maker_amount: String,
593    pub taker_amount: String,
594    pub expiration: String,
595    pub nonce: String,
596    pub fee_rate_bps: String,
597    pub side: Side,
598    pub signature_type: SignatureType,
599    pub signature: String,
600}
601
602// Simple Chain constants
603pub const CHAIN_POLYGON: i32 = 137;
604pub const CHAIN_AMOY: i32 = 80002;
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct UserOrder {
609    pub token_id: String,
610    pub price: f64,
611    pub size: f64,
612    pub side: Side,
613    // 改为必填: fee_rate_bps 由外部传入,不再在创建流程中自动获取
614    pub fee_rate_bps: f64,
615    pub nonce: Option<u64>,
616    pub expiration: Option<u64>,
617    pub taker: Option<String>,
618}
619
620#[derive(Debug, Clone, Serialize, Deserialize)]
621#[serde(rename_all = "camelCase")]
622pub struct UserMarketOrder {
623    pub token_id: String,
624    // 改为必填: 市价单价格需由外部计算并传入
625    pub price: f64,
626    pub amount: f64,
627    pub side: Side,
628    // 改为必填: 由外部传入
629    pub fee_rate_bps: f64,
630    pub nonce: Option<u64>,
631    pub taker: Option<String>,
632    // order_type 改为必填: 市价单必须明确 FOK/FAK (或未来支持的其他类型)
633    pub order_type: OrderType,
634}
635
636#[derive(Debug, Clone, Serialize, Deserialize)]
637#[serde(rename_all = "camelCase")]
638pub struct Order {
639    pub id: Option<String>,
640    pub salt: Option<String>,
641    pub maker: Option<String>,
642    pub signer: Option<String>,
643    pub taker: Option<String>,
644    pub token_id: Option<String>,
645    pub maker_amount: Option<String>,
646    pub taker_amount: Option<String>,
647    pub price: Option<String>,
648    pub size: Option<String>,
649    pub expiration: Option<String>,
650    pub nonce: Option<String>,
651    pub fee_rate_bps: Option<String>,
652    pub side: Option<Side>,
653    pub signature_type: Option<SignatureType>,
654    pub signature: Option<String>,
655    pub status: Option<OrderStatus>,
656    pub metadata: Option<serde_json::Value>,
657}
658
659/// Response from GET /order endpoint
660/// According to API docs: https://docs.polymarket.com/developers/CLOB/orders/get-order
661/// Matches TypeScript SDK's OpenOrder interface
662#[derive(Debug, Clone, Serialize, Deserialize)]
663#[serde(rename_all = "snake_case")]
664pub struct OpenOrder {
665    pub id: String,
666    pub status: String,
667    pub owner: String,
668    pub maker_address: String,
669    pub market: String,
670    pub asset_id: String,
671    pub side: String,
672    pub original_size: String,
673    pub size_matched: String,
674    pub price: String,
675    pub associate_trades: Vec<String>,
676    pub outcome: String,
677    pub created_at: u64,
678    pub expiration: String,
679    #[serde(rename = "type", alias = "order_type")]
680    pub order_type: String,
681}
682
683/// Response from POST /order endpoint
684/// According to API docs: https://docs.polymarket.com/developers/CLOB/orders/create-order
685#[derive(Debug, Clone, Serialize, Deserialize)]
686#[serde(rename_all = "camelCase")]
687pub struct OrderResponse {
688    /// Boolean indicating if server-side error occurred (success = false -> server-side error)
689    pub success: bool,
690
691    /// Error message in case of unsuccessful placement
692    #[serde(rename = "errorMsg", default)]
693    pub error_msg: String,
694
695    /// ID of the order
696    #[serde(rename = "orderID", alias = "orderId", default)]
697    pub order_id: String,
698
699    /// Hash of settlement transaction if order was marketable and triggered a match
700    /// API docs call this "orderHashes", but TypeScript SDK uses "transactionsHashes"
701    #[serde(rename = "orderHashes", alias = "transactionsHashes", default)]
702    pub order_hashes: Vec<String>,
703
704    /// Order status: "matched", "live", "delayed", "unmatched"
705    #[serde(default)]
706    pub status: Option<String>,
707
708    /// Taking amount (not in official API docs, but returned by some endpoints)
709    #[serde(default)]
710    pub taking_amount: Option<String>,
711
712    /// Making amount (not in official API docs, but returned by some endpoints)
713    #[serde(default)]
714    pub making_amount: Option<String>,
715}
716
717#[derive(Debug, Clone, PartialEq)]
718pub enum OrderStatus {
719    OPEN,
720    FILLED,
721    CANCELLED,
722    OTHER(String),
723}
724
725impl fmt::Display for OrderStatus {
726    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
727        let s = match self {
728            OrderStatus::OPEN => "OPEN",
729            OrderStatus::FILLED => "FILLED",
730            OrderStatus::CANCELLED => "CANCELLED",
731            OrderStatus::OTHER(v) => v.as_str(),
732        };
733        write!(f, "{}", s)
734    }
735}
736
737impl Serialize for OrderStatus {
738    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
739    where
740        S: Serializer,
741    {
742        serializer.serialize_str(&self.to_string())
743    }
744}
745
746impl<'de> Deserialize<'de> for OrderStatus {
747    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
748    where
749        D: Deserializer<'de>,
750    {
751        let s = String::deserialize(deserializer)?;
752        match s.as_str() {
753            "OPEN" | "open" => Ok(OrderStatus::OPEN),
754            "FILLED" | "filled" => Ok(OrderStatus::FILLED),
755            "CANCELLED" | "CANCELED" | "cancelled" | "canceled" => Ok(OrderStatus::CANCELLED),
756            other => Ok(OrderStatus::OTHER(other.to_string())),
757        }
758    }
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
762pub struct MakerOrder {
763    pub order_id: String,
764    pub owner: String,
765    pub maker_address: String,
766    pub matched_amount: String,
767    pub price: String,
768    pub fee_rate_bps: String,
769    pub asset_id: String,
770    pub outcome: String,
771    pub side: Side,
772}
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
775pub struct Trade {
776    pub id: String,
777    pub taker_order_id: String,
778    pub market: String,
779    pub asset_id: String,
780    pub side: Side,
781    pub size: String,
782    pub fee_rate_bps: String,
783    pub price: String,
784    pub status: String,
785    pub match_time: String,
786    pub last_update: String,
787    pub outcome: String,
788    pub bucket_index: i64,
789    pub owner: String,
790    pub maker_address: String,
791    pub maker_orders: Vec<MakerOrder>,
792    pub transaction_hash: String,
793    pub trader_side: String,
794}
795
796#[derive(Debug, Clone, Serialize, Deserialize)]
797#[serde(rename_all = "camelCase")]
798pub struct Notification {
799    pub id: Option<String>,
800    pub title: Option<String>,
801    pub body: Option<String>,
802    pub data: Option<serde_json::Value>,
803    pub created_at: Option<String>,
804    pub read: Option<bool>,
805}
806
807/// Reward market info from `/rewards/markets/current`.
808#[derive(Debug, Clone, Serialize, Deserialize)]
809pub struct RewardsMarket {
810    pub condition_id: String,
811    #[serde(default)]
812    pub rewards_config: Option<serde_json::Value>,
813    #[serde(default, deserialize_with = "deserialize_string_or_number")]
814    pub rewards_max_spread: Option<String>,
815    #[serde(default, deserialize_with = "deserialize_string_or_number")]
816    pub rewards_min_size: Option<String>,
817    #[serde(default, deserialize_with = "deserialize_string_or_number")]
818    pub native_daily_rate: Option<String>,
819    #[serde(default, deserialize_with = "deserialize_string_or_number")]
820    pub total_daily_rate: Option<String>,
821}
822
823/// User earnings reward from `/rewards/user` endpoints.
824#[derive(Debug, Clone, Serialize, Deserialize)]
825pub struct Reward {
826    #[serde(default)]
827    pub market: Option<String>,
828    #[serde(default)]
829    pub amount: Option<String>,
830    #[serde(default)]
831    pub timestamp: Option<String>,
832    #[serde(default)]
833    pub metadata: Option<serde_json::Value>,
834}
835
836/// Single price-history data point from `/prices-history`.
837#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
838pub struct MarketPrice {
839    /// timestamp (unix seconds)
840    pub t: i64,
841    /// price
842    pub p: f64,
843}
844
845/// Response from `/prices-history`: `{"history": [{t, p}, ...]}`.
846#[derive(Debug, Clone, Serialize, Deserialize)]
847pub struct PriceHistoryResponse {
848    pub history: Vec<MarketPrice>,
849}
850
851/// Response from GET `/midpoint`: `{"mid": "0.77"}`.
852#[derive(Debug, Clone, Serialize, Deserialize)]
853pub struct MidpointResponse {
854    pub mid: String,
855}
856
857/// Response from GET `/price`: `{"price": "0.76"}`.
858#[derive(Debug, Clone, Serialize, Deserialize)]
859pub struct PriceResponse {
860    pub price: String,
861}
862
863/// Response from GET `/spread`: `{"spread": "0.02"}`.
864#[derive(Debug, Clone, Serialize, Deserialize)]
865pub struct SpreadResponse {
866    pub spread: String,
867}
868
869/// Response from GET `/last-trade-price`: `{"price": "0.8", "side": "BUY"}`.
870#[derive(Debug, Clone, Serialize, Deserialize)]
871pub struct LastTradePriceResponse {
872    pub price: String,
873    pub side: String,
874}
875
876/// Request body item for batch POST endpoints (`/midpoints`, `/prices`, `/spreads`, `/last-trades-prices`).
877#[derive(Debug, Clone, Serialize, Deserialize)]
878pub struct BookParams {
879    pub token_id: String,
880    #[serde(default, skip_serializing_if = "Option::is_none")]
881    pub side: Option<Side>,
882}
883
884#[derive(Debug, Clone, Serialize, Deserialize)]
885#[serde(rename_all = "camelCase")]
886pub struct BuilderTrade {
887    pub id: String,
888    pub trade_type: String,
889    pub taker_order_hash: String,
890    pub builder: String,
891    pub market: String,
892    pub asset_id: String,
893    pub side: String,
894    pub size: String,
895    pub size_usdc: String,
896    pub price: String,
897    pub status: String,
898    pub outcome: String,
899    pub outcome_index: i64,
900    pub owner: String,
901    pub maker: String,
902    pub transaction_hash: String,
903    pub match_time: String,
904    pub bucket_index: i64,
905    pub fee: String,
906    pub fee_usdc: String,
907    pub err_msg: Option<String>,
908    pub created_at: Option<String>,
909    pub updated_at: Option<String>,
910}
911
912/// Response from the heartbeat endpoint (added in v5.2.0)
913/// Heartbeats keep the session active; if not sent within 10s, all orders will be cancelled.
914#[derive(Debug, Clone, Serialize, Deserialize)]
915pub struct HeartbeatResponse {
916    /// The heartbeat ID to chain subsequent heartbeats
917    pub heartbeat_id: String,
918    /// Error message if any
919    pub error: Option<String>,
920}
921
922/// Arguments for posting multiple orders (added in v5.2.0)
923#[derive(Debug, Clone)]
924pub struct PostOrdersArgs {
925    pub order: SignedOrder,
926    pub order_type: OrderType,
927    /// Post-only orders will be rejected if they would immediately match
928    /// Only supported for GTC and GTD orders
929    pub post_only: Option<bool>,
930}