use polyoxide_core::{ApiError, HttpClient, QueryBuilder, Request, RequestError};
use crate::{
error::GammaError,
types::{KeysetMarketsResponse, Market, MarketDescription, MarketsInformationBody, Tag},
};
#[derive(Clone)]
pub struct Markets {
pub(crate) http_client: HttpClient,
}
impl Markets {
pub fn get(&self, id: impl Into<String>) -> GetMarket {
GetMarket {
request: Request::new(
self.http_client.clone(),
format!("/markets/{}", urlencoding::encode(&id.into())),
),
}
}
pub fn get_by_slug(&self, slug: impl Into<String>) -> GetMarket {
GetMarket {
request: Request::new(
self.http_client.clone(),
format!("/markets/slug/{}", urlencoding::encode(&slug.into())),
),
}
}
pub fn list(&self) -> ListMarkets {
ListMarkets {
request: Request::new(self.http_client.clone(), "/markets"),
}
}
pub fn get_many(&self, ids: impl IntoIterator<Item = i64>) -> GetManyMarkets {
GetManyMarkets {
http_client: self.http_client.clone(),
ids: ids.into_iter().collect(),
include_tag: None,
}
}
pub fn tags(&self, id: impl Into<String>) -> Request<Vec<Tag>, GammaError> {
Request::new(
self.http_client.clone(),
format!("/markets/{}/tags", urlencoding::encode(&id.into())),
)
}
pub fn get_description(&self, id: impl Into<String>) -> Request<MarketDescription, GammaError> {
Request::new(
self.http_client.clone(),
format!("/markets/{}/description", urlencoding::encode(&id.into())),
)
}
pub fn query_by_information(&self, body: MarketsInformationBody) -> QueryByInformation {
QueryByInformation {
http_client: self.http_client.clone(),
body,
limit: Some(1000),
offset: None,
}
}
pub fn query_abridged(&self, body: MarketsInformationBody) -> QueryAbridged {
QueryAbridged {
http_client: self.http_client.clone(),
body,
limit: Some(1000),
offset: None,
}
}
pub fn list_keyset(&self) -> ListKeysetMarkets {
ListKeysetMarkets {
request: Request::new(self.http_client.clone(), "/markets/keyset"),
}
}
}
async fn post_json<B: serde::Serialize, T: serde::de::DeserializeOwned>(
http: &HttpClient,
path: &str,
body: &B,
query: &[(&str, String)],
) -> Result<T, GammaError> {
let url = http
.base_url
.join(path)
.map_err(|e| GammaError::Api(ApiError::from(e)))?;
http.acquire_rate_limit(path, Some(&reqwest::Method::POST))
.await;
let _permit = http.acquire_concurrency().await;
let response = http
.client
.post(url)
.query(query)
.json(body)
.send()
.await
.map_err(|e| GammaError::Api(ApiError::from(e)))?;
if !response.status().is_success() {
return Err(GammaError::from_response(response).await);
}
let text = response
.text()
.await
.map_err(|e| GammaError::Api(ApiError::from(e)))?;
serde_json::from_str(&text).map_err(|e| GammaError::Api(ApiError::from(e)))
}
fn pagination_query(limit: Option<u32>, offset: Option<u32>) -> Vec<(&'static str, String)> {
let mut query = Vec::new();
if let Some(limit) = limit {
query.push(("limit", limit.to_string()));
}
if let Some(offset) = offset {
query.push(("offset", offset.to_string()));
}
query
}
pub struct QueryByInformation {
http_client: HttpClient,
body: MarketsInformationBody,
limit: Option<u32>,
offset: Option<u32>,
}
impl QueryByInformation {
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.offset = Some(offset);
self
}
pub async fn send(self) -> Result<Vec<Market>, GammaError> {
let query = pagination_query(self.limit, self.offset);
post_json(
&self.http_client,
"/markets/information",
&self.body,
&query,
)
.await
}
}
pub struct QueryAbridged {
http_client: HttpClient,
body: MarketsInformationBody,
limit: Option<u32>,
offset: Option<u32>,
}
impl QueryAbridged {
pub fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.offset = Some(offset);
self
}
pub async fn send(self) -> Result<Vec<Market>, GammaError> {
let query = pagination_query(self.limit, self.offset);
post_json(&self.http_client, "/markets/abridged", &self.body, &query).await
}
}
pub struct GetMarket {
request: Request<Market, GammaError>,
}
impl GetMarket {
pub fn include_tag(mut self, include: bool) -> Self {
self.request = self.request.query("include_tag", include);
self
}
pub async fn send(self) -> Result<Market, GammaError> {
self.request.send().await
}
}
pub struct GetManyMarkets {
http_client: HttpClient,
ids: Vec<i64>,
include_tag: Option<bool>,
}
impl GetManyMarkets {
pub fn include_tag(mut self, include: bool) -> Self {
self.include_tag = Some(include);
self
}
pub async fn send(self) -> Result<Vec<Market>, GammaError> {
if self.ids.is_empty() {
return Ok(Vec::new());
}
let mut req_closed: Request<Vec<Market>, GammaError> =
Request::new(self.http_client.clone(), "/markets")
.query_many("id", self.ids.iter().copied())
.query("closed", true);
let mut req_open: Request<Vec<Market>, GammaError> =
Request::new(self.http_client, "/markets")
.query_many("id", self.ids.iter().copied())
.query("closed", false);
if let Some(include) = self.include_tag {
req_closed = req_closed.query("include_tag", include);
req_open = req_open.query("include_tag", include);
}
let (mut closed_markets, open_markets) =
tokio::try_join!(req_closed.send(), req_open.send())?;
closed_markets.extend(open_markets);
Ok(closed_markets)
}
}
pub struct ListMarkets {
request: Request<Vec<Market>, GammaError>,
}
impl ListMarkets {
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.query("limit", limit);
self
}
pub fn offset(mut self, offset: u32) -> Self {
self.request = self.request.query("offset", offset);
self
}
pub fn order(mut self, order: impl Into<String>) -> Self {
self.request = self.request.query("order", order.into());
self
}
pub fn ascending(mut self, ascending: bool) -> Self {
self.request = self.request.query("ascending", ascending);
self
}
pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
self.request = self.request.query_many("id", ids);
self
}
pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("slug", slugs);
self
}
pub fn clob_token_ids(mut self, token_ids: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("clob_token_ids", token_ids);
self
}
pub fn condition_ids(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("condition_ids", condition_ids);
self
}
pub fn market_maker_address(
mut self,
addresses: impl IntoIterator<Item = impl ToString>,
) -> Self {
self.request = self.request.query_many("market_maker_address", addresses);
self
}
pub fn liquidity_num_min(mut self, min: f64) -> Self {
self.request = self.request.query("liquidity_num_min", min);
self
}
pub fn liquidity_num_max(mut self, max: f64) -> Self {
self.request = self.request.query("liquidity_num_max", max);
self
}
pub fn volume_num_min(mut self, min: f64) -> Self {
self.request = self.request.query("volume_num_min", min);
self
}
pub fn volume_num_max(mut self, max: f64) -> Self {
self.request = self.request.query("volume_num_max", max);
self
}
pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("start_date_min", date.into());
self
}
pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("start_date_max", date.into());
self
}
pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("end_date_min", date.into());
self
}
pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("end_date_max", date.into());
self
}
pub fn tag_id(mut self, tag_id: i64) -> Self {
self.request = self.request.query("tag_id", tag_id);
self
}
pub fn related_tags(mut self, include: bool) -> Self {
self.request = self.request.query("related_tags", include);
self
}
pub fn cyom(mut self, cyom: bool) -> Self {
self.request = self.request.query("cyom", cyom);
self
}
pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
self.request = self.request.query("uma_resolution_status", status.into());
self
}
pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
self.request = self.request.query("game_id", game_id.into());
self
}
pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("sports_market_types", types);
self
}
pub fn rewards_min_size(mut self, min: f64) -> Self {
self.request = self.request.query("rewards_min_size", min);
self
}
pub fn question_ids(mut self, question_ids: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("question_ids", question_ids);
self
}
pub fn include_tag(mut self, include: bool) -> Self {
self.request = self.request.query("include_tag", include);
self
}
pub fn closed(mut self, closed: bool) -> Self {
self.request = self.request.query("closed", closed);
self
}
pub fn open(mut self, open: bool) -> Self {
self.request = self.request.query("closed", !open);
self
}
pub fn archived(mut self, archived: bool) -> Self {
self.request = self.request.query("archived", archived);
self
}
pub async fn send(self) -> Result<Vec<Market>, GammaError> {
self.request.send().await
}
}
pub struct ListKeysetMarkets {
request: Request<KeysetMarketsResponse, GammaError>,
}
impl ListKeysetMarkets {
pub fn limit(mut self, limit: u32) -> Self {
self.request = self.request.query("limit", limit);
self
}
pub fn order(mut self, order: impl Into<String>) -> Self {
self.request = self.request.query("order", order.into());
self
}
pub fn ascending(mut self, ascending: bool) -> Self {
self.request = self.request.query("ascending", ascending);
self
}
pub fn after_cursor(mut self, cursor: impl Into<String>) -> Self {
self.request = self.request.query("after_cursor", cursor.into());
self
}
pub fn id(mut self, ids: impl IntoIterator<Item = i64>) -> Self {
self.request = self.request.query_many("id", ids);
self
}
pub fn slug(mut self, slugs: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("slug", slugs);
self
}
pub fn closed(mut self, closed: bool) -> Self {
self.request = self.request.query("closed", closed);
self
}
pub fn clob_token_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("clob_token_ids", ids);
self
}
pub fn condition_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("condition_ids", ids);
self
}
pub fn question_ids(mut self, ids: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("question_ids", ids);
self
}
pub fn market_maker_address(
mut self,
addresses: impl IntoIterator<Item = impl ToString>,
) -> Self {
self.request = self.request.query_many("market_maker_address", addresses);
self
}
pub fn liquidity_num_min(mut self, min: f64) -> Self {
self.request = self.request.query("liquidity_num_min", min);
self
}
pub fn liquidity_num_max(mut self, max: f64) -> Self {
self.request = self.request.query("liquidity_num_max", max);
self
}
pub fn volume_num_min(mut self, min: f64) -> Self {
self.request = self.request.query("volume_num_min", min);
self
}
pub fn volume_num_max(mut self, max: f64) -> Self {
self.request = self.request.query("volume_num_max", max);
self
}
pub fn start_date_min(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("start_date_min", date.into());
self
}
pub fn start_date_max(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("start_date_max", date.into());
self
}
pub fn end_date_min(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("end_date_min", date.into());
self
}
pub fn end_date_max(mut self, date: impl Into<String>) -> Self {
self.request = self.request.query("end_date_max", date.into());
self
}
pub fn tag_id(mut self, tag_ids: impl IntoIterator<Item = i64>) -> Self {
self.request = self.request.query_many("tag_id", tag_ids);
self
}
pub fn related_tags(mut self, include: bool) -> Self {
self.request = self.request.query("related_tags", include);
self
}
pub fn cyom(mut self, cyom: bool) -> Self {
self.request = self.request.query("cyom", cyom);
self
}
pub fn rfq_enabled(mut self, enabled: bool) -> Self {
self.request = self.request.query("rfq_enabled", enabled);
self
}
pub fn uma_resolution_status(mut self, status: impl Into<String>) -> Self {
self.request = self.request.query("uma_resolution_status", status.into());
self
}
pub fn game_id(mut self, game_id: impl Into<String>) -> Self {
self.request = self.request.query("game_id", game_id.into());
self
}
pub fn sports_market_types(mut self, types: impl IntoIterator<Item = impl ToString>) -> Self {
self.request = self.request.query_many("sports_market_types", types);
self
}
pub fn include_tag(mut self, include: bool) -> Self {
self.request = self.request.query("include_tag", include);
self
}
pub async fn send(self) -> Result<KeysetMarketsResponse, GammaError> {
self.request.send().await
}
}
#[cfg(test)]
mod tests {
use crate::Gamma;
fn gamma() -> Gamma {
Gamma::new().unwrap()
}
#[test]
fn test_list_markets_full_chain() {
let _list = gamma()
.markets()
.list()
.limit(25)
.offset(50)
.order("volume")
.ascending(false)
.id(vec![1i64, 2, 3])
.slug(vec!["slug-a"])
.clob_token_ids(vec!["token-1"])
.condition_ids(vec!["cond-1"])
.market_maker_address(vec!["0xaddr"])
.liquidity_num_min(1000.0)
.liquidity_num_max(50000.0)
.volume_num_min(100.0)
.volume_num_max(10000.0)
.start_date_min("2024-01-01")
.start_date_max("2025-01-01")
.end_date_min("2024-06-01")
.end_date_max("2025-12-31")
.tag_id(42)
.related_tags(true)
.cyom(false)
.uma_resolution_status("resolved")
.game_id("game-1")
.sports_market_types(vec!["moneyline"])
.rewards_min_size(10.0)
.question_ids(vec!["q1"])
.include_tag(true)
.closed(false)
.archived(false);
}
#[test]
fn test_open_and_closed_are_inverse() {
let _open = gamma().markets().list().open(true);
let _closed = gamma().markets().list().closed(false);
}
#[test]
fn test_get_market_accepts_string_and_str() {
let _req1 = gamma().markets().get("12345");
let _req2 = gamma().markets().get(String::from("12345"));
}
#[test]
fn test_get_by_slug_accepts_string_and_str() {
let _req1 = gamma().markets().get_by_slug("my-slug");
let _req2 = gamma().markets().get_by_slug(String::from("my-slug"));
}
#[test]
fn test_get_market_with_include_tag() {
let _req = gamma().markets().get("12345").include_tag(true);
}
#[test]
fn test_market_tags_accepts_str_and_string() {
let _req1 = gamma().markets().tags("12345");
let _req2 = gamma().markets().tags(String::from("12345"));
}
#[test]
fn test_get_many_builds_with_include_tag() {
let _req = gamma()
.markets()
.get_many(vec![1i64, 2, 3])
.include_tag(true);
}
#[test]
fn test_get_description_accepts_str_and_string() {
let _req1 = gamma().markets().get_description("12345");
let _req2 = gamma().markets().get_description(String::from("12345"));
}
#[test]
fn test_list_keyset_full_chain() {
let _req = gamma()
.markets()
.list_keyset()
.limit(50)
.order("volume_num,liquidity_num")
.ascending(false)
.after_cursor("opaque-cursor")
.id(vec![1i64, 2, 3])
.slug(vec!["a-slug"])
.closed(true)
.clob_token_ids(vec!["tok-a"])
.condition_ids(vec!["0xcond"])
.question_ids(vec!["q1"])
.market_maker_address(vec!["0xmm"])
.liquidity_num_min(1.0)
.liquidity_num_max(10.0)
.volume_num_min(1.0)
.volume_num_max(10.0)
.start_date_min("2024-01-01")
.start_date_max("2025-01-01")
.end_date_min("2024-01-01")
.end_date_max("2026-01-01")
.tag_id(vec![1i64, 2])
.related_tags(true)
.cyom(false)
.rfq_enabled(true)
.uma_resolution_status("resolved")
.game_id("game-1")
.sports_market_types(vec!["moneyline"])
.include_tag(true);
}
}