polyte_data/api/
users.rs

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