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 {
38 self.request = self.request.query("limit", limit);
39 self
40 }
41
42 pub fn min_balance(mut self, min_balance: u32) -> Self {
44 self.request = self.request.query("minBalance", min_balance);
45 self
46 }
47
48 pub async fn send(self) -> Result<Vec<MarketHolders>, DataApiError> {
50 self.request.send().await
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(rename_all(deserialize = "camelCase"))]
57pub struct MarketHolders {
58 pub token: String,
60 pub holders: Vec<Holder>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(rename_all(deserialize = "camelCase"))]
67pub struct Holder {
68 pub proxy_wallet: String,
70 pub bio: Option<String>,
72 pub asset: Option<String>,
74 pub pseudonym: Option<String>,
76 pub amount: f64,
78 pub display_username_public: Option<bool>,
80 pub outcome_index: u32,
82 pub name: Option<String>,
84 pub profile_image: Option<String>,
86 pub profile_image_optimized: Option<String>,
88 #[serde(default)]
90 pub verified: Option<bool>,
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn deserialize_market_holders() {
99 let json = r#"{
100 "token": "token_abc",
101 "holders": [
102 {
103 "proxyWallet": "0xholder1",
104 "bio": "Top trader",
105 "asset": "token_abc",
106 "pseudonym": "whale1",
107 "amount": 50000.0,
108 "displayUsernamePublic": true,
109 "outcomeIndex": 0,
110 "name": "Holder One",
111 "profileImage": "https://example.com/img.png",
112 "profileImageOptimized": "https://example.com/img_opt.png",
113 "verified": true
114 },
115 {
116 "proxyWallet": "0xholder2",
117 "bio": null,
118 "asset": null,
119 "pseudonym": null,
120 "amount": 1000.0,
121 "displayUsernamePublic": null,
122 "outcomeIndex": 1,
123 "name": null,
124 "profileImage": null,
125 "profileImageOptimized": null,
126 "verified": false
127 }
128 ]
129 }"#;
130
131 let mh: MarketHolders = serde_json::from_str(json).unwrap();
132 assert_eq!(mh.token, "token_abc");
133 assert_eq!(mh.holders.len(), 2);
134
135 let h1 = &mh.holders[0];
136 assert_eq!(h1.proxy_wallet, "0xholder1");
137 assert_eq!(h1.bio, Some("Top trader".to_string()));
138 assert!((h1.amount - 50000.0).abs() < f64::EPSILON);
139 assert_eq!(h1.outcome_index, 0);
140 assert_eq!(h1.display_username_public, Some(true));
141 assert_eq!(h1.name, Some("Holder One".to_string()));
142 assert_eq!(h1.verified, Some(true));
143
144 let h2 = &mh.holders[1];
145 assert_eq!(h2.proxy_wallet, "0xholder2");
146 assert!(h2.bio.is_none());
147 assert!(h2.asset.is_none());
148 assert!(h2.pseudonym.is_none());
149 assert!((h2.amount - 1000.0).abs() < f64::EPSILON);
150 assert_eq!(h2.outcome_index, 1);
151 assert!(h2.name.is_none());
152 assert_eq!(h2.verified, Some(false));
153 }
154
155 #[test]
156 fn deserialize_empty_holders_list() {
157 let json = r#"{"token": "empty_token", "holders": []}"#;
158 let mh: MarketHolders = serde_json::from_str(json).unwrap();
159 assert_eq!(mh.token, "empty_token");
160 assert!(mh.holders.is_empty());
161 }
162
163 #[test]
164 fn holder_without_verified_field() {
165 let json = r#"{
166 "proxyWallet": "0xholder",
167 "amount": 100.0,
168 "outcomeIndex": 0
169 }"#;
170 let h: Holder = serde_json::from_str(json).unwrap();
171 assert_eq!(h.proxy_wallet, "0xholder");
172 assert!(h.verified.is_none());
173 }
174}