use crate::domain::market::Status;
use crate::domain::orderbook::wire::OrderbookResponse;
use crate::shared::{OrderBookId, PubkeyStr};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OutcomeResponse {
pub index: i16,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name_long: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_high: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DepositAssetResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
pub deposit_asset: String,
pub id: i32,
pub market_pubkey: String,
pub vault: String,
pub num_outcomes: i16,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_high: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata_uri: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decimals: Option<i16>,
pub conditional_mints: Vec<ConditionalTokenResponse>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ConditionalTokenResponse {
pub id: i32,
pub outcome_index: i16,
pub token_address: String,
pub symbol: Option<String>,
pub uri: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deposit_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub short_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_high: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata_uri: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decimals: Option<i16>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct DepositMintsResponse {
pub market_pubkey: String,
pub deposit_assets: Vec<DepositAssetResponse>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum MarketResolutionKind {
SingleWinner,
Scalar,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarketResolutionPayout {
pub outcome_index: i16,
pub payout_numerator: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarketResolutionResponse {
pub kind: MarketResolutionKind,
pub payout_denominator: i64,
pub payouts: Vec<MarketResolutionPayout>,
pub single_winning_outcome: Option<i16>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MarketResponse {
#[serde(skip_serializing_if = "Option::is_none")]
pub market_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slug: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub definition: Option<String>,
pub outcomes: Vec<OutcomeResponse>,
#[serde(skip_serializing_if = "Option::is_none")]
pub banner_image_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub banner_image_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub banner_image_url_high: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_high: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub featured_rank: Option<i16>,
pub market_pubkey: String,
pub market_id: i64,
pub oracle: String,
pub question_id: String,
pub condition_id: String,
pub market_status: String,
#[serde(default)]
pub resolution: Option<MarketResolutionResponse>,
pub created_at: DateTime<Utc>,
pub activated_at: Option<DateTime<Utc>>,
pub settled_at: Option<DateTime<Utc>>,
pub deposit_assets: Vec<DepositAssetResponse>,
pub orderbooks: Vec<OrderbookResponse>,
}
impl MarketResponse {
pub fn is_resolved(&self) -> bool {
self.resolution.is_some()
}
pub fn single_winning_outcome(&self) -> Option<i16> {
self.resolution
.as_ref()
.and_then(|resolution| resolution.single_winning_outcome)
}
pub fn has_single_winning_outcome(&self) -> bool {
self.single_winning_outcome().is_some()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarketsResponse {
pub markets: Vec<MarketResponse>,
pub next_cursor: Option<i64>,
pub has_more: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SingleMarketResponse {
pub market: MarketResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SearchOrderbook {
pub orderbook_id: OrderBookId,
pub outcome_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub outcome_name_long: Option<String>,
pub outcome_index: i16,
pub deposit_base_asset: PubkeyStr,
pub deposit_quote_asset: PubkeyStr,
pub deposit_base_symbol: String,
pub deposit_quote_symbol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_icon_url_high: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quote_icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quote_icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub quote_icon_url_high: Option<String>,
pub conditional_base_mint: PubkeyStr,
pub conditional_quote_mint: PubkeyStr,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome_icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome_icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outcome_icon_url_high: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conditional_base_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub conditional_quote_symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latest_mid_price: Option<Decimal>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MarketSearchResult {
pub slug: String,
pub market_name: String,
pub market_status: Status,
pub category: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
pub featured_rank: i16,
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_high: Option<String>,
pub orderbooks: Vec<SearchOrderbook>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SearchOutcomeGroup {
pub outcome_index: i16,
pub outcome_name: String,
pub outcome_name_long: Option<String>,
pub outcome_icon_url_low: Option<String>,
pub outcome_icon_url_medium: Option<String>,
pub outcome_icon_url_high: Option<String>,
pub orderbooks: Vec<SearchOrderbook>,
pub market_name: String,
pub market_slug: String,
pub market_icon_url_low: Option<String>,
pub market_icon_url_medium: Option<String>,
pub market_icon_url_high: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GlobalDepositAssetResponse {
pub id: i32,
pub mint: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub symbol: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_low: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icon_url_high: Option<String>,
pub decimals: Option<i16>,
pub whitelist_index: i16,
pub active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalDepositAssetsListResponse {
pub assets: Vec<GlobalDepositAssetResponse>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "event_type")]
pub enum MarketEvent {
#[serde(rename = "settled")]
Settled { market_pubkey: String },
#[serde(rename = "created")]
Created { market_pubkey: String },
#[serde(rename = "opened")]
Opened { market_pubkey: String },
#[serde(rename = "paused")]
Paused { market_pubkey: String },
#[serde(rename = "orderbook_created")]
OrderbookCreated {
market_pubkey: String,
orderbook_id: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn market_resolution_deserializes_single_winner() {
let resolution: MarketResolutionResponse = serde_json::from_str(
r#"{
"kind": "single_winner",
"payout_denominator": 1,
"payouts": [
{ "outcome_index": 0, "payout_numerator": 0 },
{ "outcome_index": 1, "payout_numerator": 1 }
],
"single_winning_outcome": 1
}"#,
)
.unwrap();
assert_eq!(resolution.kind, MarketResolutionKind::SingleWinner);
assert_eq!(resolution.payout_denominator, 1);
assert_eq!(resolution.single_winning_outcome, Some(1));
assert_eq!(resolution.payouts[1].payout_numerator, 1);
}
#[test]
fn market_resolution_deserializes_scalar() {
let resolution: MarketResolutionResponse = serde_json::from_str(
r#"{
"kind": "scalar",
"payout_denominator": 10,
"payouts": [
{ "outcome_index": 0, "payout_numerator": 7 },
{ "outcome_index": 1, "payout_numerator": 3 }
],
"single_winning_outcome": null
}"#,
)
.unwrap();
assert_eq!(resolution.kind, MarketResolutionKind::Scalar);
assert_eq!(resolution.payout_denominator, 10);
assert_eq!(resolution.single_winning_outcome, None);
assert_eq!(resolution.payouts[0].payout_numerator, 7);
assert_eq!(resolution.payouts[1].payout_numerator, 3);
}
}