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}
167
168impl std::fmt::Display for TradeSide {
169 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170 match self {
171 Self::Buy => write!(f, "BUY"),
172 Self::Sell => write!(f, "SELL"),
173 }
174 }
175}
176
177#[cfg_attr(feature = "specta", derive(specta::Type))]
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180#[serde(rename_all = "UPPERCASE")]
181pub enum TradeFilterType {
182 Cash,
184 Tokens,
186}
187
188impl std::fmt::Display for TradeFilterType {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match self {
191 Self::Cash => write!(f, "CASH"),
192 Self::Tokens => write!(f, "TOKENS"),
193 }
194 }
195}
196
197#[cfg_attr(feature = "specta", derive(specta::Type))]
199#[derive(Debug, Clone, Serialize, Deserialize)]
200#[serde(rename_all = "camelCase")]
201pub struct Trade {
202 pub proxy_wallet: String,
204 pub side: TradeSide,
206 pub asset: String,
208 pub condition_id: String,
210 pub size: f64,
212 pub price: f64,
214 #[cfg_attr(feature = "specta", specta(type = f64))]
216 pub timestamp: i64,
217 pub title: String,
219 pub slug: String,
221 pub icon: Option<String>,
223 pub event_slug: Option<String>,
225 pub outcome: String,
227 pub outcome_index: u32,
229 pub name: Option<String>,
231 pub pseudonym: Option<String>,
233 pub bio: Option<String>,
235 pub profile_image: Option<String>,
237 pub profile_image_optimized: Option<String>,
239 pub transaction_hash: Option<String>,
241}
242
243#[cfg_attr(feature = "specta", derive(specta::Type))]
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246#[serde(rename_all = "UPPERCASE")]
247pub enum ActivityType {
248 Trade,
250 Split,
252 Merge,
254 Redeem,
256 Reward,
258 Conversion,
260}
261
262impl std::fmt::Display for ActivityType {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 match self {
265 Self::Trade => write!(f, "TRADE"),
266 Self::Split => write!(f, "SPLIT"),
267 Self::Merge => write!(f, "MERGE"),
268 Self::Redeem => write!(f, "REDEEM"),
269 Self::Reward => write!(f, "REWARD"),
270 Self::Conversion => write!(f, "CONVERSION"),
271 }
272 }
273}
274
275#[cfg_attr(feature = "specta", derive(specta::Type))]
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
278#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
279pub enum ActivitySortBy {
280 #[default]
282 Timestamp,
283 Tokens,
285 Cash,
287}
288
289impl std::fmt::Display for ActivitySortBy {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 match self {
292 Self::Timestamp => write!(f, "TIMESTAMP"),
293 Self::Tokens => write!(f, "TOKENS"),
294 Self::Cash => write!(f, "CASH"),
295 }
296 }
297}
298
299#[cfg_attr(feature = "specta", derive(specta::Type))]
301#[derive(Debug, Clone, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct Activity {
304 pub proxy_wallet: String,
306 #[cfg_attr(feature = "specta", specta(type = f64))]
308 pub timestamp: i64,
309 pub condition_id: String,
311 #[serde(rename = "type")]
313 pub activity_type: ActivityType,
314 pub size: f64,
316 pub usdc_size: f64,
318 pub transaction_hash: Option<String>,
320 pub price: Option<f64>,
322 pub asset: Option<String>,
324 pub side: Option<String>,
327 pub outcome_index: Option<u32>,
329 pub title: Option<String>,
331 pub slug: Option<String>,
333 pub icon: Option<String>,
335 pub outcome: Option<String>,
337 pub name: Option<String>,
339 pub pseudonym: Option<String>,
341 pub bio: Option<String>,
343 pub profile_image: Option<String>,
345 pub profile_image_optimized: Option<String>,
347}
348
349#[cfg_attr(feature = "specta", derive(specta::Type))]
351#[derive(Debug, Clone, Serialize, Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct Position {
354 pub proxy_wallet: String,
356 pub asset: String,
358 pub condition_id: String,
360 pub size: f64,
362 pub avg_price: f64,
364 pub initial_value: f64,
366 pub current_value: f64,
368 pub cash_pnl: f64,
370 pub percent_pnl: f64,
372 pub total_bought: f64,
374 pub realized_pnl: f64,
376 pub percent_realized_pnl: f64,
378 pub cur_price: f64,
380 pub redeemable: bool,
382 pub mergeable: bool,
384 pub title: String,
386 pub slug: String,
388 pub icon: Option<String>,
390 pub event_slug: Option<String>,
392 pub outcome: String,
394 pub outcome_index: u32,
396 pub opposite_outcome: String,
398 pub opposite_asset: String,
400 pub end_date: Option<String>,
402 pub negative_risk: bool,
404}
405
406#[cfg_attr(feature = "specta", derive(specta::Type))]
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
409#[serde(rename_all = "UPPERCASE")]
410pub enum TimePeriod {
411 #[default]
413 Day,
414 Week,
416 Month,
418 All,
420}
421
422impl std::fmt::Display for TimePeriod {
423 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
424 match self {
425 Self::Day => write!(f, "DAY"),
426 Self::Week => write!(f, "WEEK"),
427 Self::Month => write!(f, "MONTH"),
428 Self::All => write!(f, "ALL"),
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
439 fn position_sort_by_display_matches_serde() {
440 let variants = [
441 PositionSortBy::Current,
442 PositionSortBy::Initial,
443 PositionSortBy::Tokens,
444 PositionSortBy::CashPnl,
445 PositionSortBy::PercentPnl,
446 PositionSortBy::Title,
447 PositionSortBy::Resolving,
448 PositionSortBy::Price,
449 PositionSortBy::AvgPrice,
450 ];
451 for variant in variants {
452 let serialized = serde_json::to_value(variant).unwrap();
453 let display = variant.to_string();
454 assert_eq!(
455 format!("\"{}\"", display),
456 serialized.to_string(),
457 "Display mismatch for {:?}",
458 variant
459 );
460 }
461 }
462
463 #[test]
465 fn closed_position_sort_by_display_matches_serde() {
466 let variants = [
467 ClosedPositionSortBy::RealizedPnl,
468 ClosedPositionSortBy::Title,
469 ClosedPositionSortBy::Price,
470 ClosedPositionSortBy::AvgPrice,
471 ClosedPositionSortBy::Timestamp,
472 ];
473 for variant in variants {
474 let serialized = serde_json::to_value(variant).unwrap();
475 let display = variant.to_string();
476 assert_eq!(
477 format!("\"{}\"", display),
478 serialized.to_string(),
479 "Display mismatch for {:?}",
480 variant
481 );
482 }
483 }
484
485 #[test]
486 fn activity_sort_by_display_matches_serde() {
487 let variants = [
488 ActivitySortBy::Timestamp,
489 ActivitySortBy::Tokens,
490 ActivitySortBy::Cash,
491 ];
492 for variant in variants {
493 let serialized = serde_json::to_value(variant).unwrap();
494 let display = variant.to_string();
495 assert_eq!(
496 format!("\"{}\"", display),
497 serialized.to_string(),
498 "Display mismatch for {:?}",
499 variant
500 );
501 }
502 }
503
504 #[test]
505 fn sort_direction_display_matches_serde() {
506 let variants = [SortDirection::Asc, SortDirection::Desc];
507 for variant in variants {
508 let serialized = serde_json::to_value(variant).unwrap();
509 let display = variant.to_string();
510 assert_eq!(
511 format!("\"{}\"", display),
512 serialized.to_string(),
513 "Display mismatch for {:?}",
514 variant
515 );
516 }
517 }
518
519 #[test]
520 fn trade_side_display_matches_serde() {
521 let variants = [TradeSide::Buy, TradeSide::Sell];
522 for variant in variants {
523 let serialized = serde_json::to_value(variant).unwrap();
524 let display = variant.to_string();
525 assert_eq!(
526 format!("\"{}\"", display),
527 serialized.to_string(),
528 "Display mismatch for {:?}",
529 variant
530 );
531 }
532 }
533
534 #[test]
535 fn trade_filter_type_display_matches_serde() {
536 let variants = [TradeFilterType::Cash, TradeFilterType::Tokens];
537 for variant in variants {
538 let serialized = serde_json::to_value(variant).unwrap();
539 let display = variant.to_string();
540 assert_eq!(
541 format!("\"{}\"", display),
542 serialized.to_string(),
543 "Display mismatch for {:?}",
544 variant
545 );
546 }
547 }
548
549 #[test]
550 fn activity_type_display_matches_serde() {
551 let variants = [
552 ActivityType::Trade,
553 ActivityType::Split,
554 ActivityType::Merge,
555 ActivityType::Redeem,
556 ActivityType::Reward,
557 ActivityType::Conversion,
558 ];
559 for variant in variants {
560 let serialized = serde_json::to_value(variant).unwrap();
561 let display = variant.to_string();
562 assert_eq!(
563 format!("\"{}\"", display),
564 serialized.to_string(),
565 "Display mismatch for {:?}",
566 variant
567 );
568 }
569 }
570
571 #[test]
572 fn activity_type_roundtrip_serde() {
573 for variant in [
574 ActivityType::Trade,
575 ActivityType::Split,
576 ActivityType::Merge,
577 ActivityType::Redeem,
578 ActivityType::Reward,
579 ActivityType::Conversion,
580 ] {
581 let json = serde_json::to_string(&variant).unwrap();
582 let deserialized: ActivityType = serde_json::from_str(&json).unwrap();
583 assert_eq!(variant, deserialized);
584 }
585 }
586
587 #[test]
588 fn activity_type_rejects_unknown_variant() {
589 let result = serde_json::from_str::<ActivityType>("\"UNKNOWN\"");
590 assert!(result.is_err(), "should reject unknown activity type");
591 }
592
593 #[test]
594 fn activity_type_rejects_lowercase() {
595 let result = serde_json::from_str::<ActivityType>("\"trade\"");
596 assert!(result.is_err(), "should reject lowercase activity type");
597 }
598
599 #[test]
600 fn sort_direction_default_is_desc() {
601 assert_eq!(SortDirection::default(), SortDirection::Desc);
602 }
603
604 #[test]
605 fn closed_position_sort_by_default_is_realized_pnl() {
606 assert_eq!(
607 ClosedPositionSortBy::default(),
608 ClosedPositionSortBy::RealizedPnl
609 );
610 }
611
612 #[test]
613 fn activity_sort_by_default_is_timestamp() {
614 assert_eq!(ActivitySortBy::default(), ActivitySortBy::Timestamp);
615 }
616
617 #[test]
618 fn position_sort_by_serde_roundtrip() {
619 for variant in [
620 PositionSortBy::Current,
621 PositionSortBy::Initial,
622 PositionSortBy::Tokens,
623 PositionSortBy::CashPnl,
624 PositionSortBy::PercentPnl,
625 PositionSortBy::Title,
626 PositionSortBy::Resolving,
627 PositionSortBy::Price,
628 PositionSortBy::AvgPrice,
629 ] {
630 let json = serde_json::to_string(&variant).unwrap();
631 let deserialized: PositionSortBy = serde_json::from_str(&json).unwrap();
632 assert_eq!(variant, deserialized);
633 }
634 }
635
636 #[test]
637 fn deserialize_position_from_json() {
638 let json = r#"{
639 "proxyWallet": "0xabc123",
640 "asset": "token123",
641 "conditionId": "cond456",
642 "size": 100.5,
643 "avgPrice": 0.65,
644 "initialValue": 65.0,
645 "currentValue": 70.0,
646 "cashPnl": 5.0,
647 "percentPnl": 7.69,
648 "totalBought": 100.5,
649 "realizedPnl": 2.0,
650 "percentRealizedPnl": 3.08,
651 "curPrice": 0.70,
652 "redeemable": false,
653 "mergeable": true,
654 "title": "Will X happen?",
655 "slug": "will-x-happen",
656 "icon": "https://example.com/icon.png",
657 "eventSlug": "x-event",
658 "outcome": "Yes",
659 "outcomeIndex": 0,
660 "oppositeOutcome": "No",
661 "oppositeAsset": "token789",
662 "endDate": "2025-12-31",
663 "negativeRisk": false
664 }"#;
665
666 let pos: Position = serde_json::from_str(json).unwrap();
667 assert_eq!(pos.proxy_wallet, "0xabc123");
668 assert_eq!(pos.asset, "token123");
669 assert_eq!(pos.condition_id, "cond456");
670 assert!((pos.size - 100.5).abs() < f64::EPSILON);
671 assert!((pos.avg_price - 0.65).abs() < f64::EPSILON);
672 assert!((pos.initial_value - 65.0).abs() < f64::EPSILON);
673 assert!((pos.current_value - 70.0).abs() < f64::EPSILON);
674 assert!((pos.cash_pnl - 5.0).abs() < f64::EPSILON);
675 assert!(!pos.redeemable);
676 assert!(pos.mergeable);
677 assert_eq!(pos.title, "Will X happen?");
678 assert_eq!(pos.outcome, "Yes");
679 assert_eq!(pos.outcome_index, 0);
680 assert_eq!(pos.opposite_outcome, "No");
681 assert!(!pos.negative_risk);
682 assert_eq!(pos.icon, Some("https://example.com/icon.png".to_string()));
683 assert_eq!(pos.event_slug, Some("x-event".to_string()));
684 }
685
686 #[test]
687 fn deserialize_position_with_null_optionals() {
688 let json = r#"{
689 "proxyWallet": "0xabc123",
690 "asset": "token123",
691 "conditionId": "cond456",
692 "size": 0.0,
693 "avgPrice": 0.0,
694 "initialValue": 0.0,
695 "currentValue": 0.0,
696 "cashPnl": 0.0,
697 "percentPnl": 0.0,
698 "totalBought": 0.0,
699 "realizedPnl": 0.0,
700 "percentRealizedPnl": 0.0,
701 "curPrice": 0.0,
702 "redeemable": false,
703 "mergeable": false,
704 "title": "Test",
705 "slug": "test",
706 "icon": null,
707 "eventSlug": null,
708 "outcome": "No",
709 "outcomeIndex": 1,
710 "oppositeOutcome": "Yes",
711 "oppositeAsset": "token000",
712 "endDate": null,
713 "negativeRisk": true
714 }"#;
715
716 let pos: Position = serde_json::from_str(json).unwrap();
717 assert!(pos.icon.is_none());
718 assert!(pos.event_slug.is_none());
719 assert!(pos.end_date.is_none());
720 assert!(pos.negative_risk);
721 }
722
723 #[test]
724 fn deserialize_closed_position_from_json() {
725 let json = r#"{
726 "proxyWallet": "0xdef456",
727 "asset": "token_closed",
728 "conditionId": "cond_closed",
729 "avgPrice": 0.45,
730 "totalBought": 200.0,
731 "realizedPnl": -10.0,
732 "curPrice": 0.35,
733 "timestamp": 1700000000,
734 "title": "Closed market?",
735 "slug": "closed-market",
736 "icon": null,
737 "eventSlug": "closed-event",
738 "outcome": "No",
739 "outcomeIndex": 1,
740 "oppositeOutcome": "Yes",
741 "oppositeAsset": "token_opp",
742 "endDate": "2024-06-30"
743 }"#;
744
745 let closed: ClosedPosition = serde_json::from_str(json).unwrap();
746 assert_eq!(closed.proxy_wallet, "0xdef456");
747 assert!((closed.avg_price - 0.45).abs() < f64::EPSILON);
748 assert!((closed.realized_pnl - (-10.0)).abs() < f64::EPSILON);
749 assert_eq!(closed.timestamp, 1700000000);
750 assert_eq!(closed.outcome, "No");
751 assert_eq!(closed.outcome_index, 1);
752 assert!(closed.icon.is_none());
753 assert_eq!(closed.event_slug, Some("closed-event".to_string()));
754 }
755
756 #[test]
757 fn deserialize_trade_from_json() {
758 let json = r#"{
759 "proxyWallet": "0x1234",
760 "side": "BUY",
761 "asset": "token_buy",
762 "conditionId": "cond_trade",
763 "size": 50.0,
764 "price": 0.72,
765 "timestamp": 1700001000,
766 "title": "Trade market?",
767 "slug": "trade-market",
768 "icon": "https://example.com/trade.png",
769 "eventSlug": null,
770 "outcome": "Yes",
771 "outcomeIndex": 0,
772 "name": "TraderOne",
773 "pseudonym": "t1",
774 "bio": "A trader",
775 "profileImage": null,
776 "profileImageOptimized": null,
777 "transactionHash": "0xhash123"
778 }"#;
779
780 let trade: Trade = serde_json::from_str(json).unwrap();
781 assert_eq!(trade.proxy_wallet, "0x1234");
782 assert_eq!(trade.side, TradeSide::Buy);
783 assert!((trade.size - 50.0).abs() < f64::EPSILON);
784 assert!((trade.price - 0.72).abs() < f64::EPSILON);
785 assert_eq!(trade.timestamp, 1700001000);
786 assert_eq!(trade.name, Some("TraderOne".to_string()));
787 assert_eq!(trade.transaction_hash, Some("0xhash123".to_string()));
788 assert!(trade.profile_image.is_none());
789 }
790
791 #[test]
792 fn deserialize_trade_sell_side() {
793 let json = r#"{
794 "proxyWallet": "0x5678",
795 "side": "SELL",
796 "asset": "token_sell",
797 "conditionId": "cond_sell",
798 "size": 25.0,
799 "price": 0.30,
800 "timestamp": 1700002000,
801 "title": "Sell test",
802 "slug": "sell-test",
803 "icon": null,
804 "eventSlug": null,
805 "outcome": "No",
806 "outcomeIndex": 1,
807 "name": null,
808 "pseudonym": null,
809 "bio": null,
810 "profileImage": null,
811 "profileImageOptimized": null,
812 "transactionHash": null
813 }"#;
814
815 let trade: Trade = serde_json::from_str(json).unwrap();
816 assert_eq!(trade.side, TradeSide::Sell);
817 assert!(trade.name.is_none());
818 assert!(trade.transaction_hash.is_none());
819 }
820
821 #[test]
822 fn deserialize_activity_from_json() {
823 let json = r#"{
824 "proxyWallet": "0xact123",
825 "timestamp": 1700003000,
826 "conditionId": "cond_act",
827 "type": "TRADE",
828 "size": 10.0,
829 "usdcSize": 7.50,
830 "transactionHash": "0xacthash",
831 "price": 0.75,
832 "asset": "token_act",
833 "side": "BUY",
834 "outcomeIndex": 0,
835 "title": "Activity market",
836 "slug": "activity-market",
837 "icon": null,
838 "outcome": "Yes",
839 "name": null,
840 "pseudonym": null,
841 "bio": null,
842 "profileImage": null,
843 "profileImageOptimized": null
844 }"#;
845
846 let activity: Activity = serde_json::from_str(json).unwrap();
847 assert_eq!(activity.proxy_wallet, "0xact123");
848 assert_eq!(activity.activity_type, ActivityType::Trade);
849 assert!((activity.size - 10.0).abs() < f64::EPSILON);
850 assert!((activity.usdc_size - 7.50).abs() < f64::EPSILON);
851 assert_eq!(activity.side, Some("BUY".to_string()));
852 assert_eq!(activity.outcome_index, Some(0));
853 }
854
855 #[test]
856 fn deserialize_activity_merge_type() {
857 let json = r#"{
858 "proxyWallet": "0xmerge",
859 "timestamp": 1700004000,
860 "conditionId": "cond_merge",
861 "type": "MERGE",
862 "size": 5.0,
863 "usdcSize": 3.0,
864 "transactionHash": null,
865 "price": null,
866 "asset": null,
867 "side": "",
868 "outcomeIndex": null,
869 "title": null,
870 "slug": null,
871 "icon": null,
872 "outcome": null,
873 "name": null,
874 "pseudonym": null,
875 "bio": null,
876 "profileImage": null,
877 "profileImageOptimized": null
878 }"#;
879
880 let activity: Activity = serde_json::from_str(json).unwrap();
881 assert_eq!(activity.activity_type, ActivityType::Merge);
882 assert_eq!(activity.side, Some("".to_string()));
884 assert!(activity.price.is_none());
885 assert!(activity.asset.is_none());
886 assert!(activity.title.is_none());
887 }
888
889 #[test]
890 fn deserialize_user_value() {
891 let json = r#"{"user": "0xuser", "value": 1234.56}"#;
892 let uv: UserValue = serde_json::from_str(json).unwrap();
893 assert_eq!(uv.user, "0xuser");
894 assert!((uv.value - 1234.56).abs() < f64::EPSILON);
895 }
896
897 #[test]
898 fn deserialize_open_interest() {
899 let json = r#"{"market": "0xcond", "value": 50000.0}"#;
900 let oi: OpenInterest = serde_json::from_str(json).unwrap();
901 assert_eq!(oi.market, "0xcond");
902 assert!((oi.value - 50000.0).abs() < f64::EPSILON);
903 }
904}