Skip to main content

polyoxide_gamma/api/
markets.rs

1use polyoxide_core::{HttpClient, QueryBuilder, Request};
2
3use crate::{
4    error::GammaError,
5    types::{Market, Tag},
6};
7
8/// Markets namespace for market-related operations
9#[derive(Clone)]
10pub struct Markets {
11    pub(crate) http_client: HttpClient,
12}
13
14impl Markets {
15    /// Get a specific market by ID
16    pub fn get(&self, id: impl Into<String>) -> GetMarket {
17        GetMarket {
18            request: Request::new(
19                self.http_client.clone(),
20                format!("/markets/{}", urlencoding::encode(&id.into())),
21            ),
22        }
23    }
24
25    /// Get a market by its slug
26    pub fn get_by_slug(&self, slug: impl Into<String>) -> GetMarket {
27        GetMarket {
28            request: Request::new(
29                self.http_client.clone(),
30                format!("/markets/slug/{}", urlencoding::encode(&slug.into())),
31            ),
32        }
33    }
34
35    /// List markets with optional filtering
36    pub fn list(&self) -> ListMarkets {
37        ListMarkets {
38            request: Request::new(self.http_client.clone(), "/markets"),
39        }
40    }
41
42    /// Get tags for a market
43    pub fn tags(&self, id: impl Into<String>) -> Request<Vec<Tag>, GammaError> {
44        Request::new(
45            self.http_client.clone(),
46            format!("/markets/{}/tags", urlencoding::encode(&id.into())),
47        )
48    }
49}
50
51/// Request builder for getting a single market
52pub struct GetMarket {
53    request: Request<Market, GammaError>,
54}
55
56impl GetMarket {
57    /// Include tag data in response
58    pub fn include_tag(mut self, include: bool) -> Self {
59        self.request = self.request.query("include_tag", include);
60        self
61    }
62
63    /// Execute the request
64    pub async fn send(self) -> Result<Market, GammaError> {
65        self.request.send().await
66    }
67}
68
69/// Request builder for listing markets
70pub struct ListMarkets {
71    request: Request<Vec<Market>, GammaError>,
72}
73
74impl ListMarkets {
75    /// Set maximum number of results (minimum: 0)
76    pub fn limit(mut self, limit: u32) -> Self {
77        self.request = self.request.query("limit", limit);
78        self
79    }
80
81    /// Set pagination offset (minimum: 0)
82    pub fn offset(mut self, offset: u32) -> Self {
83        self.request = self.request.query("offset", offset);
84        self
85    }
86
87    /// Set order fields (comma-separated list)
88    pub fn order(mut self, order: impl Into<String>) -> Self {
89        self.request = self.request.query("order", order.into());
90        self
91    }
92
93    /// Set sort direction
94    pub fn ascending(mut self, ascending: bool) -> Self {
95        self.request = self.request.query("ascending", ascending);
96        self
97    }
98
99    /// Filter by specific market IDs
100    pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
101        self.request = self.request.query_many("id", ids);
102        self
103    }
104
105    /// Filter by market slugs
106    pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
107        self.request = self.request.query_many("slug", slugs);
108        self
109    }
110
111    /// Filter by CLOB token IDs
112    pub fn clob_token_ids(mut self, token_ids: impl IntoIterator<Item = impl ToString>) -> Self {
113        self.request = self.request.query_many("clob_token_ids", token_ids);
114        self
115    }
116
117    /// Filter by condition IDs
118    pub fn condition_ids(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
119        self.request = self.request.query_many("condition_ids", condition_ids);
120        self
121    }
122
123    /// Filter by market maker addresses
124    pub fn market_maker_address(
125        mut self,
126        addresses: impl IntoIterator<Item = impl ToString>,
127    ) -> Self {
128        self.request = self.request.query_many("market_maker_address", addresses);
129        self
130    }
131
132    /// Set minimum liquidity threshold
133    pub fn liquidity_num_min(mut self, min: f64) -> Self {
134        self.request = self.request.query("liquidity_num_min", min);
135        self
136    }
137
138    /// Set maximum liquidity threshold
139    pub fn liquidity_num_max(mut self, max: f64) -> Self {
140        self.request = self.request.query("liquidity_num_max", max);
141        self
142    }
143
144    /// Set minimum trading volume
145    pub fn volume_num_min(mut self, min: f64) -> Self {
146        self.request = self.request.query("volume_num_min", min);
147        self
148    }
149
150    /// Set maximum trading volume
151    pub fn volume_num_max(mut self, max: f64) -> Self {
152        self.request = self.request.query("volume_num_max", max);
153        self
154    }
155
156    /// Set earliest market start date (ISO 8601 format)
157    pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
158        self.request = self.request.query("start_date_min", date.into());
159        self
160    }
161
162    /// Set latest market start date (ISO 8601 format)
163    pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
164        self.request = self.request.query("start_date_max", date.into());
165        self
166    }
167
168    /// Set earliest market end date (ISO 8601 format)
169    pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
170        self.request = self.request.query("end_date_min", date.into());
171        self
172    }
173
174    /// Set latest market end date (ISO 8601 format)
175    pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
176        self.request = self.request.query("end_date_max", date.into());
177        self
178    }
179
180    /// Filter by tag identifier
181    pub fn tag_id(mut self, tag_id: i64) -> Self {
182        self.request = self.request.query("tag_id", tag_id);
183        self
184    }
185
186    /// Include related tags in response
187    pub fn related_tags(mut self, include: bool) -> Self {
188        self.request = self.request.query("related_tags", include);
189        self
190    }
191
192    /// Filter for create-your-own markets
193    pub fn cyom(mut self, cyom: bool) -> Self {
194        self.request = self.request.query("cyom", cyom);
195        self
196    }
197
198    /// Filter by UMA resolution status
199    pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
200        self.request = self.request.query("uma_resolution_status", status.into());
201        self
202    }
203
204    /// Filter by game identifier
205    pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
206        self.request = self.request.query("game_id", game_id.into());
207        self
208    }
209
210    /// Filter by sports market types
211    pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
212        self.request = self.request.query_many("sports_market_types", types);
213        self
214    }
215
216    /// Set minimum rewards threshold
217    pub fn rewards_min_size(mut self, min: f64) -> Self {
218        self.request = self.request.query("rewards_min_size", min);
219        self
220    }
221
222    /// Filter by question identifiers
223    pub fn question_ids(mut self, question_ids: impl IntoIterator<Item = impl ToString>) -> Self {
224        self.request = self.request.query_many("question_ids", question_ids);
225        self
226    }
227
228    /// Include tag data in results
229    pub fn include_tag(mut self, include: bool) -> Self {
230        self.request = self.request.query("include_tag", include);
231        self
232    }
233
234    /// Filter for closed or active markets
235    pub fn closed(mut self, closed: bool) -> Self {
236        self.request = self.request.query("closed", closed);
237        self
238    }
239
240    /// Filter by open status (convenience method, opposite of closed)
241    pub fn open(mut self, open: bool) -> Self {
242        self.request = self.request.query("closed", !open);
243        self
244    }
245
246    /// Filter by archived status
247    pub fn archived(mut self, archived: bool) -> Self {
248        self.request = self.request.query("archived", archived);
249        self
250    }
251
252    /// Execute the request
253    pub async fn send(self) -> Result<Vec<Market>, GammaError> {
254        self.request.send().await
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use crate::Gamma;
261
262    fn gamma() -> Gamma {
263        Gamma::new().unwrap()
264    }
265
266    /// Verify that all builder methods chain correctly (compile-time type check)
267    /// and produce a valid builder ready to send.
268    #[test]
269    fn test_list_markets_full_chain() {
270        // This test verifies that every builder method returns Self and chains
271        let _list = gamma()
272            .markets()
273            .list()
274            .limit(25)
275            .offset(50)
276            .order("volume")
277            .ascending(false)
278            .id(vec![1i64, 2, 3])
279            .slug(vec!["slug-a"])
280            .clob_token_ids(vec!["token-1"])
281            .condition_ids(vec!["cond-1"])
282            .market_maker_address(vec!["0xaddr"])
283            .liquidity_num_min(1000.0)
284            .liquidity_num_max(50000.0)
285            .volume_num_min(100.0)
286            .volume_num_max(10000.0)
287            .start_date_min("2024-01-01")
288            .start_date_max("2025-01-01")
289            .end_date_min("2024-06-01")
290            .end_date_max("2025-12-31")
291            .tag_id(42)
292            .related_tags(true)
293            .cyom(false)
294            .uma_resolution_status("resolved")
295            .game_id("game-1")
296            .sports_market_types(vec!["moneyline"])
297            .rewards_min_size(10.0)
298            .question_ids(vec!["q1"])
299            .include_tag(true)
300            .closed(false)
301            .archived(false);
302    }
303
304    #[test]
305    fn test_open_and_closed_are_inverse() {
306        // Both should compile and produce a valid builder
307        let _open = gamma().markets().list().open(true);
308        let _closed = gamma().markets().list().closed(false);
309    }
310
311    #[test]
312    fn test_get_market_accepts_string_and_str() {
313        let _req1 = gamma().markets().get("12345");
314        let _req2 = gamma().markets().get(String::from("12345"));
315    }
316
317    #[test]
318    fn test_get_by_slug_accepts_string_and_str() {
319        let _req1 = gamma().markets().get_by_slug("my-slug");
320        let _req2 = gamma().markets().get_by_slug(String::from("my-slug"));
321    }
322
323    #[test]
324    fn test_get_market_with_include_tag() {
325        let _req = gamma().markets().get("12345").include_tag(true);
326    }
327
328    #[test]
329    fn test_market_tags_accepts_str_and_string() {
330        let _req1 = gamma().markets().tags("12345");
331        let _req2 = gamma().markets().tags(String::from("12345"));
332    }
333}