Skip to main content

polymarket_sdk/
types.rs

1//! Data models for the Polymarket CLOB and Gamma APIs.
2
3use serde::{Deserialize, Serialize};
4
5// ---------------------------------------------------------------------------
6// Enums
7// ---------------------------------------------------------------------------
8
9/// Order side.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "UPPERCASE")]
12pub enum OrderSide {
13    Buy,
14    Sell,
15}
16
17impl OrderSide {
18    /// Returns the numeric value used in the EIP-712 Order struct.
19    pub fn as_u8(&self) -> u8 {
20        match self {
21            OrderSide::Buy => 0,
22            OrderSide::Sell => 1,
23        }
24    }
25}
26
27/// Order time-in-force type.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "UPPERCASE")]
30pub enum OrderType {
31    /// Good-Till-Cancelled.
32    Gtc,
33    /// Good-Till-Date.
34    Gtd,
35    /// Fill-Or-Kill.
36    Fok,
37}
38
39/// Signature type for wallet interactions.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[repr(u8)]
42pub enum SignatureType {
43    /// Standard EOA (MetaMask, hardware wallets).
44    Eoa = 0,
45    /// Poly-proxy (email / Magic wallet).
46    PolyProxy = 1,
47    /// Poly-gnosis-safe (browser wallet proxy).
48    PolyGnosisSafe = 2,
49}
50
51impl SignatureType {
52    pub fn as_u8(&self) -> u8 {
53        *self as u8
54    }
55}
56
57// ---------------------------------------------------------------------------
58// API Credentials
59// ---------------------------------------------------------------------------
60
61/// L2 API credentials returned by key creation / derivation.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct ApiCredentials {
65    pub api_key: String,
66    pub secret: String,
67    pub passphrase: String,
68}
69
70// ---------------------------------------------------------------------------
71// Market Data
72// ---------------------------------------------------------------------------
73
74/// A simplified market from the CLOB API.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct SimplifiedMarket {
77    /// Unique token identifier.
78    #[serde(default)]
79    pub token_id: String,
80
81    /// Condition ID of the market.
82    #[serde(default)]
83    pub condition_id: String,
84
85    /// Human-readable question.
86    #[serde(default)]
87    pub question: String,
88
89    /// End date of the market.
90    #[serde(default)]
91    pub end_date_iso: Option<String>,
92
93    /// Whether or not the market is active.
94    #[serde(default)]
95    pub active: bool,
96
97    /// Whether or not the market is closed.
98    #[serde(default)]
99    pub closed: bool,
100
101    /// The list of tokens belonging to this market.
102    #[serde(default)]
103    pub tokens: Vec<MarketToken>,
104}
105
106/// A token belonging to a market.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct MarketToken {
109    pub token_id: String,
110    #[serde(default)]
111    pub outcome: String,
112    #[serde(default)]
113    pub price: Option<f64>,
114    #[serde(default)]
115    pub winner: bool,
116}
117
118/// A Gamma API market entry.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct GammaMarket {
121    #[serde(default)]
122    pub id: String,
123    #[serde(default)]
124    pub question: String,
125    #[serde(default)]
126    pub description: String,
127    #[serde(default)]
128    pub outcomes: Vec<String>,
129    #[serde(rename = "conditionId", default)]
130    pub condition_id: String,
131    #[serde(rename = "slug", default)]
132    pub slug: String,
133    #[serde(default)]
134    pub active: bool,
135    #[serde(default)]
136    pub closed: bool,
137    #[serde(rename = "endDate", default)]
138    pub end_date: Option<String>,
139
140    /// Catch-all for additional fields.
141    #[serde(flatten)]
142    pub extra: serde_json::Value,
143}
144
145// ---------------------------------------------------------------------------
146// Order Book
147// ---------------------------------------------------------------------------
148
149/// A single entry (level) in the order book.
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct OrderBookEntry {
152    pub price: String,
153    pub size: String,
154}
155
156/// Order book snapshot for a token.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct OrderBook {
159    #[serde(default)]
160    pub market: Option<String>,
161
162    #[serde(default)]
163    pub asset_id: Option<String>,
164
165    #[serde(default)]
166    pub bids: Vec<OrderBookEntry>,
167
168    #[serde(default)]
169    pub asks: Vec<OrderBookEntry>,
170
171    /// Hash of the order book state.
172    #[serde(default)]
173    pub hash: Option<String>,
174
175    #[serde(default)]
176    pub timestamp: Option<String>,
177}
178
179/// Price response.
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct PriceResponse {
182    #[serde(default)]
183    pub price: Option<String>,
184
185    #[serde(default)]
186    pub mid: Option<String>,
187}
188
189/// Spread response.
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct SpreadResponse {
192    #[serde(default)]
193    pub spread: Option<String>,
194}
195
196/// Last trade price response.
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct LastTradePriceResponse {
199    #[serde(default)]
200    pub price: Option<String>,
201}
202
203/// Price history entry.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct PriceHistoryEntry {
206    #[serde(rename = "t")]
207    pub timestamp: i64,
208    #[serde(rename = "p")]
209    pub price: f64,
210}
211
212// ---------------------------------------------------------------------------
213// Orders
214// ---------------------------------------------------------------------------
215
216/// Parameters for creating an order.
217#[derive(Debug, Clone)]
218pub struct TradeParams {
219    /// Token ID to trade.
220    pub token_id: String,
221    /// Buy or Sell.
222    pub side: OrderSide,
223    /// Limit price (0.0 – 1.0).
224    pub price: f64,
225    /// Size in USDC terms.
226    pub size: f64,
227    /// Order type (GTC, GTD, FOK).
228    pub order_type: OrderType,
229    /// Fee rate in basis points (default: 0).
230    pub fee_rate_bps: u64,
231    /// Expiration timestamp (only for GTD). 0 = no expiration.
232    pub expiration: u64,
233    /// Whether this is a neg-risk market.
234    pub neg_risk: bool,
235}
236
237impl TradeParams {
238    /// Create new trade parameters with sensible defaults.
239    pub fn new(token_id: impl Into<String>, side: OrderSide, price: f64, size: f64) -> Self {
240        Self {
241            token_id: token_id.into(),
242            side,
243            price,
244            size,
245            order_type: OrderType::Gtc,
246            fee_rate_bps: 0,
247            expiration: 0,
248            neg_risk: false,
249        }
250    }
251
252    pub fn with_order_type(mut self, order_type: OrderType) -> Self {
253        self.order_type = order_type;
254        self
255    }
256
257    pub fn with_fee_rate_bps(mut self, fee_rate_bps: u64) -> Self {
258        self.fee_rate_bps = fee_rate_bps;
259        self
260    }
261
262    pub fn with_expiration(mut self, expiration: u64) -> Self {
263        self.expiration = expiration;
264        self
265    }
266
267    pub fn with_neg_risk(mut self, neg_risk: bool) -> Self {
268        self.neg_risk = neg_risk;
269        self
270    }
271}
272
273/// A signed order ready for submission to the API.
274#[derive(Debug, Clone, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct SignedOrder {
277    /// EIP-712 order salt / id.
278    pub order: OrderPayload,
279    /// Hex-encoded signature.
280    pub signature: String,
281    /// Signature type.
282    pub signature_type: u8,
283    /// Order type (GTC, GTD, FOK).
284    pub order_type: OrderType,
285}
286
287/// The raw order payload within a signed order.
288#[derive(Debug, Clone, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct OrderPayload {
291    /// EIP-712 order salt.
292    pub salt: String,
293    /// Maker address.
294    pub maker: String,
295    /// Signer address.
296    pub signer: String,
297    /// Taker address (usually zero).
298    pub taker: String,
299    /// Token ID.
300    pub token_id: String,
301    /// Maker amount in base units.
302    pub maker_amount: String,
303    /// Taker amount in base units.
304    pub taker_amount: String,
305    /// Expiration timestamp.
306    pub expiration: String,
307    /// Nonce.
308    pub nonce: String,
309    /// Fee rate in basis points.
310    pub fee_rate_bps: String,
311    /// Side (0 = BUY, 1 = SELL).
312    pub side: String,
313    /// Signature type.
314    pub signature_type: String,
315}
316
317/// Response from placing an order.
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct OrderResponse {
320    #[serde(default)]
321    pub success: bool,
322    #[serde(rename = "orderID", default)]
323    pub order_id: Option<String>,
324    #[serde(rename = "errorMsg", default)]
325    pub error_msg: Option<String>,
326    #[serde(default)]
327    pub status: Option<String>,
328
329    #[serde(flatten)]
330    pub extra: serde_json::Value,
331}
332
333/// An existing order from the API.
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct OpenOrder {
336    #[serde(default)]
337    pub id: String,
338    #[serde(default)]
339    pub status: String,
340    #[serde(default)]
341    pub owner: String,
342    #[serde(default)]
343    pub side: String,
344    #[serde(rename = "tokenId", default)]
345    pub token_id: String,
346    #[serde(rename = "originalSize", default)]
347    pub original_size: String,
348    #[serde(rename = "remainingSize", default)]
349    pub remaining_size: String,
350    #[serde(default)]
351    pub price: String,
352    #[serde(rename = "orderType", default)]
353    pub order_type: String,
354    #[serde(rename = "createdAt", default)]
355    pub created_at: Option<String>,
356
357    #[serde(flatten)]
358    pub extra: serde_json::Value,
359}
360
361/// Cancel response.
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct CancelResponse {
364    #[serde(default)]
365    pub canceled: Vec<String>,
366    #[serde(rename = "notCanceled", default)]
367    pub not_canceled: Vec<String>,
368}
369
370// ---------------------------------------------------------------------------
371// API Key response
372// ---------------------------------------------------------------------------
373
374/// API key creation response.
375#[derive(Debug, Clone, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct ApiKeyResponse {
378    pub api_key: String,
379    pub secret: String,
380    pub passphrase: String,
381}
382
383// ---------------------------------------------------------------------------
384// Tick Size
385// ---------------------------------------------------------------------------
386
387/// Tick size info for a market.
388#[derive(Debug, Clone, Serialize, Deserialize)]
389pub struct TickSizeInfo {
390    #[serde(default)]
391    pub minimum_tick_size: Option<String>,
392
393    #[serde(default)]
394    pub minimum_order_size: Option<String>,
395}
396
397// ---------------------------------------------------------------------------
398// Amount helpers
399// ---------------------------------------------------------------------------
400
401/// USDC has 6 decimals on Polygon.
402pub const USDC_DECIMALS: u32 = 6;
403
404/// Scale factor for USDC amounts (10^6).
405pub const USDC_SCALE: f64 = 1_000_000.0;
406
407/// Convert a human-readable USDC amount to base units (6 decimals).
408pub fn to_base_units(amount: f64) -> u128 {
409    (amount * USDC_SCALE).round() as u128
410}
411
412/// Convert base units to a human-readable USDC amount.
413pub fn from_base_units(amount: u128) -> f64 {
414    amount as f64 / USDC_SCALE
415}
416
417/// Calculate maker and taker amounts from price and size.
418///
419/// For a BUY: maker pays USDC (size), taker delivers tokens (size / price).
420/// For a SELL: maker delivers tokens (size), taker pays USDC (size * price).
421pub fn calculate_amounts(side: OrderSide, price: f64, size: f64) -> (u128, u128) {
422    match side {
423        OrderSide::Buy => {
424            let maker_amount = to_base_units(size);
425            let taker_amount = to_base_units(size / price);
426            (maker_amount, taker_amount)
427        }
428        OrderSide::Sell => {
429            let maker_amount = to_base_units(size);
430            let taker_amount = to_base_units(size * price);
431            (maker_amount, taker_amount)
432        }
433    }
434}
435
436// ---------------------------------------------------------------------------
437// Gamma Events
438// ---------------------------------------------------------------------------
439
440/// A Gamma API event (groups multiple markets).
441#[derive(Debug, Clone, Serialize, Deserialize)]
442pub struct GammaEvent {
443    #[serde(default)]
444    pub id: String,
445    #[serde(default)]
446    pub title: String,
447    #[serde(default)]
448    pub slug: String,
449    #[serde(default)]
450    pub description: String,
451    #[serde(default)]
452    pub active: bool,
453    #[serde(default)]
454    pub closed: bool,
455    #[serde(default)]
456    pub markets: Vec<GammaMarket>,
457    #[serde(rename = "startDate", default)]
458    pub start_date: Option<String>,
459    #[serde(rename = "endDate", default)]
460    pub end_date: Option<String>,
461
462    /// Catch-all for additional fields.
463    #[serde(flatten)]
464    pub extra: serde_json::Value,
465}
466
467// ---------------------------------------------------------------------------
468// Data API types
469// ---------------------------------------------------------------------------
470
471/// A historical trade record.
472#[derive(Debug, Clone, Serialize, Deserialize)]
473pub struct TradeRecord {
474    #[serde(default)]
475    pub id: String,
476    #[serde(default)]
477    pub taker_order_id: String,
478    #[serde(default)]
479    pub market: String,
480    #[serde(rename = "assetId", default)]
481    pub asset_id: String,
482    #[serde(default)]
483    pub side: String,
484    #[serde(default)]
485    pub size: String,
486    #[serde(default)]
487    pub price: String,
488    #[serde(default)]
489    pub status: String,
490    #[serde(rename = "matchTime", default)]
491    pub match_time: Option<String>,
492    #[serde(rename = "lastUpdate", default)]
493    pub last_update: Option<String>,
494    #[serde(rename = "createdAt", default)]
495    pub created_at: Option<String>,
496
497    #[serde(flatten)]
498    pub extra: serde_json::Value,
499}
500
501/// A user's position in a market.
502#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct Position {
504    #[serde(rename = "assetId", default)]
505    pub asset_id: String,
506    #[serde(rename = "conditionId", default)]
507    pub condition_id: String,
508    #[serde(default)]
509    pub size: String,
510    #[serde(rename = "avgPrice", default)]
511    pub avg_price: String,
512    #[serde(rename = "currentValue", default)]
513    pub current_value: Option<String>,
514    #[serde(rename = "realizedPnl", default)]
515    pub realized_pnl: Option<String>,
516    #[serde(rename = "unrealizedPnl", default)]
517    pub unrealized_pnl: Option<String>,
518    #[serde(default)]
519    pub side: String,
520
521    #[serde(flatten)]
522    pub extra: serde_json::Value,
523}
524
525// ---------------------------------------------------------------------------
526// WebSocket Stream types
527// ---------------------------------------------------------------------------
528
529/// Events received from the Polymarket WebSocket stream.
530#[cfg(feature = "ws")]
531#[derive(Debug, Clone, Serialize, Deserialize)]
532#[serde(tag = "event_type")]
533pub enum StreamEvent {
534    /// Price change on a market token.
535    #[serde(rename = "price_change")]
536    PriceChange {
537        #[serde(default)]
538        asset_id: String,
539        #[serde(default)]
540        price: String,
541        #[serde(default)]
542        timestamp: String,
543    },
544
545    /// A trade was executed.
546    #[serde(rename = "trade")]
547    Trade {
548        #[serde(default)]
549        asset_id: String,
550        #[serde(default)]
551        price: String,
552        #[serde(default)]
553        size: String,
554        #[serde(default)]
555        side: String,
556        #[serde(default)]
557        timestamp: String,
558    },
559
560    /// Order book snapshot or update.
561    #[serde(rename = "book")]
562    Book {
563        #[serde(default)]
564        asset_id: String,
565        #[serde(default)]
566        bids: Vec<OrderBookEntry>,
567        #[serde(default)]
568        asks: Vec<OrderBookEntry>,
569        #[serde(default)]
570        timestamp: String,
571    },
572
573    /// Catch-all for unknown events.
574    #[serde(other)]
575    Unknown,
576}
577
578/// Raw WebSocket message from the stream (before type dispatch).
579#[cfg(feature = "ws")]
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct RawStreamMessage {
582    #[serde(default)]
583    pub event_type: String,
584
585    #[serde(flatten)]
586    pub data: serde_json::Value,
587}
588