Skip to main content

predict_sdk/
api_types.rs

1/// API types for predict.fun REST API responses
2///
3/// These types represent the data structures returned by the predict.fun API.
4
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7
8/// Represents a Predict market
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct PredictMarket {
12    /// Market ID
13    pub id: u64,
14    /// Market title
15    pub title: String,
16    /// Market question
17    pub question: String,
18    /// Condition ID (for on-chain settlement)
19    pub condition_id: String,
20    /// Market status (REGISTERED, OPEN, RESOLVED)
21    pub status: String,
22    /// Category slug this market belongs to
23    pub category_slug: String,
24    /// Whether this is a neg risk market
25    pub is_neg_risk: bool,
26    /// Whether yield bearing is enabled
27    pub is_yield_bearing: bool,
28    /// Fee rate in basis points (fetched from GET /markets endpoint)
29    #[serde(default)]
30    pub fee_rate_bps: u64,
31    /// Market outcomes
32    pub outcomes: Vec<PredictOutcome>,
33    /// Created at timestamp
34    pub created_at: String,
35
36    // --- Strike price fields (populated from GraphQL) ---
37
38    /// Strike price (Pyth) - populated from GraphQL marketData.startPrice
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub start_price: Option<Decimal>,
41    /// Price feed ID (e.g., "1" for BTC/USD) - populated from GraphQL
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub price_feed_id: Option<String>,
44}
45
46impl PredictMarket {
47    /// Get the strike price if available
48    pub fn strike_price(&self) -> Option<Decimal> {
49        self.start_price
50    }
51
52    /// Set the strike price from GraphQL market data
53    pub fn set_strike_price(&mut self, start_price: Decimal, price_feed_id: String) {
54        self.start_price = Some(start_price);
55        self.price_feed_id = Some(price_feed_id);
56    }
57}
58
59/// Represents a market outcome
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct PredictOutcome {
63    /// Outcome name (e.g., "Up", "Down")
64    pub name: String,
65    /// Index set for this outcome
66    pub index_set: u64,
67    /// On-chain token ID
68    pub on_chain_id: String,
69    /// Resolution status (null if not resolved)
70    pub status: Option<String>,
71}
72
73/// Represents an order book for a Predict market
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct PredictOrderBook {
76    /// Market ID
77    pub market_id: String,
78    /// Best bid price
79    pub best_bid: Option<Decimal>,
80    /// Best ask price
81    pub best_ask: Option<Decimal>,
82    /// Bid orders (price, size)
83    pub bids: Vec<(Decimal, Decimal)>,
84    /// Ask orders (price, size)
85    pub asks: Vec<(Decimal, Decimal)>,
86}
87
88// ============================================================================
89// Order types matching Predict API v1
90// ============================================================================
91
92/// Order status
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "UPPERCASE")]
95pub enum OrderStatus {
96    Open,
97    Filled,
98    Expired,
99    Cancelled,
100    Invalidated,
101}
102
103/// The signed order data embedded in an order response
104#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct PredictOrderData {
107    pub hash: String,
108    #[serde(deserialize_with = "string_or_number")]
109    pub salt: String,
110    pub maker: String,
111    pub signer: String,
112    pub taker: String,
113    pub token_id: String,
114    #[serde(deserialize_with = "string_or_number")]
115    pub maker_amount: String,
116    #[serde(deserialize_with = "string_or_number")]
117    pub taker_amount: String,
118    #[serde(deserialize_with = "string_or_number")]
119    pub expiration: String,
120    #[serde(deserialize_with = "string_or_number")]
121    pub nonce: String,
122    #[serde(deserialize_with = "string_or_number")]
123    pub fee_rate_bps: String,
124    /// 0 = BUY, 1 = SELL
125    pub side: u8,
126    /// 0 = EOA
127    pub signature_type: u8,
128    pub signature: String,
129}
130
131/// Deserialize a field that can be either a string or a number, always returning a String
132fn string_or_number<'de, D>(deserializer: D) -> std::result::Result<String, D::Error>
133where
134    D: serde::Deserializer<'de>,
135{
136    use serde::de;
137
138    struct StringOrNumberVisitor;
139
140    impl<'de> de::Visitor<'de> for StringOrNumberVisitor {
141        type Value = String;
142
143        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
144            formatter.write_str("a string or number")
145        }
146
147        fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<String, E> {
148            Ok(v.to_string())
149        }
150
151        fn visit_string<E: de::Error>(self, v: String) -> std::result::Result<String, E> {
152            Ok(v)
153        }
154
155        fn visit_u64<E: de::Error>(self, v: u64) -> std::result::Result<String, E> {
156            Ok(v.to_string())
157        }
158
159        fn visit_i64<E: de::Error>(self, v: i64) -> std::result::Result<String, E> {
160            Ok(v.to_string())
161        }
162
163        fn visit_f64<E: de::Error>(self, v: f64) -> std::result::Result<String, E> {
164            Ok(v.to_string())
165        }
166    }
167
168    deserializer.deserialize_any(StringOrNumberVisitor)
169}
170
171/// Represents an order on Predict (from GET /v1/orders)
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct PredictOrder {
175    /// Order ID (bigint as string)
176    pub id: String,
177    /// Market ID (can be number or string depending on endpoint)
178    #[serde(deserialize_with = "deserialize_market_id")]
179    pub market_id: u64,
180    /// Currency (e.g., "USDT")
181    #[serde(default)]
182    pub currency: Option<String>,
183    /// Total order amount (wei string)
184    pub amount: String,
185    /// Amount filled so far (wei string)
186    pub amount_filled: String,
187    /// Whether this is a neg risk market
188    #[serde(default)]
189    pub is_neg_risk: bool,
190    /// Whether yield bearing is enabled
191    #[serde(default)]
192    pub is_yield_bearing: bool,
193    /// Order strategy: "LIMIT" or "MARKET"
194    #[serde(default)]
195    pub strategy: String,
196    /// Order status
197    pub status: OrderStatus,
198    /// The signed order payload
199    pub order: PredictOrderData,
200}
201
202/// Deserialize market_id from either a number or a string
203fn deserialize_market_id<'de, D>(deserializer: D) -> std::result::Result<u64, D::Error>
204where
205    D: serde::Deserializer<'de>,
206{
207    use serde::de;
208
209    struct MarketIdVisitor;
210
211    impl<'de> de::Visitor<'de> for MarketIdVisitor {
212        type Value = u64;
213
214        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
215            formatter.write_str("a number or string containing a number")
216        }
217
218        fn visit_u64<E: de::Error>(self, v: u64) -> std::result::Result<u64, E> {
219            Ok(v)
220        }
221
222        fn visit_i64<E: de::Error>(self, v: i64) -> std::result::Result<u64, E> {
223            Ok(v as u64)
224        }
225
226        fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<u64, E> {
227            v.parse::<u64>().map_err(de::Error::custom)
228        }
229    }
230
231    deserializer.deserialize_any(MarketIdVisitor)
232}
233
234// ============================================================================
235// API response wrappers
236// ============================================================================
237
238/// Response from GET /v1/orders
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct GetOrdersResponse {
241    pub success: bool,
242    pub cursor: Option<String>,
243    pub data: Vec<PredictOrder>,
244}
245
246/// Response from POST /v1/orders (place order)
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct PlaceOrderResponse {
249    pub success: bool,
250    pub data: Option<PlaceOrderData>,
251}
252
253/// Inner data from place order response
254#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct PlaceOrderData {
257    pub code: Option<String>,
258    pub order_id: String,
259    pub order_hash: String,
260}
261
262/// Request body for POST /v1/orders
263#[derive(Debug, Clone, Serialize)]
264#[serde(rename_all = "camelCase")]
265pub struct CreateOrderRequest {
266    pub data: CreateOrderData,
267}
268
269/// Inner data for create order request
270#[derive(Debug, Clone, Serialize)]
271#[serde(rename_all = "camelCase")]
272pub struct CreateOrderData {
273    pub order: serde_json::Value,
274    pub price_per_share: String,
275    pub strategy: String,
276}
277
278/// Response from POST /v1/orders/remove (cancel orders)
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct RemoveOrdersResponse {
281    pub success: bool,
282    pub removed: Vec<String>,
283    pub noop: Vec<String>,
284}
285
286/// Request body for POST /v1/orders/remove
287#[derive(Debug, Clone, Serialize)]
288pub struct RemoveOrdersRequest {
289    pub data: RemoveOrdersData,
290}
291
292/// Inner data for remove orders request
293#[derive(Debug, Clone, Serialize)]
294pub struct RemoveOrdersData {
295    pub ids: Vec<String>,
296}
297
298// ============================================================================
299// Auth types
300// ============================================================================
301
302/// Response from GET /v1/auth/message
303#[derive(Debug, Clone, Deserialize)]
304pub struct AuthMessageResponse {
305    pub success: bool,
306    pub data: AuthMessageData,
307}
308
309/// Inner data from auth message response
310#[derive(Debug, Clone, Deserialize)]
311pub struct AuthMessageData {
312    pub message: String,
313}
314
315/// Request body for POST /v1/auth
316#[derive(Debug, Clone, Serialize)]
317pub struct AuthRequest {
318    pub signer: String,
319    pub signature: String,
320    pub message: String,
321}
322
323/// Response from POST /v1/auth
324#[derive(Debug, Clone, Deserialize)]
325pub struct AuthResponse {
326    pub success: bool,
327    pub data: AuthResponseData,
328}
329
330/// Inner data from auth response
331#[derive(Debug, Clone, Deserialize)]
332pub struct AuthResponseData {
333    pub token: String,
334}
335
336// ============================================================================
337// Position types
338// ============================================================================
339
340/// Response from GET /v1/positions
341#[derive(Debug, Clone, Deserialize)]
342pub struct GetPositionsResponse {
343    pub success: bool,
344    pub cursor: Option<String>,
345    pub data: Vec<PredictPosition>,
346}
347
348/// A position on Predict
349#[derive(Debug, Clone, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct PredictPosition {
352    pub id: String,
353    pub market: PredictPositionMarket,
354    pub outcome: PredictPositionOutcome,
355    /// Token amount (wei string)
356    pub amount: String,
357    /// USD value
358    #[serde(default)]
359    pub value_usd: Option<String>,
360}
361
362/// Market info embedded in a position
363#[derive(Debug, Clone, Deserialize)]
364#[serde(rename_all = "camelCase")]
365pub struct PredictPositionMarket {
366    pub id: u64,
367    pub title: String,
368    #[serde(default)]
369    pub condition_id: Option<String>,
370}
371
372/// Outcome info embedded in a position
373#[derive(Debug, Clone, Deserialize)]
374#[serde(rename_all = "camelCase")]
375pub struct PredictPositionOutcome {
376    pub name: String,
377    pub index_set: u64,
378    pub on_chain_id: String,
379    pub status: Option<String>,
380}
381
382// ============================================================================
383// Wallet event types (from predictWalletEvents WebSocket topic)
384// ============================================================================
385
386/// Details from a wallet event's `details` object (present on transaction events)
387#[derive(Debug, Clone, Default)]
388pub struct WalletEventDetails {
389    /// Fill price (e.g., "0.290")
390    pub price: Option<String>,
391    /// Order quantity (e.g., "5.000")
392    pub quantity: Option<String>,
393    /// Quantity filled (e.g., "5.000")
394    pub quantity_filled: Option<String>,
395    /// Outcome side (e.g., "YES" or "NO")
396    pub outcome: Option<String>,
397    /// Quote type (e.g., "ASK" or "BID")
398    pub quote_type: Option<String>,
399}
400
401/// Wallet events received via predictWalletEvents/{jwt} WebSocket topic
402#[derive(Debug, Clone)]
403pub enum PredictWalletEvent {
404    /// Order successfully placed in orderbook
405    OrderAccepted {
406        order_hash: String,
407        order_id: String,
408    },
409    /// Order rejected
410    OrderNotAccepted {
411        order_hash: String,
412        order_id: String,
413        reason: Option<String>,
414    },
415    /// Order expired
416    OrderExpired {
417        order_hash: String,
418        order_id: String,
419    },
420    /// Order cancelled by user
421    OrderCancelled {
422        order_hash: String,
423        order_id: String,
424    },
425    /// Order matched, transaction sent to blockchain
426    OrderTransactionSubmitted {
427        order_hash: String,
428        order_id: String,
429        tx_hash: Option<String>,
430        details: WalletEventDetails,
431    },
432    /// On-chain transaction succeeded (order filled)
433    OrderTransactionSuccess {
434        order_hash: String,
435        order_id: String,
436        tx_hash: Option<String>,
437        details: WalletEventDetails,
438    },
439    /// On-chain transaction failed
440    OrderTransactionFailed {
441        order_hash: String,
442        order_id: String,
443        tx_hash: Option<String>,
444        details: WalletEventDetails,
445    },
446    /// Unknown wallet event type
447    Unknown {
448        event_type: String,
449        data: serde_json::Value,
450    },
451}
452
453// ============================================================================
454// Category types
455// ============================================================================
456
457/// API response wrapper for category endpoint
458#[derive(Debug, Clone, Serialize, Deserialize)]
459pub struct CategoryResponse {
460    pub success: bool,
461    pub data: PredictCategory,
462}
463
464/// Predict category (collection of related markets)
465///
466/// Categories group markets by asset, time, and duration.
467/// The slug follows the pattern: `{asset}-usd-up-down-{yyyy}-{mm}-{dd}-{hh}-{mm}-{duration}`
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct PredictCategory {
470    /// Category ID
471    pub id: u64,
472    /// Category slug (used for lookup)
473    pub slug: String,
474    /// Category title
475    pub title: String,
476    /// Markets within this category
477    pub markets: Vec<PredictMarket>,
478}