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))]
904#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct OtherSize {
906 pub id: Option<i64>,
908 pub user: Option<String>,
910 pub size: Option<f64>,
912}
913
914#[cfg_attr(feature = "specta", derive(specta::Type))]
916#[derive(Debug, Clone, Serialize, Deserialize)]
917pub struct RevisionEntry {
918 pub revision: Option<String>,
920 pub timestamp: Option<i64>,
922}
923
924#[cfg_attr(feature = "specta", derive(specta::Type))]
926#[derive(Debug, Clone, Serialize, Deserialize)]
927pub struct RevisionPayload {
928 #[serde(rename = "questionID")]
930 pub question_id: Option<String>,
931 #[serde(default)]
933 pub revisions: Vec<RevisionEntry>,
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939
940 #[test]
942 fn position_sort_by_display_matches_serde() {
943 let variants = [
944 PositionSortBy::Current,
945 PositionSortBy::Initial,
946 PositionSortBy::Tokens,
947 PositionSortBy::CashPnl,
948 PositionSortBy::PercentPnl,
949 PositionSortBy::Title,
950 PositionSortBy::Resolving,
951 PositionSortBy::Price,
952 PositionSortBy::AvgPrice,
953 ];
954 for variant in variants {
955 let serialized = serde_json::to_value(variant).unwrap();
956 let display = variant.to_string();
957 assert_eq!(
958 format!("\"{}\"", display),
959 serialized.to_string(),
960 "Display mismatch for {:?}",
961 variant
962 );
963 }
964 }
965
966 #[test]
968 fn closed_position_sort_by_display_matches_serde() {
969 let variants = [
970 ClosedPositionSortBy::RealizedPnl,
971 ClosedPositionSortBy::Title,
972 ClosedPositionSortBy::Price,
973 ClosedPositionSortBy::AvgPrice,
974 ClosedPositionSortBy::Timestamp,
975 ];
976 for variant in variants {
977 let serialized = serde_json::to_value(variant).unwrap();
978 let display = variant.to_string();
979 assert_eq!(
980 format!("\"{}\"", display),
981 serialized.to_string(),
982 "Display mismatch for {:?}",
983 variant
984 );
985 }
986 }
987
988 #[test]
989 fn activity_sort_by_display_matches_serde() {
990 let variants = [
991 ActivitySortBy::Timestamp,
992 ActivitySortBy::Tokens,
993 ActivitySortBy::Cash,
994 ];
995 for variant in variants {
996 let serialized = serde_json::to_value(variant).unwrap();
997 let display = variant.to_string();
998 assert_eq!(
999 format!("\"{}\"", display),
1000 serialized.to_string(),
1001 "Display mismatch for {:?}",
1002 variant
1003 );
1004 }
1005 }
1006
1007 #[test]
1008 fn sort_direction_display_matches_serde() {
1009 let variants = [SortDirection::Asc, SortDirection::Desc];
1010 for variant in variants {
1011 let serialized = serde_json::to_value(variant).unwrap();
1012 let display = variant.to_string();
1013 assert_eq!(
1014 format!("\"{}\"", display),
1015 serialized.to_string(),
1016 "Display mismatch for {:?}",
1017 variant
1018 );
1019 }
1020 }
1021
1022 #[test]
1023 fn trade_side_display_matches_serde() {
1024 let variants = [TradeSide::Buy, TradeSide::Sell, TradeSide::Unknown];
1025 for variant in variants {
1026 let serialized = serde_json::to_value(variant).unwrap();
1027 let display = variant.to_string();
1028 assert_eq!(
1029 format!("\"{}\"", display),
1030 serialized.to_string(),
1031 "Display mismatch for {:?}",
1032 variant
1033 );
1034 }
1035 }
1036
1037 #[test]
1038 fn trade_side_falls_back_to_unknown_for_future_values() {
1039 let result: TradeSide = serde_json::from_str("\"SOME_FUTURE_SIDE\"").unwrap();
1040 assert_eq!(result, TradeSide::Unknown);
1041 }
1042
1043 #[test]
1044 fn trade_filter_type_display_matches_serde() {
1045 let variants = [TradeFilterType::Cash, TradeFilterType::Tokens];
1046 for variant in variants {
1047 let serialized = serde_json::to_value(variant).unwrap();
1048 let display = variant.to_string();
1049 assert_eq!(
1050 format!("\"{}\"", display),
1051 serialized.to_string(),
1052 "Display mismatch for {:?}",
1053 variant
1054 );
1055 }
1056 }
1057
1058 #[test]
1059 fn activity_type_display_matches_serde() {
1060 let variants = [
1061 ActivityType::Trade,
1062 ActivityType::Split,
1063 ActivityType::Merge,
1064 ActivityType::Redeem,
1065 ActivityType::Reward,
1066 ActivityType::Conversion,
1067 ActivityType::MakerRebate,
1068 ActivityType::ReferralReward,
1069 ActivityType::TakerRebate,
1070 ActivityType::Unknown,
1071 ];
1072 for variant in variants {
1073 let serialized = serde_json::to_value(variant).unwrap();
1074 let display = variant.to_string();
1075 assert_eq!(
1076 format!("\"{}\"", display),
1077 serialized.to_string(),
1078 "Display mismatch for {:?}",
1079 variant
1080 );
1081 }
1082 }
1083
1084 #[test]
1085 fn activity_type_roundtrip_serde() {
1086 for variant in [
1087 ActivityType::Trade,
1088 ActivityType::Split,
1089 ActivityType::Merge,
1090 ActivityType::Redeem,
1091 ActivityType::Reward,
1092 ActivityType::Conversion,
1093 ActivityType::MakerRebate,
1094 ActivityType::ReferralReward,
1095 ActivityType::TakerRebate,
1096 ] {
1097 let json = serde_json::to_string(&variant).unwrap();
1098 let deserialized: ActivityType = serde_json::from_str(&json).unwrap();
1099 assert_eq!(variant, deserialized);
1100 }
1101 }
1102
1103 #[test]
1104 fn activity_type_falls_back_to_unknown_for_future_types() {
1105 let result: ActivityType = serde_json::from_str("\"SOME_FUTURE_TYPE\"").unwrap();
1107 assert_eq!(result, ActivityType::Unknown);
1108
1109 let result: ActivityType = serde_json::from_str("\"trade\"").unwrap();
1111 assert_eq!(result, ActivityType::Unknown);
1112 }
1113
1114 #[test]
1115 fn sort_direction_default_is_desc() {
1116 assert_eq!(SortDirection::default(), SortDirection::Desc);
1117 }
1118
1119 #[test]
1120 fn closed_position_sort_by_default_is_realized_pnl() {
1121 assert_eq!(
1122 ClosedPositionSortBy::default(),
1123 ClosedPositionSortBy::RealizedPnl
1124 );
1125 }
1126
1127 #[test]
1128 fn activity_sort_by_default_is_timestamp() {
1129 assert_eq!(ActivitySortBy::default(), ActivitySortBy::Timestamp);
1130 }
1131
1132 #[test]
1133 fn position_sort_by_serde_roundtrip() {
1134 for variant in [
1135 PositionSortBy::Current,
1136 PositionSortBy::Initial,
1137 PositionSortBy::Tokens,
1138 PositionSortBy::CashPnl,
1139 PositionSortBy::PercentPnl,
1140 PositionSortBy::Title,
1141 PositionSortBy::Resolving,
1142 PositionSortBy::Price,
1143 PositionSortBy::AvgPrice,
1144 ] {
1145 let json = serde_json::to_string(&variant).unwrap();
1146 let deserialized: PositionSortBy = serde_json::from_str(&json).unwrap();
1147 assert_eq!(variant, deserialized);
1148 }
1149 }
1150
1151 #[test]
1152 fn deserialize_position_from_json() {
1153 let json = r#"{
1154 "proxyWallet": "0xabc123",
1155 "asset": "token123",
1156 "conditionId": "cond456",
1157 "size": 100.5,
1158 "avgPrice": 0.65,
1159 "initialValue": 65.0,
1160 "currentValue": 70.0,
1161 "cashPnl": 5.0,
1162 "percentPnl": 7.69,
1163 "totalBought": 100.5,
1164 "realizedPnl": 2.0,
1165 "percentRealizedPnl": 3.08,
1166 "curPrice": 0.70,
1167 "redeemable": false,
1168 "mergeable": true,
1169 "title": "Will X happen?",
1170 "slug": "will-x-happen",
1171 "icon": "https://example.com/icon.png",
1172 "eventSlug": "x-event",
1173 "outcome": "Yes",
1174 "outcomeIndex": 0,
1175 "oppositeOutcome": "No",
1176 "oppositeAsset": "token789",
1177 "endDate": "2025-12-31",
1178 "negativeRisk": false
1179 }"#;
1180
1181 let pos: Position = serde_json::from_str(json).unwrap();
1182 assert_eq!(pos.proxy_wallet, "0xabc123");
1183 assert_eq!(pos.asset, "token123");
1184 assert_eq!(pos.condition_id, "cond456");
1185 assert!((pos.size - 100.5).abs() < f64::EPSILON);
1186 assert!((pos.avg_price - 0.65).abs() < f64::EPSILON);
1187 assert!((pos.initial_value - 65.0).abs() < f64::EPSILON);
1188 assert!((pos.current_value - 70.0).abs() < f64::EPSILON);
1189 assert!((pos.cash_pnl - 5.0).abs() < f64::EPSILON);
1190 assert!(!pos.redeemable);
1191 assert!(pos.mergeable);
1192 assert_eq!(pos.title, "Will X happen?");
1193 assert_eq!(pos.outcome, "Yes");
1194 assert_eq!(pos.outcome_index, 0);
1195 assert_eq!(pos.opposite_outcome, "No");
1196 assert!(!pos.negative_risk);
1197 assert_eq!(pos.icon, Some("https://example.com/icon.png".to_string()));
1198 assert_eq!(pos.event_slug, Some("x-event".to_string()));
1199 }
1200
1201 #[test]
1202 fn deserialize_position_with_null_optionals() {
1203 let json = r#"{
1204 "proxyWallet": "0xabc123",
1205 "asset": "token123",
1206 "conditionId": "cond456",
1207 "size": 0.0,
1208 "avgPrice": 0.0,
1209 "initialValue": 0.0,
1210 "currentValue": 0.0,
1211 "cashPnl": 0.0,
1212 "percentPnl": 0.0,
1213 "totalBought": 0.0,
1214 "realizedPnl": 0.0,
1215 "percentRealizedPnl": 0.0,
1216 "curPrice": 0.0,
1217 "redeemable": false,
1218 "mergeable": false,
1219 "title": "Test",
1220 "slug": "test",
1221 "icon": null,
1222 "eventSlug": null,
1223 "outcome": "No",
1224 "outcomeIndex": 1,
1225 "oppositeOutcome": "Yes",
1226 "oppositeAsset": "token000",
1227 "endDate": null,
1228 "negativeRisk": true
1229 }"#;
1230
1231 let pos: Position = serde_json::from_str(json).unwrap();
1232 assert!(pos.icon.is_none());
1233 assert!(pos.event_slug.is_none());
1234 assert!(pos.end_date.is_none());
1235 assert!(pos.negative_risk);
1236 }
1237
1238 #[test]
1239 fn deserialize_closed_position_from_json() {
1240 let json = r#"{
1241 "proxyWallet": "0xdef456",
1242 "asset": "token_closed",
1243 "conditionId": "cond_closed",
1244 "avgPrice": 0.45,
1245 "totalBought": 200.0,
1246 "realizedPnl": -10.0,
1247 "curPrice": 0.35,
1248 "timestamp": 1700000000,
1249 "title": "Closed market?",
1250 "slug": "closed-market",
1251 "icon": null,
1252 "eventSlug": "closed-event",
1253 "outcome": "No",
1254 "outcomeIndex": 1,
1255 "oppositeOutcome": "Yes",
1256 "oppositeAsset": "token_opp",
1257 "endDate": "2024-06-30"
1258 }"#;
1259
1260 let closed: ClosedPosition = serde_json::from_str(json).unwrap();
1261 assert_eq!(closed.proxy_wallet, "0xdef456");
1262 assert!((closed.avg_price - 0.45).abs() < f64::EPSILON);
1263 assert!((closed.realized_pnl - (-10.0)).abs() < f64::EPSILON);
1264 assert_eq!(closed.timestamp, 1700000000);
1265 assert_eq!(closed.outcome, "No");
1266 assert_eq!(closed.outcome_index, 1);
1267 assert!(closed.icon.is_none());
1268 assert_eq!(closed.event_slug, Some("closed-event".to_string()));
1269 }
1270
1271 #[test]
1272 fn deserialize_trade_from_json() {
1273 let json = r#"{
1274 "proxyWallet": "0x1234",
1275 "side": "BUY",
1276 "asset": "token_buy",
1277 "conditionId": "cond_trade",
1278 "size": 50.0,
1279 "price": 0.72,
1280 "timestamp": 1700001000,
1281 "title": "Trade market?",
1282 "slug": "trade-market",
1283 "icon": "https://example.com/trade.png",
1284 "eventSlug": null,
1285 "outcome": "Yes",
1286 "outcomeIndex": 0,
1287 "name": "TraderOne",
1288 "pseudonym": "t1",
1289 "bio": "A trader",
1290 "profileImage": null,
1291 "profileImageOptimized": null,
1292 "transactionHash": "0xhash123"
1293 }"#;
1294
1295 let trade: Trade = serde_json::from_str(json).unwrap();
1296 assert_eq!(trade.proxy_wallet, "0x1234");
1297 assert_eq!(trade.side, TradeSide::Buy);
1298 assert!((trade.size - 50.0).abs() < f64::EPSILON);
1299 assert!((trade.price - 0.72).abs() < f64::EPSILON);
1300 assert_eq!(trade.timestamp, 1700001000);
1301 assert_eq!(trade.name, Some("TraderOne".to_string()));
1302 assert_eq!(trade.transaction_hash, Some("0xhash123".to_string()));
1303 assert!(trade.profile_image.is_none());
1304 }
1305
1306 #[test]
1307 fn deserialize_trade_sell_side() {
1308 let json = r#"{
1309 "proxyWallet": "0x5678",
1310 "side": "SELL",
1311 "asset": "token_sell",
1312 "conditionId": "cond_sell",
1313 "size": 25.0,
1314 "price": 0.30,
1315 "timestamp": 1700002000,
1316 "title": "Sell test",
1317 "slug": "sell-test",
1318 "icon": null,
1319 "eventSlug": null,
1320 "outcome": "No",
1321 "outcomeIndex": 1,
1322 "name": null,
1323 "pseudonym": null,
1324 "bio": null,
1325 "profileImage": null,
1326 "profileImageOptimized": null,
1327 "transactionHash": null
1328 }"#;
1329
1330 let trade: Trade = serde_json::from_str(json).unwrap();
1331 assert_eq!(trade.side, TradeSide::Sell);
1332 assert!(trade.name.is_none());
1333 assert!(trade.transaction_hash.is_none());
1334 }
1335
1336 #[test]
1337 fn deserialize_activity_from_json() {
1338 let json = r#"{
1339 "proxyWallet": "0xact123",
1340 "timestamp": 1700003000,
1341 "conditionId": "cond_act",
1342 "type": "TRADE",
1343 "size": 10.0,
1344 "usdcSize": 7.50,
1345 "transactionHash": "0xacthash",
1346 "price": 0.75,
1347 "asset": "token_act",
1348 "side": "BUY",
1349 "outcomeIndex": 0,
1350 "title": "Activity market",
1351 "slug": "activity-market",
1352 "icon": null,
1353 "outcome": "Yes",
1354 "name": null,
1355 "pseudonym": null,
1356 "bio": null,
1357 "profileImage": null,
1358 "profileImageOptimized": null
1359 }"#;
1360
1361 let activity: Activity = serde_json::from_str(json).unwrap();
1362 assert_eq!(activity.proxy_wallet, "0xact123");
1363 assert_eq!(activity.activity_type, ActivityType::Trade);
1364 assert!((activity.size - 10.0).abs() < f64::EPSILON);
1365 assert!((activity.usdc_size - 7.50).abs() < f64::EPSILON);
1366 assert_eq!(activity.side, Some("BUY".to_string()));
1367 assert_eq!(activity.outcome_index, Some(0));
1368 }
1369
1370 #[test]
1371 fn deserialize_activity_merge_type() {
1372 let json = r#"{
1373 "proxyWallet": "0xmerge",
1374 "timestamp": 1700004000,
1375 "conditionId": "cond_merge",
1376 "type": "MERGE",
1377 "size": 5.0,
1378 "usdcSize": 3.0,
1379 "transactionHash": null,
1380 "price": null,
1381 "asset": null,
1382 "side": "",
1383 "outcomeIndex": null,
1384 "title": null,
1385 "slug": null,
1386 "icon": null,
1387 "outcome": null,
1388 "name": null,
1389 "pseudonym": null,
1390 "bio": null,
1391 "profileImage": null,
1392 "profileImageOptimized": null
1393 }"#;
1394
1395 let activity: Activity = serde_json::from_str(json).unwrap();
1396 assert_eq!(activity.activity_type, ActivityType::Merge);
1397 assert_eq!(activity.side, Some("".to_string()));
1399 assert!(activity.price.is_none());
1400 assert!(activity.asset.is_none());
1401 assert!(activity.title.is_none());
1402 }
1403
1404 #[test]
1405 fn deserialize_user_value() {
1406 let json = r#"{"user": "0xuser", "value": 1234.56}"#;
1407 let uv: UserValue = serde_json::from_str(json).unwrap();
1408 assert_eq!(uv.user, "0xuser");
1409 assert!((uv.value - 1234.56).abs() < f64::EPSILON);
1410 }
1411
1412 #[test]
1413 fn deserialize_open_interest() {
1414 let json = r#"{"market": "0xcond", "value": 50000.0}"#;
1415 let oi: OpenInterest = serde_json::from_str(json).unwrap();
1416 assert_eq!(oi.market, "0xcond");
1417 assert!((oi.value - 50000.0).abs() < f64::EPSILON);
1418 }
1419
1420 #[test]
1421 fn market_position_status_display_matches_serde() {
1422 for variant in [
1423 MarketPositionStatus::Open,
1424 MarketPositionStatus::Closed,
1425 MarketPositionStatus::All,
1426 ] {
1427 let serialized = serde_json::to_value(variant).unwrap();
1428 assert_eq!(format!("\"{}\"", variant), serialized.to_string());
1429 }
1430 }
1431
1432 #[test]
1433 fn market_position_status_default_is_all() {
1434 assert_eq!(MarketPositionStatus::default(), MarketPositionStatus::All);
1435 }
1436
1437 #[test]
1438 fn market_position_sort_by_display_matches_serde() {
1439 for variant in [
1440 MarketPositionSortBy::Tokens,
1441 MarketPositionSortBy::CashPnl,
1442 MarketPositionSortBy::RealizedPnl,
1443 MarketPositionSortBy::TotalPnl,
1444 ] {
1445 let serialized = serde_json::to_value(variant).unwrap();
1446 assert_eq!(format!("\"{}\"", variant), serialized.to_string());
1447 }
1448 }
1449
1450 #[test]
1451 fn market_position_sort_by_default_is_total_pnl() {
1452 assert_eq!(
1453 MarketPositionSortBy::default(),
1454 MarketPositionSortBy::TotalPnl
1455 );
1456 }
1457
1458 #[test]
1459 fn deserialize_market_position_v1() {
1460 let json = r#"{
1462 "proxyWallet": "0xabc",
1463 "name": "Alice",
1464 "profileImage": "https://example.com/a.png",
1465 "verified": true,
1466 "asset": "token_a",
1467 "conditionId": "cond_mp",
1468 "avgPrice": 0.42,
1469 "size": 1234.5,
1470 "currPrice": 0.51,
1471 "currentValue": 629.60,
1472 "cashPnl": 110.0,
1473 "totalBought": 520.0,
1474 "realizedPnl": 15.5,
1475 "totalPnl": 125.5,
1476 "outcome": "Yes",
1477 "outcomeIndex": 0
1478 }"#;
1479
1480 let pos: MarketPositionV1 = serde_json::from_str(json).unwrap();
1481 assert_eq!(pos.proxy_wallet, "0xabc");
1482 assert_eq!(pos.name, "Alice");
1483 assert_eq!(
1484 pos.profile_image.as_deref(),
1485 Some("https://example.com/a.png")
1486 );
1487 assert!(pos.verified);
1488 assert_eq!(pos.asset, "token_a");
1489 assert_eq!(pos.condition_id, "cond_mp");
1490 assert!((pos.avg_price - 0.42).abs() < f64::EPSILON);
1491 assert!((pos.size - 1234.5).abs() < f64::EPSILON);
1492 assert!((pos.curr_price - 0.51).abs() < f64::EPSILON);
1493 assert!((pos.current_value - 629.60).abs() < f64::EPSILON);
1494 assert!((pos.cash_pnl - 110.0).abs() < f64::EPSILON);
1495 assert!((pos.total_bought - 520.0).abs() < f64::EPSILON);
1496 assert!((pos.realized_pnl - 15.5).abs() < f64::EPSILON);
1497 assert!((pos.total_pnl - 125.5).abs() < f64::EPSILON);
1498 assert_eq!(pos.outcome, "Yes");
1499 assert_eq!(pos.outcome_index, 0);
1500 }
1501
1502 #[test]
1503 fn market_position_v1_roundtrip() {
1504 let original = MarketPositionV1 {
1505 proxy_wallet: "0xabc".into(),
1506 name: "Alice".into(),
1507 profile_image: None,
1508 verified: false,
1509 asset: "token_a".into(),
1510 condition_id: "cond_mp".into(),
1511 avg_price: 0.5,
1512 size: 10.0,
1513 curr_price: 0.6,
1514 current_value: 6.0,
1515 cash_pnl: 1.0,
1516 total_bought: 5.0,
1517 realized_pnl: 0.0,
1518 total_pnl: 1.0,
1519 outcome: "No".into(),
1520 outcome_index: 1,
1521 };
1522 let json = serde_json::to_string(&original).unwrap();
1523 assert!(json.contains("\"currPrice\""));
1525 let back: MarketPositionV1 = serde_json::from_str(&json).unwrap();
1526 assert_eq!(back.proxy_wallet, original.proxy_wallet);
1527 assert_eq!(back.outcome_index, original.outcome_index);
1528 assert!((back.curr_price - original.curr_price).abs() < f64::EPSILON);
1529 }
1530
1531 #[test]
1532 fn deserialize_meta_market_position_v1() {
1533 let json = r#"{
1534 "token": "token_a",
1535 "positions": [
1536 {
1537 "proxyWallet": "0xabc",
1538 "name": "Alice",
1539 "profileImage": null,
1540 "verified": false,
1541 "asset": "token_a",
1542 "conditionId": "cond_mp",
1543 "avgPrice": 0.42,
1544 "size": 100.0,
1545 "currPrice": 0.51,
1546 "currentValue": 51.0,
1547 "cashPnl": 9.0,
1548 "totalBought": 42.0,
1549 "realizedPnl": 0.0,
1550 "totalPnl": 9.0,
1551 "outcome": "Yes",
1552 "outcomeIndex": 0
1553 }
1554 ]
1555 }"#;
1556
1557 let meta: MetaMarketPositionV1 = serde_json::from_str(json).unwrap();
1558 assert_eq!(meta.token, "token_a");
1559 assert_eq!(meta.positions.len(), 1);
1560 assert_eq!(meta.positions[0].name, "Alice");
1561 assert!(meta.positions[0].profile_image.is_none());
1562 }
1563}