polyte_data/api/
users.rs

1use reqwest::Client;
2use serde::{Deserialize, Serialize};
3use url::Url;
4
5use crate::{
6    error::DataApiError,
7    request::{QueryBuilder, Request},
8    types::{
9        Activity, ActivitySortBy, ActivityType, ClosedPosition, ClosedPositionSortBy, Position,
10        PositionSortBy, SortDirection, Trade, TradeFilterType, TradeSide, UserValue,
11    },
12};
13
14/// User namespace for user-related operations
15#[derive(Clone)]
16pub struct UserApi {
17    pub(crate) client: Client,
18    pub(crate) base_url: Url,
19    pub(crate) user_address: String,
20}
21
22impl UserApi {
23    /// List positions for this user
24    pub fn list_positions(&self) -> ListPositions {
25        let mut request = Request::new(
26            self.client.clone(),
27            self.base_url.clone(),
28            "/positions".to_string(),
29        );
30        request = request.query("user", &self.user_address);
31
32        ListPositions { request }
33    }
34
35    /// Get total value of this user's positions
36    pub fn positions_value(&self) -> GetPositionValue {
37        let mut request = Request::new(
38            self.client.clone(),
39            self.base_url.clone(),
40            "/value".to_string(),
41        );
42        request = request.query("user", &self.user_address);
43
44        GetPositionValue { request }
45    }
46
47    /// List closed positions for this user
48    pub fn closed_positions(&self) -> ListClosedPositions {
49        let mut request = Request::new(
50            self.client.clone(),
51            self.base_url.clone(),
52            "/closed-positions".to_string(),
53        );
54        request = request.query("user", &self.user_address);
55
56        ListClosedPositions { request }
57    }
58
59    /// List trades for this user
60    pub fn trades(&self) -> ListUserTrades {
61        let mut request = Request::new(
62            self.client.clone(),
63            self.base_url.clone(),
64            "/trades".to_string(),
65        );
66        request = request.query("user", &self.user_address);
67
68        ListUserTrades { request }
69    }
70
71    /// List activity for this user
72    pub fn activity(&self) -> ListActivity {
73        let mut request = Request::new(
74            self.client.clone(),
75            self.base_url.clone(),
76            "/activity".to_string(),
77        );
78        request = request.query("user", &self.user_address);
79
80        ListActivity { request }
81    }
82
83    /// Get total markets traded by this user
84    pub async fn traded(&self) -> Result<UserTraded, DataApiError> {
85        let url = self.base_url.join("/traded")?;
86        let response = self
87            .client
88            .get(url)
89            .query(&[("user", &self.user_address)])
90            .send()
91            .await?;
92        let status = response.status();
93
94        if !status.is_success() {
95            return Err(DataApiError::from_response(response).await);
96        }
97
98        let traded: UserTraded = response.json().await?;
99        Ok(traded)
100    }
101}
102
103/// User's total markets traded count
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct UserTraded {
106    /// User address
107    pub user: String,
108    /// Total count of distinct markets traded
109    pub traded: u64,
110}
111
112/// Request builder for listing user positions
113pub struct ListPositions {
114    request: Request<Vec<Position>>,
115}
116
117impl ListPositions {
118    /// Filter by specific market condition IDs (comma-separated)
119    pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
120        let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
121        if !ids.is_empty() {
122            self.request = self.request.query("market", ids.join(","));
123        }
124        self
125    }
126
127    /// Filter by event IDs (comma-separated)
128    pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
129        let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
130        if !ids.is_empty() {
131            self.request = self.request.query("eventId", ids.join(","));
132        }
133        self
134    }
135
136    /// Set minimum position size filter (default: 1)
137    pub fn size_threshold(mut self, threshold: f64) -> Self {
138        self.request = self.request.query("sizeThreshold", threshold);
139        self
140    }
141
142    /// Filter for redeemable positions only
143    pub fn redeemable(mut self, redeemable: bool) -> Self {
144        self.request = self.request.query("redeemable", redeemable);
145        self
146    }
147
148    /// Filter for mergeable positions only
149    pub fn mergeable(mut self, mergeable: bool) -> Self {
150        self.request = self.request.query("mergeable", mergeable);
151        self
152    }
153
154    /// Set maximum number of results (0-500, default: 100)
155    pub fn limit(mut self, limit: u32) -> Self {
156        self.request = self.request.query("limit", limit);
157        self
158    }
159
160    /// Set pagination offset (0-10000, default: 0)
161    pub fn offset(mut self, offset: u32) -> Self {
162        self.request = self.request.query("offset", offset);
163        self
164    }
165
166    /// Set sort field
167    pub fn sort_by(mut self, sort_by: PositionSortBy) -> Self {
168        self.request = self.request.query("sortBy", sort_by);
169        self
170    }
171
172    /// Set sort direction (default: DESC)
173    pub fn sort_direction(mut self, direction: SortDirection) -> Self {
174        self.request = self.request.query("sortDirection", direction);
175        self
176    }
177
178    /// Filter by market title (max 100 chars)
179    pub fn title(mut self, title: impl Into<String>) -> Self {
180        self.request = self.request.query("title", title.into());
181        self
182    }
183
184    /// Execute the request
185    pub async fn send(self) -> Result<Vec<Position>, DataApiError> {
186        self.request.send().await
187    }
188}
189
190/// Request builder for getting total position value
191pub struct GetPositionValue {
192    request: Request<Vec<UserValue>>,
193}
194
195impl GetPositionValue {
196    /// Filter by specific market condition IDs (comma-separated)
197    pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
198        let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
199        if !ids.is_empty() {
200            self.request = self.request.query("market", ids.join(","));
201        }
202        self
203    }
204
205    /// Execute the request
206    pub async fn send(self) -> Result<Vec<UserValue>, DataApiError> {
207        self.request.send().await
208    }
209}
210
211/// Request builder for listing closed positions
212pub struct ListClosedPositions {
213    request: Request<Vec<ClosedPosition>>,
214}
215
216impl ListClosedPositions {
217    /// Filter by specific market condition IDs (comma-separated)
218    pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
219        let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
220        if !ids.is_empty() {
221            self.request = self.request.query("market", ids.join(","));
222        }
223        self
224    }
225
226    /// Filter by event IDs (comma-separated)
227    pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
228        let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
229        if !ids.is_empty() {
230            self.request = self.request.query("eventId", ids.join(","));
231        }
232        self
233    }
234
235    /// Filter by market title (max 100 chars)
236    pub fn title(mut self, title: impl Into<String>) -> Self {
237        self.request = self.request.query("title", title.into());
238        self
239    }
240
241    /// Set maximum number of results (0-50, default: 10)
242    pub fn limit(mut self, limit: u32) -> Self {
243        self.request = self.request.query("limit", limit);
244        self
245    }
246
247    /// Set pagination offset (0-100000, default: 0)
248    pub fn offset(mut self, offset: u32) -> Self {
249        self.request = self.request.query("offset", offset);
250        self
251    }
252
253    /// Set sort field (default: REALIZEDPNL)
254    pub fn sort_by(mut self, sort_by: ClosedPositionSortBy) -> Self {
255        self.request = self.request.query("sortBy", sort_by);
256        self
257    }
258
259    /// Set sort direction (default: DESC)
260    pub fn sort_direction(mut self, direction: SortDirection) -> Self {
261        self.request = self.request.query("sortDirection", direction);
262        self
263    }
264
265    /// Execute the request
266    pub async fn send(self) -> Result<Vec<ClosedPosition>, DataApiError> {
267        self.request.send().await
268    }
269}
270
271/// Request builder for listing user trades
272pub struct ListUserTrades {
273    request: Request<Vec<Trade>>,
274}
275
276impl ListUserTrades {
277    /// Filter by market condition IDs (comma-separated)
278    /// Note: Mutually exclusive with `event_id`
279    pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
280        let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
281        if !ids.is_empty() {
282            self.request = self.request.query("market", ids.join(","));
283        }
284        self
285    }
286
287    /// Filter by event IDs (comma-separated)
288    /// Note: Mutually exclusive with `market`
289    pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
290        let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
291        if !ids.is_empty() {
292            self.request = self.request.query("eventId", ids.join(","));
293        }
294        self
295    }
296
297    /// Filter by trade side (BUY or SELL)
298    pub fn side(mut self, side: TradeSide) -> Self {
299        self.request = self.request.query("side", side);
300        self
301    }
302
303    /// Filter for taker trades only (default: true)
304    pub fn taker_only(mut self, taker_only: bool) -> Self {
305        self.request = self.request.query("takerOnly", taker_only);
306        self
307    }
308
309    /// Set filter type (must be paired with `filter_amount`)
310    pub fn filter_type(mut self, filter_type: TradeFilterType) -> Self {
311        self.request = self.request.query("filterType", filter_type);
312        self
313    }
314
315    /// Set filter amount (must be paired with `filter_type`)
316    pub fn filter_amount(mut self, amount: f64) -> Self {
317        self.request = self.request.query("filterAmount", amount);
318        self
319    }
320
321    /// Set maximum number of results (0-10000, default: 100)
322    pub fn limit(mut self, limit: u32) -> Self {
323        self.request = self.request.query("limit", limit);
324        self
325    }
326
327    /// Set pagination offset (0-10000, default: 0)
328    pub fn offset(mut self, offset: u32) -> Self {
329        self.request = self.request.query("offset", offset);
330        self
331    }
332
333    /// Execute the request
334    pub async fn send(self) -> Result<Vec<Trade>, DataApiError> {
335        self.request.send().await
336    }
337}
338
339/// Request builder for listing user activity
340pub struct ListActivity {
341    request: Request<Vec<Activity>>,
342}
343
344impl ListActivity {
345    /// Filter by market condition IDs (comma-separated)
346    pub fn market(mut self, condition_ids: impl IntoIterator<Item = impl ToString>) -> Self {
347        let ids: Vec<String> = condition_ids.into_iter().map(|s| s.to_string()).collect();
348        if !ids.is_empty() {
349            self.request = self.request.query("market", ids.join(","));
350        }
351        self
352    }
353
354    /// Filter by event IDs (comma-separated)
355    pub fn event_id(mut self, event_ids: impl IntoIterator<Item = impl ToString>) -> Self {
356        let ids: Vec<String> = event_ids.into_iter().map(|s| s.to_string()).collect();
357        if !ids.is_empty() {
358            self.request = self.request.query("eventId", ids.join(","));
359        }
360        self
361    }
362
363    /// Filter by activity types (comma-separated)
364    pub fn activity_type(mut self, types: impl IntoIterator<Item = ActivityType>) -> Self {
365        let type_strs: Vec<String> = types.into_iter().map(|t| t.to_string()).collect();
366        if !type_strs.is_empty() {
367            self.request = self.request.query("type", type_strs.join(","));
368        }
369        self
370    }
371
372    /// Filter by trade side (BUY or SELL)
373    pub fn side(mut self, side: TradeSide) -> Self {
374        self.request = self.request.query("side", side);
375        self
376    }
377
378    /// Set start timestamp filter
379    pub fn start(mut self, timestamp: i64) -> Self {
380        self.request = self.request.query("start", timestamp);
381        self
382    }
383
384    /// Set end timestamp filter
385    pub fn end(mut self, timestamp: i64) -> Self {
386        self.request = self.request.query("end", timestamp);
387        self
388    }
389
390    /// Set maximum number of results (0-10000, default: 100)
391    pub fn limit(mut self, limit: u32) -> Self {
392        self.request = self.request.query("limit", limit);
393        self
394    }
395
396    /// Set pagination offset (0-10000, default: 0)
397    pub fn offset(mut self, offset: u32) -> Self {
398        self.request = self.request.query("offset", offset);
399        self
400    }
401
402    /// Set sort field (default: TIMESTAMP)
403    pub fn sort_by(mut self, sort_by: ActivitySortBy) -> Self {
404        self.request = self.request.query("sortBy", sort_by);
405        self
406    }
407
408    /// Set sort direction (default: DESC)
409    pub fn sort_direction(mut self, direction: SortDirection) -> Self {
410        self.request = self.request.query("sortDirection", direction);
411        self
412    }
413
414    /// Execute the request
415    pub async fn send(self) -> Result<Vec<Activity>, DataApiError> {
416        self.request.send().await
417    }
418}