Skip to main content

polyoxide_gamma/api/
markets.rs

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