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 fn query_by_information(&self, body: MarketsInformationBody) -> QueryByInformation {
90 QueryByInformation {
91 http_client: self.http_client.clone(),
92 body,
93 limit: Some(1000),
94 offset: None,
95 }
96 }
97
98 pub fn query_abridged(&self, body: MarketsInformationBody) -> QueryAbridged {
105 QueryAbridged {
106 http_client: self.http_client.clone(),
107 body,
108 limit: Some(1000),
109 offset: None,
110 }
111 }
112
113 pub fn list_keyset(&self) -> ListKeysetMarkets {
120 ListKeysetMarkets {
121 request: Request::new(self.http_client.clone(), "/markets/keyset"),
122 }
123 }
124}
125
126async fn post_json<B: serde::Serialize, T: serde::de::DeserializeOwned>(
133 http: &HttpClient,
134 path: &str,
135 body: &B,
136 query: &[(&str, String)],
137) -> Result<T, GammaError> {
138 let url = http
139 .base_url
140 .join(path)
141 .map_err(|e| GammaError::Api(ApiError::from(e)))?;
142
143 http.acquire_rate_limit(path, Some(&reqwest::Method::POST))
144 .await;
145 let _permit = http.acquire_concurrency().await;
146 let response = http
147 .client
148 .post(url)
149 .query(query)
150 .json(body)
151 .send()
152 .await
153 .map_err(|e| GammaError::Api(ApiError::from(e)))?;
154
155 if !response.status().is_success() {
156 return Err(GammaError::from_response(response).await);
157 }
158
159 let text = response
160 .text()
161 .await
162 .map_err(|e| GammaError::Api(ApiError::from(e)))?;
163 serde_json::from_str(&text).map_err(|e| GammaError::Api(ApiError::from(e)))
164}
165
166fn pagination_query(limit: Option<u32>, offset: Option<u32>) -> Vec<(&'static str, String)> {
169 let mut query = Vec::new();
170 if let Some(limit) = limit {
171 query.push(("limit", limit.to_string()));
172 }
173 if let Some(offset) = offset {
174 query.push(("offset", offset.to_string()));
175 }
176 query
177}
178
179pub struct QueryByInformation {
186 http_client: HttpClient,
187 body: MarketsInformationBody,
188 limit: Option<u32>,
189 offset: Option<u32>,
190}
191
192impl QueryByInformation {
193 pub fn limit(mut self, limit: u32) -> Self {
196 self.limit = Some(limit);
197 self
198 }
199
200 pub fn offset(mut self, offset: u32) -> Self {
202 self.offset = Some(offset);
203 self
204 }
205
206 pub async fn send(self) -> Result<Vec<Market>, GammaError> {
208 let query = pagination_query(self.limit, self.offset);
209 post_json(
210 &self.http_client,
211 "/markets/information",
212 &self.body,
213 &query,
214 )
215 .await
216 }
217}
218
219pub struct QueryAbridged {
225 http_client: HttpClient,
226 body: MarketsInformationBody,
227 limit: Option<u32>,
228 offset: Option<u32>,
229}
230
231impl QueryAbridged {
232 pub fn limit(mut self, limit: u32) -> Self {
234 self.limit = Some(limit);
235 self
236 }
237
238 pub fn offset(mut self, offset: u32) -> Self {
240 self.offset = Some(offset);
241 self
242 }
243
244 pub async fn send(self) -> Result<Vec<Market>, GammaError> {
246 let query = pagination_query(self.limit, self.offset);
247 post_json(&self.http_client, "/markets/abridged", &self.body, &query).await
248 }
249}
250
251pub struct GetMarket {
253 request: Request<Market, GammaError>,
254}
255
256impl GetMarket {
257 pub fn include_tag(mut self, include: bool) -> Self {
259 self.request = self.request.query("include_tag", include);
260 self
261 }
262
263 pub async fn send(self) -> Result<Market, GammaError> {
265 self.request.send().await
266 }
267}
268
269pub struct GetManyMarkets {
276 http_client: HttpClient,
277 ids: Vec<i64>,
278 include_tag: Option<bool>,
279}
280
281impl GetManyMarkets {
282 pub fn include_tag(mut self, include: bool) -> Self {
284 self.include_tag = Some(include);
285 self
286 }
287
288 pub async fn send(self) -> Result<Vec<Market>, GammaError> {
294 if self.ids.is_empty() {
295 return Ok(Vec::new());
296 }
297
298 let mut req_closed: Request<Vec<Market>, GammaError> =
299 Request::new(self.http_client.clone(), "/markets")
300 .query_many("id", self.ids.iter().copied())
301 .query("closed", true);
302 let mut req_open: Request<Vec<Market>, GammaError> =
303 Request::new(self.http_client, "/markets")
304 .query_many("id", self.ids.iter().copied())
305 .query("closed", false);
306
307 if let Some(include) = self.include_tag {
308 req_closed = req_closed.query("include_tag", include);
309 req_open = req_open.query("include_tag", include);
310 }
311
312 let (mut closed_markets, open_markets) =
313 tokio::try_join!(req_closed.send(), req_open.send())?;
314 closed_markets.extend(open_markets);
315 Ok(closed_markets)
316 }
317}
318
319pub struct ListMarkets {
321 request: Request<Vec<Market>, GammaError>,
322}
323
324impl ListMarkets {
325 pub fn limit(mut self, limit: u32) -> Self {
327 self.request = self.request.query("limit", limit);
328 self
329 }
330
331 pub fn offset(mut self, offset: u32) -> Self {
333 self.request = self.request.query("offset", offset);
334 self
335 }
336
337 pub fn order(mut self, order: impl Into<String>) -> Self {
339 self.request = self.request.query("order", order.into());
340 self
341 }
342
343 pub fn ascending(mut self, ascending: bool) -> Self {
345 self.request = self.request.query("ascending", ascending);
346 self
347 }
348
349 pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
361 self.request = self.request.query_many("id", ids);
362 self
363 }
364
365 pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
375 self.request = self.request.query_many("slug", slugs);
376 self
377 }
378
379 pub fn clob_token_ids(mut self, token_ids: impl IntoIterator<Item = impl ToString>) -> Self {
384 self.request = self.request.query_many("clob_token_ids", token_ids);
385 self
386 }
387
388 pub fn condition_ids(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
399 self.request = self.request.query_many("condition_ids", condition_ids);
400 self
401 }
402
403 pub fn market_maker_address(
408 mut self,
409 addresses: impl IntoIterator<Item = impl ToString>,
410 ) -> Self {
411 self.request = self.request.query_many("market_maker_address", addresses);
412 self
413 }
414
415 pub fn liquidity_num_min(mut self, min: f64) -> Self {
417 self.request = self.request.query("liquidity_num_min", min);
418 self
419 }
420
421 pub fn liquidity_num_max(mut self, max: f64) -> Self {
423 self.request = self.request.query("liquidity_num_max", max);
424 self
425 }
426
427 pub fn volume_num_min(mut self, min: f64) -> Self {
429 self.request = self.request.query("volume_num_min", min);
430 self
431 }
432
433 pub fn volume_num_max(mut self, max: f64) -> Self {
435 self.request = self.request.query("volume_num_max", max);
436 self
437 }
438
439 pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
441 self.request = self.request.query("start_date_min", date.into());
442 self
443 }
444
445 pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
447 self.request = self.request.query("start_date_max", date.into());
448 self
449 }
450
451 pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
453 self.request = self.request.query("end_date_min", date.into());
454 self
455 }
456
457 pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
459 self.request = self.request.query("end_date_max", date.into());
460 self
461 }
462
463 pub fn tag_id(mut self, tag_id: i64) -> Self {
465 self.request = self.request.query("tag_id", tag_id);
466 self
467 }
468
469 pub fn related_tags(mut self, include: bool) -> Self {
471 self.request = self.request.query("related_tags", include);
472 self
473 }
474
475 pub fn cyom(mut self, cyom: bool) -> Self {
477 self.request = self.request.query("cyom", cyom);
478 self
479 }
480
481 pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
483 self.request = self.request.query("uma_resolution_status", status.into());
484 self
485 }
486
487 pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
489 self.request = self.request.query("game_id", game_id.into());
490 self
491 }
492
493 pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
498 self.request = self.request.query_many("sports_market_types", types);
499 self
500 }
501
502 pub fn rewards_min_size(mut self, min: f64) -> Self {
504 self.request = self.request.query("rewards_min_size", min);
505 self
506 }
507
508 pub fn question_ids(mut self, question_ids: impl IntoIterator<Item = impl ToString>) -> Self {
513 self.request = self.request.query_many("question_ids", question_ids);
514 self
515 }
516
517 pub fn include_tag(mut self, include: bool) -> Self {
519 self.request = self.request.query("include_tag", include);
520 self
521 }
522
523 pub fn closed(mut self, closed: bool) -> Self {
525 self.request = self.request.query("closed", closed);
526 self
527 }
528
529 pub fn open(mut self, open: bool) -> Self {
531 self.request = self.request.query("closed", !open);
532 self
533 }
534
535 pub fn archived(mut self, archived: bool) -> Self {
537 self.request = self.request.query("archived", archived);
538 self
539 }
540
541 pub async fn send(self) -> Result<Vec<Market>, GammaError> {
543 self.request.send().await
544 }
545}
546
547pub struct ListKeysetMarkets {
549 request: Request<KeysetMarketsResponse, GammaError>,
550}
551
552impl ListKeysetMarkets {
553 pub fn limit(mut self, limit: u32) -> Self {
555 self.request = self.request.query("limit", limit);
556 self
557 }
558
559 pub fn order(mut self, order: impl Into<String>) -> Self {
561 self.request = self.request.query("order", order.into());
562 self
563 }
564
565 pub fn ascending(mut self, ascending: bool) -> Self {
567 self.request = self.request.query("ascending", ascending);
568 self
569 }
570
571 pub fn after_cursor(mut self, cursor: impl Into<String>) -> Self {
573 self.request = self.request.query("after_cursor", cursor.into());
574 self
575 }
576
577 pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
579 self.request = self.request.query_many("id", ids);
580 self
581 }
582
583 pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
585 self.request = self.request.query_many("slug", slugs);
586 self
587 }
588
589 pub fn closed(mut self, closed: bool) -> Self {
591 self.request = self.request.query("closed", closed);
592 self
593 }
594
595 pub fn clob_token_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
597 self.request = self.request.query_many("clob_token_ids", ids);
598 self
599 }
600
601 pub fn condition_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
603 self.request = self.request.query_many("condition_ids", ids);
604 self
605 }
606
607 pub fn question_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
609 self.request = self.request.query_many("question_ids", ids);
610 self
611 }
612
613 pub fn market_maker_address(
615 mut self,
616 addresses: impl IntoIterator<Item = impl ToString>,
617 ) -> Self {
618 self.request = self.request.query_many("market_maker_address", addresses);
619 self
620 }
621
622 pub fn liquidity_num_min(mut self, min: f64) -> Self {
624 self.request = self.request.query("liquidity_num_min", min);
625 self
626 }
627
628 pub fn liquidity_num_max(mut self, max: f64) -> Self {
630 self.request = self.request.query("liquidity_num_max", max);
631 self
632 }
633
634 pub fn volume_num_min(mut self, min: f64) -> Self {
636 self.request = self.request.query("volume_num_min", min);
637 self
638 }
639
640 pub fn volume_num_max(mut self, max: f64) -> Self {
642 self.request = self.request.query("volume_num_max", max);
643 self
644 }
645
646 pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
648 self.request = self.request.query("start_date_min", date.into());
649 self
650 }
651
652 pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
654 self.request = self.request.query("start_date_max", date.into());
655 self
656 }
657
658 pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
660 self.request = self.request.query("end_date_min", date.into());
661 self
662 }
663
664 pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
666 self.request = self.request.query("end_date_max", date.into());
667 self
668 }
669
670 pub fn tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
672 self.request = self.request.query_many("tag_id", tag_ids);
673 self
674 }
675
676 pub fn related_tags(mut self, include: bool) -> Self {
678 self.request = self.request.query("related_tags", include);
679 self
680 }
681
682 pub fn cyom(mut self, cyom: bool) -> Self {
684 self.request = self.request.query("cyom", cyom);
685 self
686 }
687
688 pub fn rfq_enabled(mut self, enabled: bool) -> Self {
690 self.request = self.request.query("rfq_enabled", enabled);
691 self
692 }
693
694 pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
696 self.request = self.request.query("uma_resolution_status", status.into());
697 self
698 }
699
700 pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
702 self.request = self.request.query("game_id", game_id.into());
703 self
704 }
705
706 pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
708 self.request = self.request.query_many("sports_market_types", types);
709 self
710 }
711
712 pub fn include_tag(mut self, include: bool) -> Self {
714 self.request = self.request.query("include_tag", include);
715 self
716 }
717
718 pub async fn send(self) -> Result<KeysetMarketsResponse, GammaError> {
720 self.request.send().await
721 }
722}
723
724#[cfg(test)]
725mod tests {
726 use crate::Gamma;
727
728 fn gamma() -> Gamma {
729 Gamma::new().unwrap()
730 }
731
732 #[test]
735 fn test_list_markets_full_chain() {
736 let _list = gamma()
738 .markets()
739 .list()
740 .limit(25)
741 .offset(50)
742 .order("volume")
743 .ascending(false)
744 .id(vec![1i64, 2, 3])
745 .slug(vec!["slug-a"])
746 .clob_token_ids(vec!["token-1"])
747 .condition_ids(vec!["cond-1"])
748 .market_maker_address(vec!["0xaddr"])
749 .liquidity_num_min(1000.0)
750 .liquidity_num_max(50000.0)
751 .volume_num_min(100.0)
752 .volume_num_max(10000.0)
753 .start_date_min("2024-01-01")
754 .start_date_max("2025-01-01")
755 .end_date_min("2024-06-01")
756 .end_date_max("2025-12-31")
757 .tag_id(42)
758 .related_tags(true)
759 .cyom(false)
760 .uma_resolution_status("resolved")
761 .game_id("game-1")
762 .sports_market_types(vec!["moneyline"])
763 .rewards_min_size(10.0)
764 .question_ids(vec!["q1"])
765 .include_tag(true)
766 .closed(false)
767 .archived(false);
768 }
769
770 #[test]
771 fn test_open_and_closed_are_inverse() {
772 let _open = gamma().markets().list().open(true);
774 let _closed = gamma().markets().list().closed(false);
775 }
776
777 #[test]
778 fn test_get_market_accepts_string_and_str() {
779 let _req1 = gamma().markets().get("12345");
780 let _req2 = gamma().markets().get(String::from("12345"));
781 }
782
783 #[test]
784 fn test_get_by_slug_accepts_string_and_str() {
785 let _req1 = gamma().markets().get_by_slug("my-slug");
786 let _req2 = gamma().markets().get_by_slug(String::from("my-slug"));
787 }
788
789 #[test]
790 fn test_get_market_with_include_tag() {
791 let _req = gamma().markets().get("12345").include_tag(true);
792 }
793
794 #[test]
795 fn test_market_tags_accepts_str_and_string() {
796 let _req1 = gamma().markets().tags("12345");
797 let _req2 = gamma().markets().tags(String::from("12345"));
798 }
799
800 #[test]
801 fn test_get_many_builds_with_include_tag() {
802 let _req = gamma()
803 .markets()
804 .get_many(vec![1i64, 2, 3])
805 .include_tag(true);
806 }
807
808 #[test]
811 fn test_get_description_accepts_str_and_string() {
812 let _req1 = gamma().markets().get_description("12345");
813 let _req2 = gamma().markets().get_description(String::from("12345"));
814 }
815
816 #[test]
817 fn test_list_keyset_full_chain() {
818 let _req = gamma()
819 .markets()
820 .list_keyset()
821 .limit(50)
822 .order("volume_num,liquidity_num")
823 .ascending(false)
824 .after_cursor("opaque-cursor")
825 .id(vec![1i64, 2, 3])
826 .slug(vec!["a-slug"])
827 .closed(true)
828 .clob_token_ids(vec!["tok-a"])
829 .condition_ids(vec!["0xcond"])
830 .question_ids(vec!["q1"])
831 .market_maker_address(vec!["0xmm"])
832 .liquidity_num_min(1.0)
833 .liquidity_num_max(10.0)
834 .volume_num_min(1.0)
835 .volume_num_max(10.0)
836 .start_date_min("2024-01-01")
837 .start_date_max("2025-01-01")
838 .end_date_min("2024-01-01")
839 .end_date_max("2026-01-01")
840 .tag_id(vec![1i64, 2])
841 .related_tags(true)
842 .cyom(false)
843 .rfq_enabled(true)
844 .uma_resolution_status("resolved")
845 .game_id("game-1")
846 .sports_market_types(vec!["moneyline"])
847 .include_tag(true);
848 }
849}