polyoxide_data/api/
holders.rs1use polyoxide_core::{HttpClient, QueryBuilder, Request};
2use serde::{Deserialize, Serialize};
3
4use crate::error::DataApiError;
5
6#[derive(Clone)]
8pub struct Holders {
9 pub(crate) http_client: HttpClient,
10}
11
12impl Holders {
13 pub fn list(&self, markets: impl IntoIterator<Item = impl ToString>) -> ListHolders {
15 let market_ids: Vec<String> = markets.into_iter().map(|s| s.to_string()).collect();
16 let mut request = Request::new(self.http_client.clone(), "/holders");
17 if !market_ids.is_empty() {
18 request = request.query("market", market_ids.join(","));
19 }
20
21 ListHolders { request }
22 }
23}
24
25pub struct ListHolders {
27 request: Request<Vec<MarketHolders>, DataApiError>,
28}
29
30impl ListHolders {
31 pub fn limit(mut self, limit: u32) -> Self {
33 self.request = self.request.query("limit", limit);
34 self
35 }
36
37 pub fn min_balance(mut self, min_balance: u32) -> Self {
39 self.request = self.request.query("minBalance", min_balance);
40 self
41 }
42
43 pub async fn send(self) -> Result<Vec<MarketHolders>, DataApiError> {
45 self.request.send().await
46 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51#[serde(rename_all(deserialize = "camelCase"))]
52pub struct MarketHolders {
53 pub token: String,
55 pub holders: Vec<Holder>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all(deserialize = "camelCase"))]
62pub struct Holder {
63 pub proxy_wallet: String,
65 pub bio: Option<String>,
67 pub asset: Option<String>,
69 pub pseudonym: Option<String>,
71 pub amount: f64,
73 pub display_username_public: Option<bool>,
75 pub outcome_index: u32,
77 pub name: Option<String>,
79 pub profile_image: Option<String>,
81 pub profile_image_optimized: Option<String>,
83 #[serde(default)]
85 pub verified: Option<bool>,
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn deserialize_market_holders() {
94 let json = r#"{
95 "token": "token_abc",
96 "holders": [
97 {
98 "proxyWallet": "0xholder1",
99 "bio": "Top trader",
100 "asset": "token_abc",
101 "pseudonym": "whale1",
102 "amount": 50000.0,
103 "displayUsernamePublic": true,
104 "outcomeIndex": 0,
105 "name": "Holder One",
106 "profileImage": "https://example.com/img.png",
107 "profileImageOptimized": "https://example.com/img_opt.png",
108 "verified": true
109 },
110 {
111 "proxyWallet": "0xholder2",
112 "bio": null,
113 "asset": null,
114 "pseudonym": null,
115 "amount": 1000.0,
116 "displayUsernamePublic": null,
117 "outcomeIndex": 1,
118 "name": null,
119 "profileImage": null,
120 "profileImageOptimized": null,
121 "verified": false
122 }
123 ]
124 }"#;
125
126 let mh: MarketHolders = serde_json::from_str(json).unwrap();
127 assert_eq!(mh.token, "token_abc");
128 assert_eq!(mh.holders.len(), 2);
129
130 let h1 = &mh.holders[0];
131 assert_eq!(h1.proxy_wallet, "0xholder1");
132 assert_eq!(h1.bio, Some("Top trader".to_string()));
133 assert!((h1.amount - 50000.0).abs() < f64::EPSILON);
134 assert_eq!(h1.outcome_index, 0);
135 assert_eq!(h1.display_username_public, Some(true));
136 assert_eq!(h1.name, Some("Holder One".to_string()));
137 assert_eq!(h1.verified, Some(true));
138
139 let h2 = &mh.holders[1];
140 assert_eq!(h2.proxy_wallet, "0xholder2");
141 assert!(h2.bio.is_none());
142 assert!(h2.asset.is_none());
143 assert!(h2.pseudonym.is_none());
144 assert!((h2.amount - 1000.0).abs() < f64::EPSILON);
145 assert_eq!(h2.outcome_index, 1);
146 assert!(h2.name.is_none());
147 assert_eq!(h2.verified, Some(false));
148 }
149
150 #[test]
151 fn deserialize_empty_holders_list() {
152 let json = r#"{"token": "empty_token", "holders": []}"#;
153 let mh: MarketHolders = serde_json::from_str(json).unwrap();
154 assert_eq!(mh.token, "empty_token");
155 assert!(mh.holders.is_empty());
156 }
157
158 #[test]
159 fn holder_without_verified_field() {
160 let json = r#"{
161 "proxyWallet": "0xholder",
162 "amount": 100.0,
163 "outcomeIndex": 0
164 }"#;
165 let h: Holder = serde_json::from_str(json).unwrap();
166 assert_eq!(h.proxy_wallet, "0xholder");
167 assert!(h.verified.is_none());
168 }
169}