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