use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserValue {
pub user: String,
pub value: f64,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenInterest {
pub market: String,
pub value: f64,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PositionSortBy {
Current,
Initial,
Tokens,
CashPnl,
PercentPnl,
Title,
Resolving,
Price,
AvgPrice,
}
impl std::fmt::Display for PositionSortBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Current => write!(f, "CURRENT"),
Self::Initial => write!(f, "INITIAL"),
Self::Tokens => write!(f, "TOKENS"),
Self::CashPnl => write!(f, "CASH_PNL"),
Self::PercentPnl => write!(f, "PERCENT_PNL"),
Self::Title => write!(f, "TITLE"),
Self::Resolving => write!(f, "RESOLVING"),
Self::Price => write!(f, "PRICE"),
Self::AvgPrice => write!(f, "AVG_PRICE"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum SortDirection {
Asc,
#[default]
Desc,
}
impl std::fmt::Display for SortDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Asc => write!(f, "ASC"),
Self::Desc => write!(f, "DESC"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ClosedPositionSortBy {
#[default]
RealizedPnl,
Title,
Price,
AvgPrice,
Timestamp,
}
impl std::fmt::Display for ClosedPositionSortBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::RealizedPnl => write!(f, "REALIZED_PNL"),
Self::Title => write!(f, "TITLE"),
Self::Price => write!(f, "PRICE"),
Self::AvgPrice => write!(f, "AVG_PRICE"),
Self::Timestamp => write!(f, "TIMESTAMP"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClosedPosition {
pub proxy_wallet: String,
pub asset: String,
pub condition_id: String,
pub avg_price: f64,
pub total_bought: f64,
pub realized_pnl: f64,
pub cur_price: f64,
#[cfg_attr(feature = "specta", specta(type = f64))]
pub timestamp: i64,
pub title: String,
pub slug: String,
pub icon: Option<String>,
pub event_slug: Option<String>,
pub outcome: String,
pub outcome_index: u32,
pub opposite_outcome: String,
pub opposite_asset: String,
pub end_date: Option<String>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TradeSide {
Buy,
Sell,
#[serde(other)]
Unknown,
}
impl std::fmt::Display for TradeSide {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Buy => write!(f, "BUY"),
Self::Sell => write!(f, "SELL"),
Self::Unknown => write!(f, "UNKNOWN"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TradeFilterType {
Cash,
Tokens,
}
impl std::fmt::Display for TradeFilterType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Cash => write!(f, "CASH"),
Self::Tokens => write!(f, "TOKENS"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Trade {
pub proxy_wallet: String,
pub side: TradeSide,
pub asset: String,
pub condition_id: String,
pub size: f64,
pub price: f64,
#[cfg_attr(feature = "specta", specta(type = f64))]
pub timestamp: i64,
pub title: String,
pub slug: String,
pub icon: Option<String>,
pub event_slug: Option<String>,
pub outcome: String,
pub outcome_index: u32,
pub name: Option<String>,
pub pseudonym: Option<String>,
pub bio: Option<String>,
pub profile_image: Option<String>,
pub profile_image_optimized: Option<String>,
pub transaction_hash: Option<String>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum ActivityType {
Trade,
Split,
Merge,
Redeem,
Reward,
Conversion,
Deposit,
Withdrawal,
Yield,
#[serde(rename = "MAKER_REBATE")]
MakerRebate,
#[serde(rename = "REFERRAL_REWARD")]
ReferralReward,
#[serde(rename = "TAKER_REBATE")]
TakerRebate,
#[serde(other)]
Unknown,
}
impl std::fmt::Display for ActivityType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Trade => write!(f, "TRADE"),
Self::Split => write!(f, "SPLIT"),
Self::Merge => write!(f, "MERGE"),
Self::Redeem => write!(f, "REDEEM"),
Self::Reward => write!(f, "REWARD"),
Self::Conversion => write!(f, "CONVERSION"),
Self::Deposit => write!(f, "DEPOSIT"),
Self::Withdrawal => write!(f, "WITHDRAWAL"),
Self::Yield => write!(f, "YIELD"),
Self::MakerRebate => write!(f, "MAKER_REBATE"),
Self::ReferralReward => write!(f, "REFERRAL_REWARD"),
Self::TakerRebate => write!(f, "TAKER_REBATE"),
Self::Unknown => write!(f, "UNKNOWN"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ActivitySortBy {
#[default]
Timestamp,
Tokens,
Cash,
}
impl std::fmt::Display for ActivitySortBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Timestamp => write!(f, "TIMESTAMP"),
Self::Tokens => write!(f, "TOKENS"),
Self::Cash => write!(f, "CASH"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Activity {
pub proxy_wallet: String,
#[cfg_attr(feature = "specta", specta(type = f64))]
pub timestamp: i64,
pub condition_id: String,
#[serde(rename = "type")]
pub activity_type: ActivityType,
pub size: f64,
pub usdc_size: f64,
pub transaction_hash: Option<String>,
pub price: Option<f64>,
pub asset: Option<String>,
pub side: Option<String>,
pub outcome_index: Option<u32>,
pub title: Option<String>,
pub slug: Option<String>,
pub icon: Option<String>,
pub outcome: Option<String>,
pub name: Option<String>,
pub pseudonym: Option<String>,
pub bio: Option<String>,
pub profile_image: Option<String>,
pub profile_image_optimized: Option<String>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Position {
pub proxy_wallet: String,
pub asset: String,
pub condition_id: String,
pub size: f64,
pub avg_price: f64,
pub initial_value: f64,
pub current_value: f64,
pub cash_pnl: f64,
pub percent_pnl: f64,
pub total_bought: f64,
pub realized_pnl: f64,
pub percent_realized_pnl: f64,
pub cur_price: f64,
pub redeemable: bool,
pub mergeable: bool,
pub title: String,
pub slug: String,
pub icon: Option<String>,
pub event_slug: Option<String>,
pub outcome: String,
pub outcome_index: u32,
pub opposite_outcome: String,
pub opposite_asset: String,
pub end_date: Option<String>,
pub negative_risk: bool,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarketPositionV1 {
pub proxy_wallet: String,
pub name: String,
pub profile_image: Option<String>,
pub verified: bool,
pub asset: String,
pub condition_id: String,
pub avg_price: f64,
pub size: f64,
#[serde(rename = "currPrice")]
pub curr_price: f64,
pub current_value: f64,
pub cash_pnl: f64,
pub total_bought: f64,
pub realized_pnl: f64,
pub total_pnl: f64,
pub outcome: String,
pub outcome_index: u32,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaMarketPositionV1 {
pub token: String,
pub positions: Vec<MarketPositionV1>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum MarketPositionStatus {
Open,
Closed,
#[default]
All,
}
impl std::fmt::Display for MarketPositionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Open => write!(f, "OPEN"),
Self::Closed => write!(f, "CLOSED"),
Self::All => write!(f, "ALL"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MarketPositionSortBy {
Tokens,
CashPnl,
RealizedPnl,
#[default]
TotalPnl,
}
impl std::fmt::Display for MarketPositionSortBy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Tokens => write!(f, "TOKENS"),
Self::CashPnl => write!(f, "CASH_PNL"),
Self::RealizedPnl => write!(f, "REALIZED_PNL"),
Self::TotalPnl => write!(f, "TOTAL_PNL"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "UPPERCASE")]
pub enum TimePeriod {
#[default]
Day,
Week,
Month,
All,
}
impl std::fmt::Display for TimePeriod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Day => write!(f, "DAY"),
Self::Week => write!(f, "WEEK"),
Self::Month => write!(f, "MONTH"),
Self::All => write!(f, "ALL"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ComboStatus {
Open,
Partial,
ResolvedPartial,
ResolvedWin,
ResolvedLoss,
#[serde(other)]
Unknown,
}
impl std::fmt::Display for ComboStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Open => write!(f, "OPEN"),
Self::Partial => write!(f, "PARTIAL"),
Self::ResolvedPartial => write!(f, "RESOLVED_PARTIAL"),
Self::ResolvedWin => write!(f, "RESOLVED_WIN"),
Self::ResolvedLoss => write!(f, "RESOLVED_LOSS"),
Self::Unknown => write!(f, "UNKNOWN"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ComboLegStatus {
Open,
ResolvedPartial,
ResolvedWin,
ResolvedLoss,
#[serde(other)]
Unknown,
}
impl std::fmt::Display for ComboLegStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Open => write!(f, "OPEN"),
Self::ResolvedPartial => write!(f, "RESOLVED_PARTIAL"),
Self::ResolvedWin => write!(f, "RESOLVED_WIN"),
Self::ResolvedLoss => write!(f, "RESOLVED_LOSS"),
Self::Unknown => write!(f, "UNKNOWN"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum ComboSort {
#[default]
CurrentValueDesc,
EntryCostDesc,
FirstEntryDesc,
ResolvedAtDesc,
UpdatedAsc,
}
impl std::fmt::Display for ComboSort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CurrentValueDesc => write!(f, "current_value_desc"),
Self::EntryCostDesc => write!(f, "entry_cost_desc"),
Self::FirstEntryDesc => write!(f, "first_entry_desc"),
Self::ResolvedAtDesc => write!(f, "resolved_at_desc"),
Self::UpdatedAsc => write!(f, "updated_asc"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pagination {
pub limit: i64,
pub offset: i64,
pub has_more: bool,
pub next_cursor: Option<String>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComboEvent {
pub event_id: Option<String>,
pub event_slug: Option<String>,
pub event_title: Option<String>,
pub event_image: Option<String>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComboMarket {
pub market_id: Option<String>,
pub slug: Option<String>,
pub title: Option<String>,
pub outcome: Option<String>,
pub image_url: Option<String>,
pub icon_url: Option<String>,
pub category: Option<String>,
pub subcategory: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
pub end_date: Option<String>,
pub event: Option<ComboEvent>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComboLeg {
pub leg_index: i64,
pub leg_position_id: Option<String>,
pub leg_condition_id: Option<String>,
pub leg_outcome_index: Option<i64>,
pub leg_outcome_label: Option<String>,
pub leg_status: Option<ComboLegStatus>,
pub leg_resolved_at: Option<String>,
pub leg_current_price: Option<String>,
pub market: Option<ComboMarket>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComboPosition {
pub combo_condition_id: String,
pub combo_position_id: Option<String>,
pub module_id: Option<i64>,
pub user_address: Option<String>,
pub shares_balance: Option<String>,
pub entry_avg_price_usdc: Option<String>,
pub entry_cost_usdc: Option<String>,
pub realized_payout_usdc: Option<String>,
pub total_cost_usdc: Option<String>,
pub gross_entry_cost_usdc: Option<String>,
pub entry_fees_usdc: Option<String>,
pub status: Option<ComboStatus>,
pub first_entry_at: Option<String>,
pub resolved_at: Option<String>,
pub updated_at: Option<String>,
pub legs_total: Option<i64>,
pub legs_resolved: Option<i64>,
pub legs_pending: Option<i64>,
#[serde(default)]
pub legs: Vec<ComboLeg>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CombosResponse {
#[serde(default)]
pub combos: Vec<ComboPosition>,
pub pagination: Option<Pagination>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComboActivity {
pub id: Option<String>,
#[serde(rename = "type")]
pub activity_type: Option<String>,
#[deprecated(note = "upstream deprecated this field; use `activity_type` instead")]
pub event_kind: Option<String>,
#[deprecated(note = "upstream deprecated this field; use `activity_type` instead")]
pub side: Option<String>,
pub module_kind: Option<String>,
pub user_address: Option<String>,
pub combo_condition_id: Option<String>,
pub combo_position_id: Option<String>,
pub module_id: Option<i64>,
pub amount_usdc: Option<f64>,
pub payout_usdc: Option<f64>,
pub timestamp: Option<i64>,
pub tx_dttm: Option<String>,
pub tx_hash: Option<String>,
pub log_index: Option<i64>,
pub block_number: Option<i64>,
#[serde(default)]
pub legs: Vec<ComboLeg>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CombosActivityResponse {
#[serde(default)]
pub activity: Vec<ComboActivity>,
pub pagination: Option<Pagination>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum PnlFidelity {
#[default]
#[serde(rename = "1d")]
OneDay,
#[serde(rename = "18h")]
EighteenHours,
#[serde(rename = "12h")]
TwelveHours,
#[serde(rename = "3h")]
ThreeHours,
#[serde(rename = "1h")]
OneHour,
}
impl std::fmt::Display for PnlFidelity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::OneDay => write!(f, "1d"),
Self::EighteenHours => write!(f, "18h"),
Self::TwelveHours => write!(f, "12h"),
Self::ThreeHours => write!(f, "3h"),
Self::OneHour => write!(f, "1h"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PnlPoint {
#[serde(rename = "t")]
pub timestamp: i64,
#[serde(rename = "p")]
pub pnl: f64,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum RankingWindow {
#[default]
#[serde(rename = "all")]
All,
#[serde(rename = "1d")]
OneDay,
#[serde(rename = "7d")]
SevenDays,
#[serde(rename = "30d")]
ThirtyDays,
}
impl std::fmt::Display for RankingWindow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::All => write!(f, "all"),
Self::OneDay => write!(f, "1d"),
Self::SevenDays => write!(f, "7d"),
Self::ThirtyDays => write!(f, "30d"),
}
}
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RankingEntry {
pub proxy_wallet: Option<String>,
pub amount: Option<f64>,
pub name: Option<String>,
pub pseudonym: Option<String>,
pub bio: Option<String>,
pub profile_image: Option<String>,
pub profile_image_optimized: Option<String>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OtherSize {
pub id: Option<i64>,
pub user: Option<String>,
pub size: Option<f64>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevisionEntry {
pub revision: Option<String>,
pub timestamp: Option<i64>,
}
#[cfg_attr(feature = "specta", derive(specta::Type))]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RevisionPayload {
#[serde(rename = "questionID")]
pub question_id: Option<String>,
#[serde(default)]
pub revisions: Vec<RevisionEntry>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn position_sort_by_display_matches_serde() {
let variants = [
PositionSortBy::Current,
PositionSortBy::Initial,
PositionSortBy::Tokens,
PositionSortBy::CashPnl,
PositionSortBy::PercentPnl,
PositionSortBy::Title,
PositionSortBy::Resolving,
PositionSortBy::Price,
PositionSortBy::AvgPrice,
];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn closed_position_sort_by_display_matches_serde() {
let variants = [
ClosedPositionSortBy::RealizedPnl,
ClosedPositionSortBy::Title,
ClosedPositionSortBy::Price,
ClosedPositionSortBy::AvgPrice,
ClosedPositionSortBy::Timestamp,
];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn activity_sort_by_display_matches_serde() {
let variants = [
ActivitySortBy::Timestamp,
ActivitySortBy::Tokens,
ActivitySortBy::Cash,
];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn sort_direction_display_matches_serde() {
let variants = [SortDirection::Asc, SortDirection::Desc];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn trade_side_display_matches_serde() {
let variants = [TradeSide::Buy, TradeSide::Sell, TradeSide::Unknown];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn trade_side_falls_back_to_unknown_for_future_values() {
let result: TradeSide = serde_json::from_str("\"SOME_FUTURE_SIDE\"").unwrap();
assert_eq!(result, TradeSide::Unknown);
}
#[test]
fn trade_filter_type_display_matches_serde() {
let variants = [TradeFilterType::Cash, TradeFilterType::Tokens];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn activity_type_display_matches_serde() {
let variants = [
ActivityType::Trade,
ActivityType::Split,
ActivityType::Merge,
ActivityType::Redeem,
ActivityType::Reward,
ActivityType::Conversion,
ActivityType::MakerRebate,
ActivityType::ReferralReward,
ActivityType::TakerRebate,
ActivityType::Unknown,
];
for variant in variants {
let serialized = serde_json::to_value(variant).unwrap();
let display = variant.to_string();
assert_eq!(
format!("\"{}\"", display),
serialized.to_string(),
"Display mismatch for {:?}",
variant
);
}
}
#[test]
fn activity_type_roundtrip_serde() {
for variant in [
ActivityType::Trade,
ActivityType::Split,
ActivityType::Merge,
ActivityType::Redeem,
ActivityType::Reward,
ActivityType::Conversion,
ActivityType::MakerRebate,
ActivityType::ReferralReward,
ActivityType::TakerRebate,
] {
let json = serde_json::to_string(&variant).unwrap();
let deserialized: ActivityType = serde_json::from_str(&json).unwrap();
assert_eq!(variant, deserialized);
}
}
#[test]
fn activity_type_falls_back_to_unknown_for_future_types() {
let result: ActivityType = serde_json::from_str("\"SOME_FUTURE_TYPE\"").unwrap();
assert_eq!(result, ActivityType::Unknown);
let result: ActivityType = serde_json::from_str("\"trade\"").unwrap();
assert_eq!(result, ActivityType::Unknown);
}
#[test]
fn sort_direction_default_is_desc() {
assert_eq!(SortDirection::default(), SortDirection::Desc);
}
#[test]
fn closed_position_sort_by_default_is_realized_pnl() {
assert_eq!(
ClosedPositionSortBy::default(),
ClosedPositionSortBy::RealizedPnl
);
}
#[test]
fn activity_sort_by_default_is_timestamp() {
assert_eq!(ActivitySortBy::default(), ActivitySortBy::Timestamp);
}
#[test]
fn position_sort_by_serde_roundtrip() {
for variant in [
PositionSortBy::Current,
PositionSortBy::Initial,
PositionSortBy::Tokens,
PositionSortBy::CashPnl,
PositionSortBy::PercentPnl,
PositionSortBy::Title,
PositionSortBy::Resolving,
PositionSortBy::Price,
PositionSortBy::AvgPrice,
] {
let json = serde_json::to_string(&variant).unwrap();
let deserialized: PositionSortBy = serde_json::from_str(&json).unwrap();
assert_eq!(variant, deserialized);
}
}
#[test]
fn deserialize_position_from_json() {
let json = r#"{
"proxyWallet": "0xabc123",
"asset": "token123",
"conditionId": "cond456",
"size": 100.5,
"avgPrice": 0.65,
"initialValue": 65.0,
"currentValue": 70.0,
"cashPnl": 5.0,
"percentPnl": 7.69,
"totalBought": 100.5,
"realizedPnl": 2.0,
"percentRealizedPnl": 3.08,
"curPrice": 0.70,
"redeemable": false,
"mergeable": true,
"title": "Will X happen?",
"slug": "will-x-happen",
"icon": "https://example.com/icon.png",
"eventSlug": "x-event",
"outcome": "Yes",
"outcomeIndex": 0,
"oppositeOutcome": "No",
"oppositeAsset": "token789",
"endDate": "2025-12-31",
"negativeRisk": false
}"#;
let pos: Position = serde_json::from_str(json).unwrap();
assert_eq!(pos.proxy_wallet, "0xabc123");
assert_eq!(pos.asset, "token123");
assert_eq!(pos.condition_id, "cond456");
assert!((pos.size - 100.5).abs() < f64::EPSILON);
assert!((pos.avg_price - 0.65).abs() < f64::EPSILON);
assert!((pos.initial_value - 65.0).abs() < f64::EPSILON);
assert!((pos.current_value - 70.0).abs() < f64::EPSILON);
assert!((pos.cash_pnl - 5.0).abs() < f64::EPSILON);
assert!(!pos.redeemable);
assert!(pos.mergeable);
assert_eq!(pos.title, "Will X happen?");
assert_eq!(pos.outcome, "Yes");
assert_eq!(pos.outcome_index, 0);
assert_eq!(pos.opposite_outcome, "No");
assert!(!pos.negative_risk);
assert_eq!(pos.icon, Some("https://example.com/icon.png".to_string()));
assert_eq!(pos.event_slug, Some("x-event".to_string()));
}
#[test]
fn deserialize_position_with_null_optionals() {
let json = r#"{
"proxyWallet": "0xabc123",
"asset": "token123",
"conditionId": "cond456",
"size": 0.0,
"avgPrice": 0.0,
"initialValue": 0.0,
"currentValue": 0.0,
"cashPnl": 0.0,
"percentPnl": 0.0,
"totalBought": 0.0,
"realizedPnl": 0.0,
"percentRealizedPnl": 0.0,
"curPrice": 0.0,
"redeemable": false,
"mergeable": false,
"title": "Test",
"slug": "test",
"icon": null,
"eventSlug": null,
"outcome": "No",
"outcomeIndex": 1,
"oppositeOutcome": "Yes",
"oppositeAsset": "token000",
"endDate": null,
"negativeRisk": true
}"#;
let pos: Position = serde_json::from_str(json).unwrap();
assert!(pos.icon.is_none());
assert!(pos.event_slug.is_none());
assert!(pos.end_date.is_none());
assert!(pos.negative_risk);
}
#[test]
fn deserialize_closed_position_from_json() {
let json = r#"{
"proxyWallet": "0xdef456",
"asset": "token_closed",
"conditionId": "cond_closed",
"avgPrice": 0.45,
"totalBought": 200.0,
"realizedPnl": -10.0,
"curPrice": 0.35,
"timestamp": 1700000000,
"title": "Closed market?",
"slug": "closed-market",
"icon": null,
"eventSlug": "closed-event",
"outcome": "No",
"outcomeIndex": 1,
"oppositeOutcome": "Yes",
"oppositeAsset": "token_opp",
"endDate": "2024-06-30"
}"#;
let closed: ClosedPosition = serde_json::from_str(json).unwrap();
assert_eq!(closed.proxy_wallet, "0xdef456");
assert!((closed.avg_price - 0.45).abs() < f64::EPSILON);
assert!((closed.realized_pnl - (-10.0)).abs() < f64::EPSILON);
assert_eq!(closed.timestamp, 1700000000);
assert_eq!(closed.outcome, "No");
assert_eq!(closed.outcome_index, 1);
assert!(closed.icon.is_none());
assert_eq!(closed.event_slug, Some("closed-event".to_string()));
}
#[test]
fn deserialize_trade_from_json() {
let json = r#"{
"proxyWallet": "0x1234",
"side": "BUY",
"asset": "token_buy",
"conditionId": "cond_trade",
"size": 50.0,
"price": 0.72,
"timestamp": 1700001000,
"title": "Trade market?",
"slug": "trade-market",
"icon": "https://example.com/trade.png",
"eventSlug": null,
"outcome": "Yes",
"outcomeIndex": 0,
"name": "TraderOne",
"pseudonym": "t1",
"bio": "A trader",
"profileImage": null,
"profileImageOptimized": null,
"transactionHash": "0xhash123"
}"#;
let trade: Trade = serde_json::from_str(json).unwrap();
assert_eq!(trade.proxy_wallet, "0x1234");
assert_eq!(trade.side, TradeSide::Buy);
assert!((trade.size - 50.0).abs() < f64::EPSILON);
assert!((trade.price - 0.72).abs() < f64::EPSILON);
assert_eq!(trade.timestamp, 1700001000);
assert_eq!(trade.name, Some("TraderOne".to_string()));
assert_eq!(trade.transaction_hash, Some("0xhash123".to_string()));
assert!(trade.profile_image.is_none());
}
#[test]
fn deserialize_trade_sell_side() {
let json = r#"{
"proxyWallet": "0x5678",
"side": "SELL",
"asset": "token_sell",
"conditionId": "cond_sell",
"size": 25.0,
"price": 0.30,
"timestamp": 1700002000,
"title": "Sell test",
"slug": "sell-test",
"icon": null,
"eventSlug": null,
"outcome": "No",
"outcomeIndex": 1,
"name": null,
"pseudonym": null,
"bio": null,
"profileImage": null,
"profileImageOptimized": null,
"transactionHash": null
}"#;
let trade: Trade = serde_json::from_str(json).unwrap();
assert_eq!(trade.side, TradeSide::Sell);
assert!(trade.name.is_none());
assert!(trade.transaction_hash.is_none());
}
#[test]
fn deserialize_activity_from_json() {
let json = r#"{
"proxyWallet": "0xact123",
"timestamp": 1700003000,
"conditionId": "cond_act",
"type": "TRADE",
"size": 10.0,
"usdcSize": 7.50,
"transactionHash": "0xacthash",
"price": 0.75,
"asset": "token_act",
"side": "BUY",
"outcomeIndex": 0,
"title": "Activity market",
"slug": "activity-market",
"icon": null,
"outcome": "Yes",
"name": null,
"pseudonym": null,
"bio": null,
"profileImage": null,
"profileImageOptimized": null
}"#;
let activity: Activity = serde_json::from_str(json).unwrap();
assert_eq!(activity.proxy_wallet, "0xact123");
assert_eq!(activity.activity_type, ActivityType::Trade);
assert!((activity.size - 10.0).abs() < f64::EPSILON);
assert!((activity.usdc_size - 7.50).abs() < f64::EPSILON);
assert_eq!(activity.side, Some("BUY".to_string()));
assert_eq!(activity.outcome_index, Some(0));
}
#[test]
fn deserialize_activity_merge_type() {
let json = r#"{
"proxyWallet": "0xmerge",
"timestamp": 1700004000,
"conditionId": "cond_merge",
"type": "MERGE",
"size": 5.0,
"usdcSize": 3.0,
"transactionHash": null,
"price": null,
"asset": null,
"side": "",
"outcomeIndex": null,
"title": null,
"slug": null,
"icon": null,
"outcome": null,
"name": null,
"pseudonym": null,
"bio": null,
"profileImage": null,
"profileImageOptimized": null
}"#;
let activity: Activity = serde_json::from_str(json).unwrap();
assert_eq!(activity.activity_type, ActivityType::Merge);
assert_eq!(activity.side, Some("".to_string()));
assert!(activity.price.is_none());
assert!(activity.asset.is_none());
assert!(activity.title.is_none());
}
#[test]
fn deserialize_user_value() {
let json = r#"{"user": "0xuser", "value": 1234.56}"#;
let uv: UserValue = serde_json::from_str(json).unwrap();
assert_eq!(uv.user, "0xuser");
assert!((uv.value - 1234.56).abs() < f64::EPSILON);
}
#[test]
fn deserialize_open_interest() {
let json = r#"{"market": "0xcond", "value": 50000.0}"#;
let oi: OpenInterest = serde_json::from_str(json).unwrap();
assert_eq!(oi.market, "0xcond");
assert!((oi.value - 50000.0).abs() < f64::EPSILON);
}
#[test]
fn market_position_status_display_matches_serde() {
for variant in [
MarketPositionStatus::Open,
MarketPositionStatus::Closed,
MarketPositionStatus::All,
] {
let serialized = serde_json::to_value(variant).unwrap();
assert_eq!(format!("\"{}\"", variant), serialized.to_string());
}
}
#[test]
fn market_position_status_default_is_all() {
assert_eq!(MarketPositionStatus::default(), MarketPositionStatus::All);
}
#[test]
fn market_position_sort_by_display_matches_serde() {
for variant in [
MarketPositionSortBy::Tokens,
MarketPositionSortBy::CashPnl,
MarketPositionSortBy::RealizedPnl,
MarketPositionSortBy::TotalPnl,
] {
let serialized = serde_json::to_value(variant).unwrap();
assert_eq!(format!("\"{}\"", variant), serialized.to_string());
}
}
#[test]
fn market_position_sort_by_default_is_total_pnl() {
assert_eq!(
MarketPositionSortBy::default(),
MarketPositionSortBy::TotalPnl
);
}
#[test]
fn deserialize_market_position_v1() {
let json = r#"{
"proxyWallet": "0xabc",
"name": "Alice",
"profileImage": "https://example.com/a.png",
"verified": true,
"asset": "token_a",
"conditionId": "cond_mp",
"avgPrice": 0.42,
"size": 1234.5,
"currPrice": 0.51,
"currentValue": 629.60,
"cashPnl": 110.0,
"totalBought": 520.0,
"realizedPnl": 15.5,
"totalPnl": 125.5,
"outcome": "Yes",
"outcomeIndex": 0
}"#;
let pos: MarketPositionV1 = serde_json::from_str(json).unwrap();
assert_eq!(pos.proxy_wallet, "0xabc");
assert_eq!(pos.name, "Alice");
assert_eq!(
pos.profile_image.as_deref(),
Some("https://example.com/a.png")
);
assert!(pos.verified);
assert_eq!(pos.asset, "token_a");
assert_eq!(pos.condition_id, "cond_mp");
assert!((pos.avg_price - 0.42).abs() < f64::EPSILON);
assert!((pos.size - 1234.5).abs() < f64::EPSILON);
assert!((pos.curr_price - 0.51).abs() < f64::EPSILON);
assert!((pos.current_value - 629.60).abs() < f64::EPSILON);
assert!((pos.cash_pnl - 110.0).abs() < f64::EPSILON);
assert!((pos.total_bought - 520.0).abs() < f64::EPSILON);
assert!((pos.realized_pnl - 15.5).abs() < f64::EPSILON);
assert!((pos.total_pnl - 125.5).abs() < f64::EPSILON);
assert_eq!(pos.outcome, "Yes");
assert_eq!(pos.outcome_index, 0);
}
#[test]
fn market_position_v1_roundtrip() {
let original = MarketPositionV1 {
proxy_wallet: "0xabc".into(),
name: "Alice".into(),
profile_image: None,
verified: false,
asset: "token_a".into(),
condition_id: "cond_mp".into(),
avg_price: 0.5,
size: 10.0,
curr_price: 0.6,
current_value: 6.0,
cash_pnl: 1.0,
total_bought: 5.0,
realized_pnl: 0.0,
total_pnl: 1.0,
outcome: "No".into(),
outcome_index: 1,
};
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("\"currPrice\""));
let back: MarketPositionV1 = serde_json::from_str(&json).unwrap();
assert_eq!(back.proxy_wallet, original.proxy_wallet);
assert_eq!(back.outcome_index, original.outcome_index);
assert!((back.curr_price - original.curr_price).abs() < f64::EPSILON);
}
#[test]
fn deserialize_meta_market_position_v1() {
let json = r#"{
"token": "token_a",
"positions": [
{
"proxyWallet": "0xabc",
"name": "Alice",
"profileImage": null,
"verified": false,
"asset": "token_a",
"conditionId": "cond_mp",
"avgPrice": 0.42,
"size": 100.0,
"currPrice": 0.51,
"currentValue": 51.0,
"cashPnl": 9.0,
"totalBought": 42.0,
"realizedPnl": 0.0,
"totalPnl": 9.0,
"outcome": "Yes",
"outcomeIndex": 0
}
]
}"#;
let meta: MetaMarketPositionV1 = serde_json::from_str(json).unwrap();
assert_eq!(meta.token, "token_a");
assert_eq!(meta.positions.len(), 1);
assert_eq!(meta.positions[0].name, "Alice");
assert!(meta.positions[0].profile_image.is_none());
}
}