1use serde::{Deserialize, Serialize};
2
3#[cfg_attr(feature = "specta", derive(specta::Type))]
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct UserValue {
7 pub user: String,
9 pub value: f64,
11}
12
13#[cfg_attr(feature = "specta", derive(specta::Type))]
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct OpenInterest {
17 pub market: String,
19 pub value: f64,
21}
22
23#[cfg_attr(feature = "specta", derive(specta::Type))]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
27pub enum PositionSortBy {
28 Current,
30 Initial,
32 Tokens,
34 CashPnl,
36 PercentPnl,
38 Title,
40 Resolving,
42 Price,
44 AvgPrice,
46}
47
48impl std::fmt::Display for PositionSortBy {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 match self {
51 Self::Current => write!(f, "CURRENT"),
52 Self::Initial => write!(f, "INITIAL"),
53 Self::Tokens => write!(f, "TOKENS"),
54 Self::CashPnl => write!(f, "CASH_PNL"),
55 Self::PercentPnl => write!(f, "PERCENT_PNL"),
56 Self::Title => write!(f, "TITLE"),
57 Self::Resolving => write!(f, "RESOLVING"),
58 Self::Price => write!(f, "PRICE"),
59 Self::AvgPrice => write!(f, "AVG_PRICE"),
60 }
61 }
62}
63
64#[cfg_attr(feature = "specta", derive(specta::Type))]
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
67#[serde(rename_all = "UPPERCASE")]
68pub enum SortDirection {
69 Asc,
71 #[default]
73 Desc,
74}
75
76impl std::fmt::Display for SortDirection {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 match self {
79 Self::Asc => write!(f, "ASC"),
80 Self::Desc => write!(f, "DESC"),
81 }
82 }
83}
84
85#[cfg_attr(feature = "specta", derive(specta::Type))]
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
88#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
89pub enum ClosedPositionSortBy {
90 #[default]
92 RealizedPnl,
93 Title,
95 Price,
97 AvgPrice,
99 Timestamp,
101}
102
103impl std::fmt::Display for ClosedPositionSortBy {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self {
106 Self::RealizedPnl => write!(f, "REALIZED_PNL"),
107 Self::Title => write!(f, "TITLE"),
108 Self::Price => write!(f, "PRICE"),
109 Self::AvgPrice => write!(f, "AVG_PRICE"),
110 Self::Timestamp => write!(f, "TIMESTAMP"),
111 }
112 }
113}
114
115#[cfg_attr(feature = "specta", derive(specta::Type))]
117#[derive(Debug, Clone, Serialize, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct ClosedPosition {
120 pub proxy_wallet: String,
122 pub asset: String,
124 pub condition_id: String,
126 pub avg_price: f64,
128 pub total_bought: f64,
130 pub realized_pnl: f64,
132 pub cur_price: f64,
134 #[cfg_attr(feature = "specta", specta(type = f64))]
136 pub timestamp: i64,
137 pub title: String,
139 pub slug: String,
141 pub icon: Option<String>,
143 pub event_slug: Option<String>,
145 pub outcome: String,
147 pub outcome_index: u32,
149 pub opposite_outcome: String,
151 pub opposite_asset: String,
153 pub end_date: Option<String>,
155}
156
157#[cfg_attr(feature = "specta", derive(specta::Type))]
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
160#[serde(rename_all = "UPPERCASE")]
161pub enum TradeSide {
162 Buy,
164 Sell,
166 #[serde(other)]
170 Unknown,
171}
172
173impl std::fmt::Display for TradeSide {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 match self {
176 Self::Buy => write!(f, "BUY"),
177 Self::Sell => write!(f, "SELL"),
178 Self::Unknown => write!(f, "UNKNOWN"),
179 }
180 }
181}
182
183#[cfg_attr(feature = "specta", derive(specta::Type))]
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
186#[serde(rename_all = "UPPERCASE")]
187pub enum TradeFilterType {
188 Cash,
190 Tokens,
192}
193
194impl std::fmt::Display for TradeFilterType {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 Self::Cash => write!(f, "CASH"),
198 Self::Tokens => write!(f, "TOKENS"),
199 }
200 }
201}
202
203#[cfg_attr(feature = "specta", derive(specta::Type))]
205#[derive(Debug, Clone, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct Trade {
208 pub proxy_wallet: String,
210 pub side: TradeSide,
212 pub asset: String,
214 pub condition_id: String,
216 pub size: f64,
218 pub price: f64,
220 #[cfg_attr(feature = "specta", specta(type = f64))]
222 pub timestamp: i64,
223 pub title: String,
225 pub slug: String,
227 pub icon: Option<String>,
229 pub event_slug: Option<String>,
231 pub outcome: String,
233 pub outcome_index: u32,
235 pub name: Option<String>,
237 pub pseudonym: Option<String>,
239 pub bio: Option<String>,
241 pub profile_image: Option<String>,
243 pub profile_image_optimized: Option<String>,
245 pub transaction_hash: Option<String>,
247}
248
249#[cfg_attr(feature = "specta", derive(specta::Type))]
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "UPPERCASE")]
253pub enum ActivityType {
254 Trade,
256 Split,
258 Merge,
260 Redeem,
262 Reward,
264 Conversion,
266 Deposit,
268 Withdrawal,
270 Yield,
272 #[serde(rename = "MAKER_REBATE")]
274 MakerRebate,
275 #[serde(rename = "REFERRAL_REWARD")]
277 ReferralReward,
278 #[serde(rename = "TAKER_REBATE")]
280 TakerRebate,
281 #[serde(other)]
285 Unknown,
286}
287
288impl std::fmt::Display for ActivityType {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 match self {
291 Self::Trade => write!(f, "TRADE"),
292 Self::Split => write!(f, "SPLIT"),
293 Self::Merge => write!(f, "MERGE"),
294 Self::Redeem => write!(f, "REDEEM"),
295 Self::Reward => write!(f, "REWARD"),
296 Self::Conversion => write!(f, "CONVERSION"),
297 Self::Deposit => write!(f, "DEPOSIT"),
298 Self::Withdrawal => write!(f, "WITHDRAWAL"),
299 Self::Yield => write!(f, "YIELD"),
300 Self::MakerRebate => write!(f, "MAKER_REBATE"),
301 Self::ReferralReward => write!(f, "REFERRAL_REWARD"),
302 Self::TakerRebate => write!(f, "TAKER_REBATE"),
303 Self::Unknown => write!(f, "UNKNOWN"),
304 }
305 }
306}
307
308#[cfg_attr(feature = "specta", derive(specta::Type))]
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
311#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
312pub enum ActivitySortBy {
313 #[default]
315 Timestamp,
316 Tokens,
318 Cash,
320}
321
322impl std::fmt::Display for ActivitySortBy {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 match self {
325 Self::Timestamp => write!(f, "TIMESTAMP"),
326 Self::Tokens => write!(f, "TOKENS"),
327 Self::Cash => write!(f, "CASH"),
328 }
329 }
330}
331
332#[cfg_attr(feature = "specta", derive(specta::Type))]
334#[derive(Debug, Clone, Serialize, Deserialize)]
335#[serde(rename_all = "camelCase")]
336pub struct Activity {
337 pub proxy_wallet: String,
339 #[cfg_attr(feature = "specta", specta(type = f64))]
341 pub timestamp: i64,
342 pub condition_id: String,
344 #[serde(rename = "type")]
346 pub activity_type: ActivityType,
347 pub size: f64,
349 pub usdc_size: f64,
351 pub transaction_hash: Option<String>,
353 pub price: Option<f64>,
355 pub asset: Option<String>,
357 pub side: Option<String>,
360 pub outcome_index: Option<u32>,
362 pub title: Option<String>,
364 pub slug: Option<String>,
366 pub icon: Option<String>,
368 pub outcome: Option<String>,
370 pub name: Option<String>,
372 pub pseudonym: Option<String>,
374 pub bio: Option<String>,
376 pub profile_image: Option<String>,
378 pub profile_image_optimized: Option<String>,
380}
381
382#[cfg_attr(feature = "specta", derive(specta::Type))]
384#[derive(Debug, Clone, Serialize, Deserialize)]
385#[serde(rename_all = "camelCase")]
386pub struct Position {
387 pub proxy_wallet: String,
389 pub asset: String,
391 pub condition_id: String,
393 pub size: f64,
395 pub avg_price: f64,
397 pub initial_value: f64,
399 pub current_value: f64,
401 pub cash_pnl: f64,
403 pub percent_pnl: f64,
405 pub total_bought: f64,
407 pub realized_pnl: f64,
409 pub percent_realized_pnl: f64,
411 pub cur_price: f64,
413 pub redeemable: bool,
415 pub mergeable: bool,
417 pub title: String,
419 pub slug: String,
421 pub icon: Option<String>,
423 pub event_slug: Option<String>,
425 pub outcome: String,
427 pub outcome_index: u32,
429 pub opposite_outcome: String,
431 pub opposite_asset: String,
433 pub end_date: Option<String>,
435 pub negative_risk: bool,
437}
438
439#[cfg_attr(feature = "specta", derive(specta::Type))]
444#[derive(Debug, Clone, Serialize, Deserialize)]
445#[serde(rename_all = "camelCase")]
446pub struct MarketPositionV1 {
447 pub proxy_wallet: String,
449 pub name: String,
451 pub profile_image: Option<String>,
453 pub verified: bool,
455 pub asset: String,
457 pub condition_id: String,
459 pub avg_price: f64,
461 pub size: f64,
463 #[serde(rename = "currPrice")]
465 pub curr_price: f64,
466 pub current_value: f64,
468 pub cash_pnl: f64,
470 pub total_bought: f64,
472 pub realized_pnl: f64,
474 pub total_pnl: f64,
476 pub outcome: String,
478 pub outcome_index: u32,
480}
481
482#[cfg_attr(feature = "specta", derive(specta::Type))]
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct MetaMarketPositionV1 {
486 pub token: String,
488 pub positions: Vec<MarketPositionV1>,
490}
491
492#[cfg_attr(feature = "specta", derive(specta::Type))]
494#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
495#[serde(rename_all = "UPPERCASE")]
496pub enum MarketPositionStatus {
497 Open,
499 Closed,
501 #[default]
503 All,
504}
505
506impl std::fmt::Display for MarketPositionStatus {
507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
508 match self {
509 Self::Open => write!(f, "OPEN"),
510 Self::Closed => write!(f, "CLOSED"),
511 Self::All => write!(f, "ALL"),
512 }
513 }
514}
515
516#[cfg_attr(feature = "specta", derive(specta::Type))]
518#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
519#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
520pub enum MarketPositionSortBy {
521 Tokens,
523 CashPnl,
525 RealizedPnl,
527 #[default]
529 TotalPnl,
530}
531
532impl std::fmt::Display for MarketPositionSortBy {
533 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534 match self {
535 Self::Tokens => write!(f, "TOKENS"),
536 Self::CashPnl => write!(f, "CASH_PNL"),
537 Self::RealizedPnl => write!(f, "REALIZED_PNL"),
538 Self::TotalPnl => write!(f, "TOTAL_PNL"),
539 }
540 }
541}
542
543#[cfg_attr(feature = "specta", derive(specta::Type))]
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
546#[serde(rename_all = "UPPERCASE")]
547pub enum TimePeriod {
548 #[default]
550 Day,
551 Week,
553 Month,
555 All,
557}
558
559impl std::fmt::Display for TimePeriod {
560 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
561 match self {
562 Self::Day => write!(f, "DAY"),
563 Self::Week => write!(f, "WEEK"),
564 Self::Month => write!(f, "MONTH"),
565 Self::All => write!(f, "ALL"),
566 }
567 }
568}
569
570#[cfg_attr(feature = "specta", derive(specta::Type))]
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
583#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
584pub enum ComboStatus {
585 Open,
587 Partial,
589 ResolvedPartial,
592 ResolvedWin,
594 ResolvedLoss,
596 #[serde(other)]
600 Unknown,
601}
602
603impl std::fmt::Display for ComboStatus {
604 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
605 match self {
606 Self::Open => write!(f, "OPEN"),
607 Self::Partial => write!(f, "PARTIAL"),
608 Self::ResolvedPartial => write!(f, "RESOLVED_PARTIAL"),
609 Self::ResolvedWin => write!(f, "RESOLVED_WIN"),
610 Self::ResolvedLoss => write!(f, "RESOLVED_LOSS"),
611 Self::Unknown => write!(f, "UNKNOWN"),
612 }
613 }
614}
615
616#[cfg_attr(feature = "specta", derive(specta::Type))]
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
619#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
620pub enum ComboLegStatus {
621 Open,
623 ResolvedPartial,
625 ResolvedWin,
627 ResolvedLoss,
629 #[serde(other)]
631 Unknown,
632}
633
634impl std::fmt::Display for ComboLegStatus {
635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636 match self {
637 Self::Open => write!(f, "OPEN"),
638 Self::ResolvedPartial => write!(f, "RESOLVED_PARTIAL"),
639 Self::ResolvedWin => write!(f, "RESOLVED_WIN"),
640 Self::ResolvedLoss => write!(f, "RESOLVED_LOSS"),
641 Self::Unknown => write!(f, "UNKNOWN"),
642 }
643 }
644}
645
646#[cfg_attr(feature = "specta", derive(specta::Type))]
648#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
649#[serde(rename_all = "snake_case")]
650pub enum ComboSort {
651 #[default]
653 CurrentValueDesc,
654 EntryCostDesc,
656 FirstEntryDesc,
658 ResolvedAtDesc,
660 UpdatedAsc,
663}
664
665impl std::fmt::Display for ComboSort {
666 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
667 match self {
668 Self::CurrentValueDesc => write!(f, "current_value_desc"),
669 Self::EntryCostDesc => write!(f, "entry_cost_desc"),
670 Self::FirstEntryDesc => write!(f, "first_entry_desc"),
671 Self::ResolvedAtDesc => write!(f, "resolved_at_desc"),
672 Self::UpdatedAsc => write!(f, "updated_asc"),
673 }
674 }
675}
676
677#[cfg_attr(feature = "specta", derive(specta::Type))]
681#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct Pagination {
683 pub limit: i64,
685 pub offset: i64,
687 pub has_more: bool,
689 pub next_cursor: Option<String>,
695}
696
697#[cfg_attr(feature = "specta", derive(specta::Type))]
699#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct ComboEvent {
701 pub event_id: Option<String>,
703 pub event_slug: Option<String>,
705 pub event_title: Option<String>,
707 pub event_image: Option<String>,
709}
710
711#[cfg_attr(feature = "specta", derive(specta::Type))]
713#[derive(Debug, Clone, Serialize, Deserialize)]
714pub struct ComboMarket {
715 pub market_id: Option<String>,
717 pub slug: Option<String>,
719 pub title: Option<String>,
721 pub outcome: Option<String>,
723 pub image_url: Option<String>,
725 pub icon_url: Option<String>,
727 pub category: Option<String>,
729 pub subcategory: Option<String>,
731 #[serde(default)]
733 pub tags: Vec<String>,
734 pub end_date: Option<String>,
736 pub event: Option<ComboEvent>,
738}
739
740#[cfg_attr(feature = "specta", derive(specta::Type))]
742#[derive(Debug, Clone, Serialize, Deserialize)]
743pub struct ComboLeg {
744 pub leg_index: i64,
746 pub leg_position_id: Option<String>,
748 pub leg_condition_id: Option<String>,
750 pub leg_outcome_index: Option<i64>,
752 pub leg_outcome_label: Option<String>,
754 pub leg_status: Option<ComboLegStatus>,
757 pub leg_resolved_at: Option<String>,
760 pub leg_current_price: Option<String>,
763 pub market: Option<ComboMarket>,
765}
766
767#[cfg_attr(feature = "specta", derive(specta::Type))]
769#[derive(Debug, Clone, Serialize, Deserialize)]
770pub struct ComboPosition {
771 pub combo_condition_id: String,
774 pub combo_position_id: Option<String>,
776 pub module_id: Option<i64>,
778 pub user_address: Option<String>,
780 pub shares_balance: Option<String>,
782 pub entry_avg_price_usdc: Option<String>,
784 pub entry_cost_usdc: Option<String>,
790 pub realized_payout_usdc: Option<String>,
796 pub total_cost_usdc: Option<String>,
799 pub gross_entry_cost_usdc: Option<String>,
806 pub entry_fees_usdc: Option<String>,
809 pub status: Option<ComboStatus>,
811 pub first_entry_at: Option<String>,
813 pub resolved_at: Option<String>,
815 pub updated_at: Option<String>,
820 pub legs_total: Option<i64>,
822 pub legs_resolved: Option<i64>,
824 pub legs_pending: Option<i64>,
826 #[serde(default)]
828 pub legs: Vec<ComboLeg>,
829}
830
831#[cfg_attr(feature = "specta", derive(specta::Type))]
833#[derive(Debug, Clone, Serialize, Deserialize)]
834pub struct CombosResponse {
835 #[serde(default)]
837 pub combos: Vec<ComboPosition>,
838 pub pagination: Option<Pagination>,
840}
841
842#[cfg_attr(feature = "specta", derive(specta::Type))]
844#[derive(Debug, Clone, Serialize, Deserialize)]
845pub struct ComboActivity {
846 pub id: Option<String>,
848 #[serde(rename = "type")]
855 pub activity_type: Option<String>,
856 #[deprecated(note = "upstream deprecated this field; use `activity_type` instead")]
858 pub event_kind: Option<String>,
859 #[deprecated(note = "upstream deprecated this field; use `activity_type` instead")]
861 pub side: Option<String>,
862 pub module_kind: Option<String>,
864 pub user_address: Option<String>,
866 pub combo_condition_id: Option<String>,
868 pub combo_position_id: Option<String>,
870 pub module_id: Option<i64>,
872 pub amount_usdc: Option<f64>,
874 pub payout_usdc: Option<f64>,
876 pub timestamp: Option<i64>,
878 pub tx_dttm: Option<String>,
880 pub tx_hash: Option<String>,
882 pub log_index: Option<i64>,
884 pub block_number: Option<i64>,
886 #[serde(default)]
888 pub legs: Vec<ComboLeg>,
889}
890
891#[cfg_attr(feature = "specta", derive(specta::Type))]
893#[derive(Debug, Clone, Serialize, Deserialize)]
894pub struct CombosActivityResponse {
895 #[serde(default)]
897 pub activity: Vec<ComboActivity>,
898 pub pagination: Option<Pagination>,
900}
901
902#[cfg_attr(feature = "specta", derive(specta::Type))]
916#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
917pub enum PnlFidelity {
918 #[default]
920 #[serde(rename = "1d")]
921 OneDay,
922 #[serde(rename = "18h")]
924 EighteenHours,
925 #[serde(rename = "12h")]
927 TwelveHours,
928 #[serde(rename = "3h")]
930 ThreeHours,
931 #[serde(rename = "1h")]
933 OneHour,
934}
935
936impl std::fmt::Display for PnlFidelity {
937 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
938 match self {
939 Self::OneDay => write!(f, "1d"),
940 Self::EighteenHours => write!(f, "18h"),
941 Self::TwelveHours => write!(f, "12h"),
942 Self::ThreeHours => write!(f, "3h"),
943 Self::OneHour => write!(f, "1h"),
944 }
945 }
946}
947
948#[cfg_attr(feature = "specta", derive(specta::Type))]
950#[derive(Debug, Clone, Serialize, Deserialize)]
951pub struct PnlPoint {
952 #[serde(rename = "t")]
954 pub timestamp: i64,
955 #[serde(rename = "p")]
957 pub pnl: f64,
958}
959
960#[cfg_attr(feature = "specta", derive(specta::Type))]
964#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
965pub enum RankingWindow {
966 #[default]
968 #[serde(rename = "all")]
969 All,
970 #[serde(rename = "1d")]
972 OneDay,
973 #[serde(rename = "7d")]
975 SevenDays,
976 #[serde(rename = "30d")]
978 ThirtyDays,
979}
980
981impl std::fmt::Display for RankingWindow {
982 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
983 match self {
984 Self::All => write!(f, "all"),
985 Self::OneDay => write!(f, "1d"),
986 Self::SevenDays => write!(f, "7d"),
987 Self::ThirtyDays => write!(f, "30d"),
988 }
989 }
990}
991
992#[cfg_attr(feature = "specta", derive(specta::Type))]
994#[derive(Debug, Clone, Serialize, Deserialize)]
995#[serde(rename_all = "camelCase")]
996pub struct RankingEntry {
997 pub proxy_wallet: Option<String>,
999 pub amount: Option<f64>,
1002 pub name: Option<String>,
1004 pub pseudonym: Option<String>,
1006 pub bio: Option<String>,
1008 pub profile_image: Option<String>,
1010 pub profile_image_optimized: Option<String>,
1012}
1013
1014#[cfg_attr(feature = "specta", derive(specta::Type))]
1016#[derive(Debug, Clone, Serialize, Deserialize)]
1017pub struct OtherSize {
1018 pub id: Option<i64>,
1020 pub user: Option<String>,
1022 pub size: Option<f64>,
1024}
1025
1026#[cfg_attr(feature = "specta", derive(specta::Type))]
1028#[derive(Debug, Clone, Serialize, Deserialize)]
1029pub struct RevisionEntry {
1030 pub revision: Option<String>,
1032 pub timestamp: Option<i64>,
1034}
1035
1036#[cfg_attr(feature = "specta", derive(specta::Type))]
1038#[derive(Debug, Clone, Serialize, Deserialize)]
1039pub struct RevisionPayload {
1040 #[serde(rename = "questionID")]
1042 pub question_id: Option<String>,
1043 #[serde(default)]
1045 pub revisions: Vec<RevisionEntry>,
1046}
1047
1048#[cfg(test)]
1049mod tests {
1050 use super::*;
1051
1052 #[test]
1054 fn position_sort_by_display_matches_serde() {
1055 let variants = [
1056 PositionSortBy::Current,
1057 PositionSortBy::Initial,
1058 PositionSortBy::Tokens,
1059 PositionSortBy::CashPnl,
1060 PositionSortBy::PercentPnl,
1061 PositionSortBy::Title,
1062 PositionSortBy::Resolving,
1063 PositionSortBy::Price,
1064 PositionSortBy::AvgPrice,
1065 ];
1066 for variant in variants {
1067 let serialized = serde_json::to_value(variant).unwrap();
1068 let display = variant.to_string();
1069 assert_eq!(
1070 format!("\"{}\"", display),
1071 serialized.to_string(),
1072 "Display mismatch for {:?}",
1073 variant
1074 );
1075 }
1076 }
1077
1078 #[test]
1080 fn closed_position_sort_by_display_matches_serde() {
1081 let variants = [
1082 ClosedPositionSortBy::RealizedPnl,
1083 ClosedPositionSortBy::Title,
1084 ClosedPositionSortBy::Price,
1085 ClosedPositionSortBy::AvgPrice,
1086 ClosedPositionSortBy::Timestamp,
1087 ];
1088 for variant in variants {
1089 let serialized = serde_json::to_value(variant).unwrap();
1090 let display = variant.to_string();
1091 assert_eq!(
1092 format!("\"{}\"", display),
1093 serialized.to_string(),
1094 "Display mismatch for {:?}",
1095 variant
1096 );
1097 }
1098 }
1099
1100 #[test]
1101 fn activity_sort_by_display_matches_serde() {
1102 let variants = [
1103 ActivitySortBy::Timestamp,
1104 ActivitySortBy::Tokens,
1105 ActivitySortBy::Cash,
1106 ];
1107 for variant in variants {
1108 let serialized = serde_json::to_value(variant).unwrap();
1109 let display = variant.to_string();
1110 assert_eq!(
1111 format!("\"{}\"", display),
1112 serialized.to_string(),
1113 "Display mismatch for {:?}",
1114 variant
1115 );
1116 }
1117 }
1118
1119 #[test]
1120 fn sort_direction_display_matches_serde() {
1121 let variants = [SortDirection::Asc, SortDirection::Desc];
1122 for variant in variants {
1123 let serialized = serde_json::to_value(variant).unwrap();
1124 let display = variant.to_string();
1125 assert_eq!(
1126 format!("\"{}\"", display),
1127 serialized.to_string(),
1128 "Display mismatch for {:?}",
1129 variant
1130 );
1131 }
1132 }
1133
1134 #[test]
1135 fn trade_side_display_matches_serde() {
1136 let variants = [TradeSide::Buy, TradeSide::Sell, TradeSide::Unknown];
1137 for variant in variants {
1138 let serialized = serde_json::to_value(variant).unwrap();
1139 let display = variant.to_string();
1140 assert_eq!(
1141 format!("\"{}\"", display),
1142 serialized.to_string(),
1143 "Display mismatch for {:?}",
1144 variant
1145 );
1146 }
1147 }
1148
1149 #[test]
1150 fn trade_side_falls_back_to_unknown_for_future_values() {
1151 let result: TradeSide = serde_json::from_str("\"SOME_FUTURE_SIDE\"").unwrap();
1152 assert_eq!(result, TradeSide::Unknown);
1153 }
1154
1155 #[test]
1156 fn trade_filter_type_display_matches_serde() {
1157 let variants = [TradeFilterType::Cash, TradeFilterType::Tokens];
1158 for variant in variants {
1159 let serialized = serde_json::to_value(variant).unwrap();
1160 let display = variant.to_string();
1161 assert_eq!(
1162 format!("\"{}\"", display),
1163 serialized.to_string(),
1164 "Display mismatch for {:?}",
1165 variant
1166 );
1167 }
1168 }
1169
1170 #[test]
1171 fn activity_type_display_matches_serde() {
1172 let variants = [
1173 ActivityType::Trade,
1174 ActivityType::Split,
1175 ActivityType::Merge,
1176 ActivityType::Redeem,
1177 ActivityType::Reward,
1178 ActivityType::Conversion,
1179 ActivityType::MakerRebate,
1180 ActivityType::ReferralReward,
1181 ActivityType::TakerRebate,
1182 ActivityType::Unknown,
1183 ];
1184 for variant in variants {
1185 let serialized = serde_json::to_value(variant).unwrap();
1186 let display = variant.to_string();
1187 assert_eq!(
1188 format!("\"{}\"", display),
1189 serialized.to_string(),
1190 "Display mismatch for {:?}",
1191 variant
1192 );
1193 }
1194 }
1195
1196 #[test]
1197 fn activity_type_roundtrip_serde() {
1198 for variant in [
1199 ActivityType::Trade,
1200 ActivityType::Split,
1201 ActivityType::Merge,
1202 ActivityType::Redeem,
1203 ActivityType::Reward,
1204 ActivityType::Conversion,
1205 ActivityType::MakerRebate,
1206 ActivityType::ReferralReward,
1207 ActivityType::TakerRebate,
1208 ] {
1209 let json = serde_json::to_string(&variant).unwrap();
1210 let deserialized: ActivityType = serde_json::from_str(&json).unwrap();
1211 assert_eq!(variant, deserialized);
1212 }
1213 }
1214
1215 #[test]
1216 fn activity_type_falls_back_to_unknown_for_future_types() {
1217 let result: ActivityType = serde_json::from_str("\"SOME_FUTURE_TYPE\"").unwrap();
1219 assert_eq!(result, ActivityType::Unknown);
1220
1221 let result: ActivityType = serde_json::from_str("\"trade\"").unwrap();
1223 assert_eq!(result, ActivityType::Unknown);
1224 }
1225
1226 #[test]
1227 fn sort_direction_default_is_desc() {
1228 assert_eq!(SortDirection::default(), SortDirection::Desc);
1229 }
1230
1231 #[test]
1232 fn closed_position_sort_by_default_is_realized_pnl() {
1233 assert_eq!(
1234 ClosedPositionSortBy::default(),
1235 ClosedPositionSortBy::RealizedPnl
1236 );
1237 }
1238
1239 #[test]
1240 fn activity_sort_by_default_is_timestamp() {
1241 assert_eq!(ActivitySortBy::default(), ActivitySortBy::Timestamp);
1242 }
1243
1244 #[test]
1245 fn position_sort_by_serde_roundtrip() {
1246 for variant in [
1247 PositionSortBy::Current,
1248 PositionSortBy::Initial,
1249 PositionSortBy::Tokens,
1250 PositionSortBy::CashPnl,
1251 PositionSortBy::PercentPnl,
1252 PositionSortBy::Title,
1253 PositionSortBy::Resolving,
1254 PositionSortBy::Price,
1255 PositionSortBy::AvgPrice,
1256 ] {
1257 let json = serde_json::to_string(&variant).unwrap();
1258 let deserialized: PositionSortBy = serde_json::from_str(&json).unwrap();
1259 assert_eq!(variant, deserialized);
1260 }
1261 }
1262
1263 #[test]
1264 fn deserialize_position_from_json() {
1265 let json = r#"{
1266 "proxyWallet": "0xabc123",
1267 "asset": "token123",
1268 "conditionId": "cond456",
1269 "size": 100.5,
1270 "avgPrice": 0.65,
1271 "initialValue": 65.0,
1272 "currentValue": 70.0,
1273 "cashPnl": 5.0,
1274 "percentPnl": 7.69,
1275 "totalBought": 100.5,
1276 "realizedPnl": 2.0,
1277 "percentRealizedPnl": 3.08,
1278 "curPrice": 0.70,
1279 "redeemable": false,
1280 "mergeable": true,
1281 "title": "Will X happen?",
1282 "slug": "will-x-happen",
1283 "icon": "https://example.com/icon.png",
1284 "eventSlug": "x-event",
1285 "outcome": "Yes",
1286 "outcomeIndex": 0,
1287 "oppositeOutcome": "No",
1288 "oppositeAsset": "token789",
1289 "endDate": "2025-12-31",
1290 "negativeRisk": false
1291 }"#;
1292
1293 let pos: Position = serde_json::from_str(json).unwrap();
1294 assert_eq!(pos.proxy_wallet, "0xabc123");
1295 assert_eq!(pos.asset, "token123");
1296 assert_eq!(pos.condition_id, "cond456");
1297 assert!((pos.size - 100.5).abs() < f64::EPSILON);
1298 assert!((pos.avg_price - 0.65).abs() < f64::EPSILON);
1299 assert!((pos.initial_value - 65.0).abs() < f64::EPSILON);
1300 assert!((pos.current_value - 70.0).abs() < f64::EPSILON);
1301 assert!((pos.cash_pnl - 5.0).abs() < f64::EPSILON);
1302 assert!(!pos.redeemable);
1303 assert!(pos.mergeable);
1304 assert_eq!(pos.title, "Will X happen?");
1305 assert_eq!(pos.outcome, "Yes");
1306 assert_eq!(pos.outcome_index, 0);
1307 assert_eq!(pos.opposite_outcome, "No");
1308 assert!(!pos.negative_risk);
1309 assert_eq!(pos.icon, Some("https://example.com/icon.png".to_string()));
1310 assert_eq!(pos.event_slug, Some("x-event".to_string()));
1311 }
1312
1313 #[test]
1314 fn deserialize_position_with_null_optionals() {
1315 let json = r#"{
1316 "proxyWallet": "0xabc123",
1317 "asset": "token123",
1318 "conditionId": "cond456",
1319 "size": 0.0,
1320 "avgPrice": 0.0,
1321 "initialValue": 0.0,
1322 "currentValue": 0.0,
1323 "cashPnl": 0.0,
1324 "percentPnl": 0.0,
1325 "totalBought": 0.0,
1326 "realizedPnl": 0.0,
1327 "percentRealizedPnl": 0.0,
1328 "curPrice": 0.0,
1329 "redeemable": false,
1330 "mergeable": false,
1331 "title": "Test",
1332 "slug": "test",
1333 "icon": null,
1334 "eventSlug": null,
1335 "outcome": "No",
1336 "outcomeIndex": 1,
1337 "oppositeOutcome": "Yes",
1338 "oppositeAsset": "token000",
1339 "endDate": null,
1340 "negativeRisk": true
1341 }"#;
1342
1343 let pos: Position = serde_json::from_str(json).unwrap();
1344 assert!(pos.icon.is_none());
1345 assert!(pos.event_slug.is_none());
1346 assert!(pos.end_date.is_none());
1347 assert!(pos.negative_risk);
1348 }
1349
1350 #[test]
1351 fn deserialize_closed_position_from_json() {
1352 let json = r#"{
1353 "proxyWallet": "0xdef456",
1354 "asset": "token_closed",
1355 "conditionId": "cond_closed",
1356 "avgPrice": 0.45,
1357 "totalBought": 200.0,
1358 "realizedPnl": -10.0,
1359 "curPrice": 0.35,
1360 "timestamp": 1700000000,
1361 "title": "Closed market?",
1362 "slug": "closed-market",
1363 "icon": null,
1364 "eventSlug": "closed-event",
1365 "outcome": "No",
1366 "outcomeIndex": 1,
1367 "oppositeOutcome": "Yes",
1368 "oppositeAsset": "token_opp",
1369 "endDate": "2024-06-30"
1370 }"#;
1371
1372 let closed: ClosedPosition = serde_json::from_str(json).unwrap();
1373 assert_eq!(closed.proxy_wallet, "0xdef456");
1374 assert!((closed.avg_price - 0.45).abs() < f64::EPSILON);
1375 assert!((closed.realized_pnl - (-10.0)).abs() < f64::EPSILON);
1376 assert_eq!(closed.timestamp, 1700000000);
1377 assert_eq!(closed.outcome, "No");
1378 assert_eq!(closed.outcome_index, 1);
1379 assert!(closed.icon.is_none());
1380 assert_eq!(closed.event_slug, Some("closed-event".to_string()));
1381 }
1382
1383 #[test]
1384 fn deserialize_trade_from_json() {
1385 let json = r#"{
1386 "proxyWallet": "0x1234",
1387 "side": "BUY",
1388 "asset": "token_buy",
1389 "conditionId": "cond_trade",
1390 "size": 50.0,
1391 "price": 0.72,
1392 "timestamp": 1700001000,
1393 "title": "Trade market?",
1394 "slug": "trade-market",
1395 "icon": "https://example.com/trade.png",
1396 "eventSlug": null,
1397 "outcome": "Yes",
1398 "outcomeIndex": 0,
1399 "name": "TraderOne",
1400 "pseudonym": "t1",
1401 "bio": "A trader",
1402 "profileImage": null,
1403 "profileImageOptimized": null,
1404 "transactionHash": "0xhash123"
1405 }"#;
1406
1407 let trade: Trade = serde_json::from_str(json).unwrap();
1408 assert_eq!(trade.proxy_wallet, "0x1234");
1409 assert_eq!(trade.side, TradeSide::Buy);
1410 assert!((trade.size - 50.0).abs() < f64::EPSILON);
1411 assert!((trade.price - 0.72).abs() < f64::EPSILON);
1412 assert_eq!(trade.timestamp, 1700001000);
1413 assert_eq!(trade.name, Some("TraderOne".to_string()));
1414 assert_eq!(trade.transaction_hash, Some("0xhash123".to_string()));
1415 assert!(trade.profile_image.is_none());
1416 }
1417
1418 #[test]
1419 fn deserialize_trade_sell_side() {
1420 let json = r#"{
1421 "proxyWallet": "0x5678",
1422 "side": "SELL",
1423 "asset": "token_sell",
1424 "conditionId": "cond_sell",
1425 "size": 25.0,
1426 "price": 0.30,
1427 "timestamp": 1700002000,
1428 "title": "Sell test",
1429 "slug": "sell-test",
1430 "icon": null,
1431 "eventSlug": null,
1432 "outcome": "No",
1433 "outcomeIndex": 1,
1434 "name": null,
1435 "pseudonym": null,
1436 "bio": null,
1437 "profileImage": null,
1438 "profileImageOptimized": null,
1439 "transactionHash": null
1440 }"#;
1441
1442 let trade: Trade = serde_json::from_str(json).unwrap();
1443 assert_eq!(trade.side, TradeSide::Sell);
1444 assert!(trade.name.is_none());
1445 assert!(trade.transaction_hash.is_none());
1446 }
1447
1448 #[test]
1449 fn deserialize_activity_from_json() {
1450 let json = r#"{
1451 "proxyWallet": "0xact123",
1452 "timestamp": 1700003000,
1453 "conditionId": "cond_act",
1454 "type": "TRADE",
1455 "size": 10.0,
1456 "usdcSize": 7.50,
1457 "transactionHash": "0xacthash",
1458 "price": 0.75,
1459 "asset": "token_act",
1460 "side": "BUY",
1461 "outcomeIndex": 0,
1462 "title": "Activity market",
1463 "slug": "activity-market",
1464 "icon": null,
1465 "outcome": "Yes",
1466 "name": null,
1467 "pseudonym": null,
1468 "bio": null,
1469 "profileImage": null,
1470 "profileImageOptimized": null
1471 }"#;
1472
1473 let activity: Activity = serde_json::from_str(json).unwrap();
1474 assert_eq!(activity.proxy_wallet, "0xact123");
1475 assert_eq!(activity.activity_type, ActivityType::Trade);
1476 assert!((activity.size - 10.0).abs() < f64::EPSILON);
1477 assert!((activity.usdc_size - 7.50).abs() < f64::EPSILON);
1478 assert_eq!(activity.side, Some("BUY".to_string()));
1479 assert_eq!(activity.outcome_index, Some(0));
1480 }
1481
1482 #[test]
1483 fn deserialize_activity_merge_type() {
1484 let json = r#"{
1485 "proxyWallet": "0xmerge",
1486 "timestamp": 1700004000,
1487 "conditionId": "cond_merge",
1488 "type": "MERGE",
1489 "size": 5.0,
1490 "usdcSize": 3.0,
1491 "transactionHash": null,
1492 "price": null,
1493 "asset": null,
1494 "side": "",
1495 "outcomeIndex": null,
1496 "title": null,
1497 "slug": null,
1498 "icon": null,
1499 "outcome": null,
1500 "name": null,
1501 "pseudonym": null,
1502 "bio": null,
1503 "profileImage": null,
1504 "profileImageOptimized": null
1505 }"#;
1506
1507 let activity: Activity = serde_json::from_str(json).unwrap();
1508 assert_eq!(activity.activity_type, ActivityType::Merge);
1509 assert_eq!(activity.side, Some("".to_string()));
1511 assert!(activity.price.is_none());
1512 assert!(activity.asset.is_none());
1513 assert!(activity.title.is_none());
1514 }
1515
1516 #[test]
1517 fn deserialize_user_value() {
1518 let json = r#"{"user": "0xuser", "value": 1234.56}"#;
1519 let uv: UserValue = serde_json::from_str(json).unwrap();
1520 assert_eq!(uv.user, "0xuser");
1521 assert!((uv.value - 1234.56).abs() < f64::EPSILON);
1522 }
1523
1524 #[test]
1525 fn deserialize_open_interest() {
1526 let json = r#"{"market": "0xcond", "value": 50000.0}"#;
1527 let oi: OpenInterest = serde_json::from_str(json).unwrap();
1528 assert_eq!(oi.market, "0xcond");
1529 assert!((oi.value - 50000.0).abs() < f64::EPSILON);
1530 }
1531
1532 #[test]
1533 fn market_position_status_display_matches_serde() {
1534 for variant in [
1535 MarketPositionStatus::Open,
1536 MarketPositionStatus::Closed,
1537 MarketPositionStatus::All,
1538 ] {
1539 let serialized = serde_json::to_value(variant).unwrap();
1540 assert_eq!(format!("\"{}\"", variant), serialized.to_string());
1541 }
1542 }
1543
1544 #[test]
1545 fn market_position_status_default_is_all() {
1546 assert_eq!(MarketPositionStatus::default(), MarketPositionStatus::All);
1547 }
1548
1549 #[test]
1550 fn market_position_sort_by_display_matches_serde() {
1551 for variant in [
1552 MarketPositionSortBy::Tokens,
1553 MarketPositionSortBy::CashPnl,
1554 MarketPositionSortBy::RealizedPnl,
1555 MarketPositionSortBy::TotalPnl,
1556 ] {
1557 let serialized = serde_json::to_value(variant).unwrap();
1558 assert_eq!(format!("\"{}\"", variant), serialized.to_string());
1559 }
1560 }
1561
1562 #[test]
1563 fn market_position_sort_by_default_is_total_pnl() {
1564 assert_eq!(
1565 MarketPositionSortBy::default(),
1566 MarketPositionSortBy::TotalPnl
1567 );
1568 }
1569
1570 #[test]
1571 fn deserialize_market_position_v1() {
1572 let json = r#"{
1574 "proxyWallet": "0xabc",
1575 "name": "Alice",
1576 "profileImage": "https://example.com/a.png",
1577 "verified": true,
1578 "asset": "token_a",
1579 "conditionId": "cond_mp",
1580 "avgPrice": 0.42,
1581 "size": 1234.5,
1582 "currPrice": 0.51,
1583 "currentValue": 629.60,
1584 "cashPnl": 110.0,
1585 "totalBought": 520.0,
1586 "realizedPnl": 15.5,
1587 "totalPnl": 125.5,
1588 "outcome": "Yes",
1589 "outcomeIndex": 0
1590 }"#;
1591
1592 let pos: MarketPositionV1 = serde_json::from_str(json).unwrap();
1593 assert_eq!(pos.proxy_wallet, "0xabc");
1594 assert_eq!(pos.name, "Alice");
1595 assert_eq!(
1596 pos.profile_image.as_deref(),
1597 Some("https://example.com/a.png")
1598 );
1599 assert!(pos.verified);
1600 assert_eq!(pos.asset, "token_a");
1601 assert_eq!(pos.condition_id, "cond_mp");
1602 assert!((pos.avg_price - 0.42).abs() < f64::EPSILON);
1603 assert!((pos.size - 1234.5).abs() < f64::EPSILON);
1604 assert!((pos.curr_price - 0.51).abs() < f64::EPSILON);
1605 assert!((pos.current_value - 629.60).abs() < f64::EPSILON);
1606 assert!((pos.cash_pnl - 110.0).abs() < f64::EPSILON);
1607 assert!((pos.total_bought - 520.0).abs() < f64::EPSILON);
1608 assert!((pos.realized_pnl - 15.5).abs() < f64::EPSILON);
1609 assert!((pos.total_pnl - 125.5).abs() < f64::EPSILON);
1610 assert_eq!(pos.outcome, "Yes");
1611 assert_eq!(pos.outcome_index, 0);
1612 }
1613
1614 #[test]
1615 fn market_position_v1_roundtrip() {
1616 let original = MarketPositionV1 {
1617 proxy_wallet: "0xabc".into(),
1618 name: "Alice".into(),
1619 profile_image: None,
1620 verified: false,
1621 asset: "token_a".into(),
1622 condition_id: "cond_mp".into(),
1623 avg_price: 0.5,
1624 size: 10.0,
1625 curr_price: 0.6,
1626 current_value: 6.0,
1627 cash_pnl: 1.0,
1628 total_bought: 5.0,
1629 realized_pnl: 0.0,
1630 total_pnl: 1.0,
1631 outcome: "No".into(),
1632 outcome_index: 1,
1633 };
1634 let json = serde_json::to_string(&original).unwrap();
1635 assert!(json.contains("\"currPrice\""));
1637 let back: MarketPositionV1 = serde_json::from_str(&json).unwrap();
1638 assert_eq!(back.proxy_wallet, original.proxy_wallet);
1639 assert_eq!(back.outcome_index, original.outcome_index);
1640 assert!((back.curr_price - original.curr_price).abs() < f64::EPSILON);
1641 }
1642
1643 #[test]
1644 fn deserialize_meta_market_position_v1() {
1645 let json = r#"{
1646 "token": "token_a",
1647 "positions": [
1648 {
1649 "proxyWallet": "0xabc",
1650 "name": "Alice",
1651 "profileImage": null,
1652 "verified": false,
1653 "asset": "token_a",
1654 "conditionId": "cond_mp",
1655 "avgPrice": 0.42,
1656 "size": 100.0,
1657 "currPrice": 0.51,
1658 "currentValue": 51.0,
1659 "cashPnl": 9.0,
1660 "totalBought": 42.0,
1661 "realizedPnl": 0.0,
1662 "totalPnl": 9.0,
1663 "outcome": "Yes",
1664 "outcomeIndex": 0
1665 }
1666 ]
1667 }"#;
1668
1669 let meta: MetaMarketPositionV1 = serde_json::from_str(json).unwrap();
1670 assert_eq!(meta.token, "token_a");
1671 assert_eq!(meta.positions.len(), 1);
1672 assert_eq!(meta.positions[0].name, "Alice");
1673 assert!(meta.positions[0].profile_image.is_none());
1674 }
1675}