Skip to main content

polyoxide_clob/api/
markets.rs

1use polyoxide_core::QueryBuilder;
2use reqwest::Client;
3use serde::{Deserialize, Serialize};
4use url::Url;
5
6use crate::{
7    request::{AuthMode, Request},
8    types::OrderSide,
9};
10
11/// Markets namespace for market-related operations
12#[derive(Clone)]
13pub struct Markets {
14    pub(crate) client: Client,
15    pub(crate) base_url: Url,
16    pub(crate) chain_id: u64,
17}
18
19impl Markets {
20    /// Get a market by condition ID
21    pub fn get(&self, condition_id: impl Into<String>) -> Request<Market> {
22        Request::get(
23            self.client.clone(),
24            self.base_url.clone(),
25            format!("/markets/{}", urlencoding::encode(&condition_id.into())),
26            AuthMode::None,
27            self.chain_id,
28        )
29    }
30
31    pub fn get_by_token_ids(
32        &self,
33        token_ids: impl Into<Vec<String>>,
34    ) -> Request<ListMarketsResponse> {
35        Request::get(
36            self.client.clone(),
37            self.base_url.clone(),
38            "/markets",
39            AuthMode::None,
40            self.chain_id,
41        )
42        .query_many("clob_token_ids", token_ids.into())
43    }
44
45    /// List all markets
46    pub fn list(&self) -> Request<ListMarketsResponse> {
47        Request::get(
48            self.client.clone(),
49            self.base_url.clone(),
50            "/markets",
51            AuthMode::None,
52            self.chain_id,
53        )
54    }
55
56    /// Get order book for a token
57    pub fn order_book(&self, token_id: impl Into<String>) -> Request<OrderBook> {
58        Request::get(
59            self.client.clone(),
60            self.base_url.clone(),
61            "/book",
62            AuthMode::None,
63            self.chain_id,
64        )
65        .query("token_id", token_id.into())
66    }
67
68    /// Get price for a token and side
69    pub fn price(&self, token_id: impl Into<String>, side: OrderSide) -> Request<PriceResponse> {
70        Request::get(
71            self.client.clone(),
72            self.base_url.clone(),
73            "/price",
74            AuthMode::None,
75            self.chain_id,
76        )
77        .query("token_id", token_id.into())
78        .query("side", side.to_string())
79    }
80
81    /// Get midpoint price for a token
82    pub fn midpoint(&self, token_id: impl Into<String>) -> Request<MidpointResponse> {
83        Request::get(
84            self.client.clone(),
85            self.base_url.clone(),
86            "/midpoint",
87            AuthMode::None,
88            self.chain_id,
89        )
90        .query("token_id", token_id.into())
91    }
92
93    /// Get historical prices for a token
94    pub fn prices_history(&self, token_id: impl Into<String>) -> Request<PricesHistoryResponse> {
95        Request::get(
96            self.client.clone(),
97            self.base_url.clone(),
98            "/prices-history",
99            AuthMode::None,
100            self.chain_id,
101        )
102        .query("market", token_id.into())
103    }
104
105    /// Get neg_risk status for a token
106    pub fn neg_risk(&self, token_id: impl Into<String>) -> Request<NegRiskResponse> {
107        Request::get(
108            self.client.clone(),
109            self.base_url.clone(),
110            "/neg-risk".to_string(),
111            AuthMode::None,
112            self.chain_id,
113        )
114        .query("token_id", token_id.into())
115    }
116
117    /// Get tick size for a token
118    pub fn tick_size(&self, token_id: impl Into<String>) -> Request<TickSizeResponse> {
119        Request::get(
120            self.client.clone(),
121            self.base_url.clone(),
122            "/tick-size".to_string(),
123            AuthMode::None,
124            self.chain_id,
125        )
126        .query("token_id", token_id.into())
127    }
128}
129
130/// Market information
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct Market {
133    pub condition_id: String,
134    pub question_id: String,
135    pub tokens: Vec<MarketToken>,
136    pub rewards: Option<serde_json::Value>,
137    pub minimum_order_size: f64,
138    pub minimum_tick_size: f64,
139    pub description: String,
140    pub category: Option<String>,
141    pub end_date_iso: Option<String>,
142    pub question: String,
143    pub active: bool,
144    pub closed: bool,
145    pub archived: bool,
146    pub neg_risk: Option<bool>,
147    pub neg_risk_market_id: Option<String>,
148    pub enable_order_book: Option<bool>,
149}
150
151/// Markets list response
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct ListMarketsResponse {
154    pub data: Vec<Market>,
155    pub next_cursor: Option<String>,
156}
157
158/// Market token (outcome)
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct MarketToken {
161    pub token_id: Option<String>,
162    pub outcome: String,
163    pub price: Option<f64>,
164    pub winner: Option<bool>,
165}
166
167/// Order book level (price and size)
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct OrderLevel {
170    pub price: String,
171    pub size: String,
172}
173
174/// Order book data
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct OrderBook {
177    pub market: String,
178    pub asset_id: String,
179    pub bids: Vec<OrderLevel>,
180    pub asks: Vec<OrderLevel>,
181    pub timestamp: String,
182    pub hash: String,
183}
184
185/// Price response
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct PriceResponse {
188    pub price: String,
189}
190
191/// Midpoint price response
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct MidpointResponse {
194    pub mid: String,
195}
196
197/// A single point in the price history timeseries
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct PriceHistoryPoint {
200    /// Unix timestamp (seconds)
201    #[serde(rename = "t")]
202    pub timestamp: i64,
203    /// Price at this point in time
204    #[serde(rename = "p")]
205    pub price: f64,
206}
207
208/// Response from the prices-history endpoint
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct PricesHistoryResponse {
211    pub history: Vec<PriceHistoryPoint>,
212}
213
214/// Response from the neg-risk endpoint
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct NegRiskResponse {
217    pub neg_risk: bool,
218}
219
220/// Response from the tick-size endpoint
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct TickSizeResponse {
223    #[serde(deserialize_with = "deserialize_tick_size")]
224    pub minimum_tick_size: String,
225}
226
227fn deserialize_tick_size<'de, D>(deserializer: D) -> Result<String, D::Error>
228where
229    D: serde::Deserializer<'de>,
230{
231    use serde::Deserialize;
232    let v = serde_json::Value::deserialize(deserializer)?;
233    match v {
234        serde_json::Value::String(s) => Ok(s),
235        serde_json::Value::Number(n) => Ok(n.to_string()),
236        _ => Err(serde::de::Error::custom(
237            "expected string or number for tick size",
238        )),
239    }
240}