1use polyoxide_core::{ApiError, HttpClient, QueryBuilder, Request, RequestError};
2
3use crate::{
4 error::GammaError,
5 types::{KeysetMarketsResponse, Market, MarketDescription, MarketsInformationBody, Tag},
6};
7
8#[derive(Clone)]
10pub struct Markets {
11 pub(crate) http_client: HttpClient,
12}
13
14impl Markets {
15 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 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 pub fn list(&self) -> ListMarkets {
37 ListMarkets {
38 request: Request::new(self.http_client.clone(), "/markets"),
39 }
40 }
41
42 pub fn get_many(&self, ids: impl IntoIterator<Item = i64>) -> GetManyMarkets {
55 GetManyMarkets {
56 http_client: self.http_client.clone(),
57 ids: ids.into_iter().collect(),
58 include_tag: None,
59 }
60 }
61
62 pub fn tags(&self, id: impl Into<String>) -> Request<Vec<Tag>, GammaError> {
64 Request::new(
65 self.http_client.clone(),
66 format!("/markets/{}/tags", urlencoding::encode(&id.into())),
67 )
68 }
69
70 pub fn get_description(&self, id: impl Into<String>) -> Request<MarketDescription, GammaError> {
72 Request::new(
73 self.http_client.clone(),
74 format!("/markets/{}/description", urlencoding::encode(&id.into())),
75 )
76 }
77
78 pub async fn query_by_information(
84 &self,
85 body: &MarketsInformationBody,
86 ) -> Result<Vec<Market>, GammaError> {
87 post_json(&self.http_client, "/markets/information", body).await
88 }
89
90 pub async fn query_abridged(
93 &self,
94 body: &MarketsInformationBody,
95 ) -> Result<Vec<Market>, GammaError> {
96 post_json(&self.http_client, "/markets/abridged", body).await
97 }
98
99 pub fn list_keyset(&self) -> ListKeysetMarkets {
106 ListKeysetMarkets {
107 request: Request::new(self.http_client.clone(), "/markets/keyset"),
108 }
109 }
110}
111
112async fn post_json<B: serde::Serialize, T: serde::de::DeserializeOwned>(
115 http: &HttpClient,
116 path: &str,
117 body: &B,
118) -> Result<T, GammaError> {
119 let url = http
120 .base_url
121 .join(path)
122 .map_err(|e| GammaError::Api(ApiError::from(e)))?;
123
124 http.acquire_rate_limit(path, Some(&reqwest::Method::POST))
125 .await;
126 let _permit = http.acquire_concurrency().await;
127 let response = http
128 .client
129 .post(url)
130 .json(body)
131 .send()
132 .await
133 .map_err(|e| GammaError::Api(ApiError::from(e)))?;
134
135 if !response.status().is_success() {
136 return Err(GammaError::from_response(response).await);
137 }
138
139 let text = response
140 .text()
141 .await
142 .map_err(|e| GammaError::Api(ApiError::from(e)))?;
143 serde_json::from_str(&text).map_err(|e| GammaError::Api(ApiError::from(e)))
144}
145
146pub struct GetMarket {
148 request: Request<Market, GammaError>,
149}
150
151impl GetMarket {
152 pub fn include_tag(mut self, include: bool) -> Self {
154 self.request = self.request.query("include_tag", include);
155 self
156 }
157
158 pub async fn send(self) -> Result<Market, GammaError> {
160 self.request.send().await
161 }
162}
163
164pub struct GetManyMarkets {
171 http_client: HttpClient,
172 ids: Vec<i64>,
173 include_tag: Option<bool>,
174}
175
176impl GetManyMarkets {
177 pub fn include_tag(mut self, include: bool) -> Self {
179 self.include_tag = Some(include);
180 self
181 }
182
183 pub async fn send(self) -> Result<Vec<Market>, GammaError> {
189 if self.ids.is_empty() {
190 return Ok(Vec::new());
191 }
192
193 let mut req_closed: Request<Vec<Market>, GammaError> =
194 Request::new(self.http_client.clone(), "/markets")
195 .query_many("id", self.ids.iter().copied())
196 .query("closed", true);
197 let mut req_open: Request<Vec<Market>, GammaError> =
198 Request::new(self.http_client, "/markets")
199 .query_many("id", self.ids.iter().copied())
200 .query("closed", false);
201
202 if let Some(include) = self.include_tag {
203 req_closed = req_closed.query("include_tag", include);
204 req_open = req_open.query("include_tag", include);
205 }
206
207 let (mut closed_markets, open_markets) =
208 tokio::try_join!(req_closed.send(), req_open.send())?;
209 closed_markets.extend(open_markets);
210 Ok(closed_markets)
211 }
212}
213
214pub struct ListMarkets {
216 request: Request<Vec<Market>, GammaError>,
217}
218
219impl ListMarkets {
220 pub fn limit(mut self, limit: u32) -> Self {
222 self.request = self.request.query("limit", limit);
223 self
224 }
225
226 pub fn offset(mut self, offset: u32) -> Self {
228 self.request = self.request.query("offset", offset);
229 self
230 }
231
232 pub fn order(mut self, order: impl Into<String>) -> Self {
234 self.request = self.request.query("order", order.into());
235 self
236 }
237
238 pub fn ascending(mut self, ascending: bool) -> Self {
240 self.request = self.request.query("ascending", ascending);
241 self
242 }
243
244 pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
256 self.request = self.request.query_many("id", ids);
257 self
258 }
259
260 pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
270 self.request = self.request.query_many("slug", slugs);
271 self
272 }
273
274 pub fn clob_token_ids(mut self, token_ids: impl IntoIterator<Item = impl ToString>) -> Self {
279 self.request = self.request.query_many("clob_token_ids", token_ids);
280 self
281 }
282
283 pub fn condition_ids(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
294 self.request = self.request.query_many("condition_ids", condition_ids);
295 self
296 }
297
298 pub fn market_maker_address(
303 mut self,
304 addresses: impl IntoIterator<Item = impl ToString>,
305 ) -> Self {
306 self.request = self.request.query_many("market_maker_address", addresses);
307 self
308 }
309
310 pub fn liquidity_num_min(mut self, min: f64) -> Self {
312 self.request = self.request.query("liquidity_num_min", min);
313 self
314 }
315
316 pub fn liquidity_num_max(mut self, max: f64) -> Self {
318 self.request = self.request.query("liquidity_num_max", max);
319 self
320 }
321
322 pub fn volume_num_min(mut self, min: f64) -> Self {
324 self.request = self.request.query("volume_num_min", min);
325 self
326 }
327
328 pub fn volume_num_max(mut self, max: f64) -> Self {
330 self.request = self.request.query("volume_num_max", max);
331 self
332 }
333
334 pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
336 self.request = self.request.query("start_date_min", date.into());
337 self
338 }
339
340 pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
342 self.request = self.request.query("start_date_max", date.into());
343 self
344 }
345
346 pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
348 self.request = self.request.query("end_date_min", date.into());
349 self
350 }
351
352 pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
354 self.request = self.request.query("end_date_max", date.into());
355 self
356 }
357
358 pub fn tag_id(mut self, tag_id: i64) -> Self {
360 self.request = self.request.query("tag_id", tag_id);
361 self
362 }
363
364 pub fn related_tags(mut self, include: bool) -> Self {
366 self.request = self.request.query("related_tags", include);
367 self
368 }
369
370 pub fn cyom(mut self, cyom: bool) -> Self {
372 self.request = self.request.query("cyom", cyom);
373 self
374 }
375
376 pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
378 self.request = self.request.query("uma_resolution_status", status.into());
379 self
380 }
381
382 pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
384 self.request = self.request.query("game_id", game_id.into());
385 self
386 }
387
388 pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
393 self.request = self.request.query_many("sports_market_types", types);
394 self
395 }
396
397 pub fn rewards_min_size(mut self, min: f64) -> Self {
399 self.request = self.request.query("rewards_min_size", min);
400 self
401 }
402
403 pub fn question_ids(mut self, question_ids: impl IntoIterator<Item = impl ToString>) -> Self {
408 self.request = self.request.query_many("question_ids", question_ids);
409 self
410 }
411
412 pub fn include_tag(mut self, include: bool) -> Self {
414 self.request = self.request.query("include_tag", include);
415 self
416 }
417
418 pub fn closed(mut self, closed: bool) -> Self {
420 self.request = self.request.query("closed", closed);
421 self
422 }
423
424 pub fn open(mut self, open: bool) -> Self {
426 self.request = self.request.query("closed", !open);
427 self
428 }
429
430 pub fn archived(mut self, archived: bool) -> Self {
432 self.request = self.request.query("archived", archived);
433 self
434 }
435
436 pub async fn send(self) -> Result<Vec<Market>, GammaError> {
438 self.request.send().await
439 }
440}
441
442pub struct ListKeysetMarkets {
444 request: Request<KeysetMarketsResponse, GammaError>,
445}
446
447impl ListKeysetMarkets {
448 pub fn limit(mut self, limit: u32) -> Self {
450 self.request = self.request.query("limit", limit);
451 self
452 }
453
454 pub fn order(mut self, order: impl Into<String>) -> Self {
456 self.request = self.request.query("order", order.into());
457 self
458 }
459
460 pub fn ascending(mut self, ascending: bool) -> Self {
462 self.request = self.request.query("ascending", ascending);
463 self
464 }
465
466 pub fn after_cursor(mut self, cursor: impl Into<String>) -> Self {
468 self.request = self.request.query("after_cursor", cursor.into());
469 self
470 }
471
472 pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
474 self.request = self.request.query_many("id", ids);
475 self
476 }
477
478 pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
480 self.request = self.request.query_many("slug", slugs);
481 self
482 }
483
484 pub fn closed(mut self, closed: bool) -> Self {
486 self.request = self.request.query("closed", closed);
487 self
488 }
489
490 pub fn clob_token_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
492 self.request = self.request.query_many("clob_token_ids", ids);
493 self
494 }
495
496 pub fn condition_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
498 self.request = self.request.query_many("condition_ids", ids);
499 self
500 }
501
502 pub fn question_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
504 self.request = self.request.query_many("question_ids", ids);
505 self
506 }
507
508 pub fn market_maker_address(
510 mut self,
511 addresses: impl IntoIterator<Item = impl ToString>,
512 ) -> Self {
513 self.request = self.request.query_many("market_maker_address", addresses);
514 self
515 }
516
517 pub fn liquidity_num_min(mut self, min: f64) -> Self {
519 self.request = self.request.query("liquidity_num_min", min);
520 self
521 }
522
523 pub fn liquidity_num_max(mut self, max: f64) -> Self {
525 self.request = self.request.query("liquidity_num_max", max);
526 self
527 }
528
529 pub fn volume_num_min(mut self, min: f64) -> Self {
531 self.request = self.request.query("volume_num_min", min);
532 self
533 }
534
535 pub fn volume_num_max(mut self, max: f64) -> Self {
537 self.request = self.request.query("volume_num_max", max);
538 self
539 }
540
541 pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
543 self.request = self.request.query("start_date_min", date.into());
544 self
545 }
546
547 pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
549 self.request = self.request.query("start_date_max", date.into());
550 self
551 }
552
553 pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
555 self.request = self.request.query("end_date_min", date.into());
556 self
557 }
558
559 pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
561 self.request = self.request.query("end_date_max", date.into());
562 self
563 }
564
565 pub fn tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
567 self.request = self.request.query_many("tag_id", tag_ids);
568 self
569 }
570
571 pub fn related_tags(mut self, include: bool) -> Self {
573 self.request = self.request.query("related_tags", include);
574 self
575 }
576
577 pub fn cyom(mut self, cyom: bool) -> Self {
579 self.request = self.request.query("cyom", cyom);
580 self
581 }
582
583 pub fn rfq_enabled(mut self, enabled: bool) -> Self {
585 self.request = self.request.query("rfq_enabled", enabled);
586 self
587 }
588
589 pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
591 self.request = self.request.query("uma_resolution_status", status.into());
592 self
593 }
594
595 pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
597 self.request = self.request.query("game_id", game_id.into());
598 self
599 }
600
601 pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
603 self.request = self.request.query_many("sports_market_types", types);
604 self
605 }
606
607 pub fn include_tag(mut self, include: bool) -> Self {
609 self.request = self.request.query("include_tag", include);
610 self
611 }
612
613 pub async fn send(self) -> Result<KeysetMarketsResponse, GammaError> {
615 self.request.send().await
616 }
617}
618
619#[cfg(test)]
620mod tests {
621 use crate::Gamma;
622
623 fn gamma() -> Gamma {
624 Gamma::new().unwrap()
625 }
626
627 #[test]
630 fn test_list_markets_full_chain() {
631 let _list = gamma()
633 .markets()
634 .list()
635 .limit(25)
636 .offset(50)
637 .order("volume")
638 .ascending(false)
639 .id(vec![1i64, 2, 3])
640 .slug(vec!["slug-a"])
641 .clob_token_ids(vec!["token-1"])
642 .condition_ids(vec!["cond-1"])
643 .market_maker_address(vec!["0xaddr"])
644 .liquidity_num_min(1000.0)
645 .liquidity_num_max(50000.0)
646 .volume_num_min(100.0)
647 .volume_num_max(10000.0)
648 .start_date_min("2024-01-01")
649 .start_date_max("2025-01-01")
650 .end_date_min("2024-06-01")
651 .end_date_max("2025-12-31")
652 .tag_id(42)
653 .related_tags(true)
654 .cyom(false)
655 .uma_resolution_status("resolved")
656 .game_id("game-1")
657 .sports_market_types(vec!["moneyline"])
658 .rewards_min_size(10.0)
659 .question_ids(vec!["q1"])
660 .include_tag(true)
661 .closed(false)
662 .archived(false);
663 }
664
665 #[test]
666 fn test_open_and_closed_are_inverse() {
667 let _open = gamma().markets().list().open(true);
669 let _closed = gamma().markets().list().closed(false);
670 }
671
672 #[test]
673 fn test_get_market_accepts_string_and_str() {
674 let _req1 = gamma().markets().get("12345");
675 let _req2 = gamma().markets().get(String::from("12345"));
676 }
677
678 #[test]
679 fn test_get_by_slug_accepts_string_and_str() {
680 let _req1 = gamma().markets().get_by_slug("my-slug");
681 let _req2 = gamma().markets().get_by_slug(String::from("my-slug"));
682 }
683
684 #[test]
685 fn test_get_market_with_include_tag() {
686 let _req = gamma().markets().get("12345").include_tag(true);
687 }
688
689 #[test]
690 fn test_market_tags_accepts_str_and_string() {
691 let _req1 = gamma().markets().tags("12345");
692 let _req2 = gamma().markets().tags(String::from("12345"));
693 }
694
695 #[test]
696 fn test_get_many_builds_with_include_tag() {
697 let _req = gamma()
698 .markets()
699 .get_many(vec![1i64, 2, 3])
700 .include_tag(true);
701 }
702
703 #[test]
706 fn test_get_description_accepts_str_and_string() {
707 let _req1 = gamma().markets().get_description("12345");
708 let _req2 = gamma().markets().get_description(String::from("12345"));
709 }
710
711 #[test]
712 fn test_list_keyset_full_chain() {
713 let _req = gamma()
714 .markets()
715 .list_keyset()
716 .limit(50)
717 .order("volume_num,liquidity_num")
718 .ascending(false)
719 .after_cursor("opaque-cursor")
720 .id(vec![1i64, 2, 3])
721 .slug(vec!["a-slug"])
722 .closed(true)
723 .clob_token_ids(vec!["tok-a"])
724 .condition_ids(vec!["0xcond"])
725 .question_ids(vec!["q1"])
726 .market_maker_address(vec!["0xmm"])
727 .liquidity_num_min(1.0)
728 .liquidity_num_max(10.0)
729 .volume_num_min(1.0)
730 .volume_num_max(10.0)
731 .start_date_min("2024-01-01")
732 .start_date_max("2025-01-01")
733 .end_date_min("2024-01-01")
734 .end_date_max("2026-01-01")
735 .tag_id(vec![1i64, 2])
736 .related_tags(true)
737 .cyom(false)
738 .rfq_enabled(true)
739 .uma_resolution_status("resolved")
740 .game_id("game-1")
741 .sports_market_types(vec!["moneyline"])
742 .include_tag(true);
743 }
744}