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}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn deserialize_market_holders() {
91 let json = r#"{
92 "token": "token_abc",
93 "holders": [
94 {
95 "proxyWallet": "0xholder1",
96 "bio": "Top trader",
97 "asset": "token_abc",
98 "pseudonym": "whale1",
99 "amount": 50000.0,
100 "displayUsernamePublic": true,
101 "outcomeIndex": 0,
102 "name": "Holder One",
103 "profileImage": "https://example.com/img.png",
104 "profileImageOptimized": "https://example.com/img_opt.png"
105 },
106 {
107 "proxyWallet": "0xholder2",
108 "bio": null,
109 "asset": null,
110 "pseudonym": null,
111 "amount": 1000.0,
112 "displayUsernamePublic": null,
113 "outcomeIndex": 1,
114 "name": null,
115 "profileImage": null,
116 "profileImageOptimized": null
117 }
118 ]
119 }"#;
120
121 let mh: MarketHolders = serde_json::from_str(json).unwrap();
122 assert_eq!(mh.token, "token_abc");
123 assert_eq!(mh.holders.len(), 2);
124
125 let h1 = &mh.holders[0];
126 assert_eq!(h1.proxy_wallet, "0xholder1");
127 assert_eq!(h1.bio, Some("Top trader".to_string()));
128 assert!((h1.amount - 50000.0).abs() < f64::EPSILON);
129 assert_eq!(h1.outcome_index, 0);
130 assert_eq!(h1.display_username_public, Some(true));
131 assert_eq!(h1.name, Some("Holder One".to_string()));
132
133 let h2 = &mh.holders[1];
134 assert_eq!(h2.proxy_wallet, "0xholder2");
135 assert!(h2.bio.is_none());
136 assert!(h2.asset.is_none());
137 assert!(h2.pseudonym.is_none());
138 assert!((h2.amount - 1000.0).abs() < f64::EPSILON);
139 assert_eq!(h2.outcome_index, 1);
140 assert!(h2.name.is_none());
141 }
142
143 #[test]
144 fn deserialize_empty_holders_list() {
145 let json = r#"{"token": "empty_token", "holders": []}"#;
146 let mh: MarketHolders = serde_json::from_str(json).unwrap();
147 assert_eq!(mh.token, "empty_token");
148 assert!(mh.holders.is_empty());
149 }
150}