Skip to main content

clob_client_rust/
types.rs

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