1use crate::prelude::*;
2use crate::signing::Eip712Signer;
3
4#[derive(Clone)]
17pub struct Trader {
18 pub client: Client,
19}
20
21impl Trader {
22 pub async fn create_order(
31 &self,
32 order_request: &str,
33 ) -> Result<CreateOrderResponse, LimitlessError> {
34 self.client
35 .post_signed("orders", Some(order_request.to_string()))
36 .await
37 }
38
39 pub async fn order_status_batch(
44 &self,
45 request_body: &str,
46 ) -> Result<OrderStatusBatchResponse, LimitlessError> {
47 self.client
48 .post_signed("orders/status/batch", Some(request_body.to_string()))
49 .await
50 }
51
52 pub async fn cancel_combined(
54 &self,
55 request_body: &str,
56 ) -> Result<CancelOrderResponse, LimitlessError> {
57 self.client
58 .post_signed("orders/cancel", Some(request_body.to_string()))
59 .await
60 }
61
62 pub async fn cancel_batch(
64 &self,
65 request_body: &str,
66 ) -> Result<CancelBatchResponse, LimitlessError> {
67 self.client
68 .post_signed("orders/cancel-batch", Some(request_body.to_string()))
69 .await
70 }
71
72 pub async fn cancel_order_by_id(
74 &self,
75 order_id: &str,
76 ) -> Result<CancelOrderResponse, LimitlessError> {
77 let path = format!("orders/{}", order_id);
78 self.client.delete_signed(&path).await
79 }
80
81 pub async fn cancel_all_in_market(
83 &self,
84 slug: &str,
85 ) -> Result<CancelAllResponse, LimitlessError> {
86 let path = format!("orders/all/{}", slug);
87 self.client.delete_signed(&path).await
88 }
89
90 pub async fn get_orderbook(&self, slug: &str) -> Result<OrderbookResponse, LimitlessError> {
92 let path = format!("markets/{}/orderbook", slug);
93 self.client.get(&path, None).await
94 }
95
96 pub async fn get_historical_prices(
98 &self,
99 slug: &str,
100 interval: Option<&str>,
101 ) -> Result<Vec<HistoricalPriceData>, LimitlessError> {
102 let mut params = BTreeMap::new();
103 if let Some(ref v) = interval {
104 params.insert("interval".into(), v.to_string());
105 }
106 let request = build_request(¶ms);
107 let path = format!("markets/{}/historical-price", slug);
108 self.client.get(&path, Some(request)).await
109 }
110
111 pub async fn get_locked_balance(
113 &self,
114 slug: &str,
115 ) -> Result<LockedBalanceResponse, LimitlessError> {
116 let path = format!("markets/{}/locked-balance", slug);
117 self.client.get(&path, None).await
118 }
119
120 pub async fn get_user_orders(
122 &self,
123 slug: &str,
124 statuses: Option<&[&str]>,
125 limit: Option<u64>,
126 ) -> Result<UserOrdersResponse, LimitlessError> {
127 let mut params = BTreeMap::new();
128 if let Some(s) = statuses {
129 params.insert("statuses".into(), s.join(","));
130 }
131 if let Some(v) = limit {
132 params.insert("limit".into(), v.to_string());
133 }
134 let request = build_request(¶ms);
135 let path = format!("markets/{}/user-orders", slug);
136 self.client.get(&path, Some(request)).await
137 }
138
139 pub async fn get_market_events(
141 &self,
142 slug: &str,
143 page: Option<u64>,
144 limit: Option<u64>,
145 ) -> Result<MarketEventsResponse, LimitlessError> {
146 let mut params = BTreeMap::new();
147 if let Some(v) = page {
148 params.insert("page".into(), v.to_string());
149 }
150 if let Some(v) = limit {
151 params.insert("limit".into(), v.to_string());
152 }
153 let request = build_request(¶ms);
154 let path = format!("markets/{}/events", slug);
155 self.client.get(&path, Some(request)).await
156 }
157
158 pub async fn buy_gtc(
172 &self,
173 private_key: &str,
174 market_slug: &str,
175 token_id: &str,
176 price: f64,
177 size: f64,
178 owner_id: u64,
179 ) -> Result<CreateOrderResponse, LimitlessError> {
180 self.place_gtc_order(
181 private_key,
182 market_slug,
183 token_id,
184 OrderSide::Buy,
185 price,
186 size,
187 owner_id,
188 )
189 .await
190 }
191
192 pub async fn sell_gtc(
194 &self,
195 private_key: &str,
196 market_slug: &str,
197 token_id: &str,
198 price: f64,
199 size: f64,
200 owner_id: u64,
201 ) -> Result<CreateOrderResponse, LimitlessError> {
202 self.place_gtc_order(
203 private_key,
204 market_slug,
205 token_id,
206 OrderSide::Sell,
207 price,
208 size,
209 owner_id,
210 )
211 .await
212 }
213
214 pub async fn buy_fok(
216 &self,
217 private_key: &str,
218 market_slug: &str,
219 token_id: &str,
220 usdc_amount: f64,
221 owner_id: u64,
222 ) -> Result<CreateOrderResponse, LimitlessError> {
223 self.place_fok_order(
224 private_key,
225 market_slug,
226 token_id,
227 OrderSide::Buy,
228 usdc_amount,
229 owner_id,
230 )
231 .await
232 }
233
234 pub async fn sell_fok(
236 &self,
237 private_key: &str,
238 market_slug: &str,
239 token_id: &str,
240 share_amount: f64,
241 owner_id: u64,
242 ) -> Result<CreateOrderResponse, LimitlessError> {
243 self.place_fok_order(
244 private_key,
245 market_slug,
246 token_id,
247 OrderSide::Sell,
248 share_amount,
249 owner_id,
250 )
251 .await
252 }
253
254 pub async fn cancel_all(&self, slug: &str) -> Result<CancelAllResponse, LimitlessError> {
256 self.cancel_all_in_market(slug).await
257 }
258
259 async fn get_verifying_contract(&self, slug: &str) -> Result<String, LimitlessError> {
263 let market: MarketDetail = self.client.get(&format!("markets/{}", slug), None).await?;
264 let venue = market.venue.ok_or_else(|| {
265 LimitlessError::ValidationError(format!(
266 "Market '{}' has no venue info — is it a CLOB market?",
267 slug
268 ))
269 })?;
270 Ok(venue.exchange)
271 }
272
273 async fn place_gtc_order(
274 &self,
275 private_key: &str,
276 market_slug: &str,
277 token_id: &str,
278 side: OrderSide,
279 price: f64,
280 size: f64,
281 owner_id: u64,
282 ) -> Result<CreateOrderResponse, LimitlessError> {
283 let verifying_contract = self.get_verifying_contract(market_slug).await?;
284 let signer = Eip712Signer::new(private_key, &verifying_contract)
285 .map_err(|e| LimitlessError::ValidationError(e))?;
286
287 let order_data = signer
288 .build_gtc_order(
289 &signer.wallet_address(),
290 token_id,
291 side,
292 price,
293 size,
294 0, )
296 .map_err(|e| LimitlessError::ValidationError(e))?;
297
298 let request = CreateOrderRequest {
299 order: order_data,
300 owner_id,
301 order_type: OrderType::Gtc,
302 market_slug: market_slug.to_string(),
303 client_order_id: None,
304 on_behalf_of: None,
305 };
306
307 let body = serde_json::to_string(&request).map_err(|e| LimitlessError::Json(e))?;
308
309 self.create_order(&body).await
310 }
311
312 async fn place_fok_order(
313 &self,
314 private_key: &str,
315 market_slug: &str,
316 token_id: &str,
317 side: OrderSide,
318 amount: f64,
319 owner_id: u64,
320 ) -> Result<CreateOrderResponse, LimitlessError> {
321 let verifying_contract = self.get_verifying_contract(market_slug).await?;
322 let signer = Eip712Signer::new(private_key, &verifying_contract)
323 .map_err(|e| LimitlessError::ValidationError(e))?;
324
325 let order_data = signer
326 .build_fok_order(&signer.wallet_address(), token_id, side, amount, 0)
327 .map_err(|e| LimitlessError::ValidationError(e))?;
328
329 let request = CreateOrderRequest {
330 order: order_data,
331 owner_id,
332 order_type: OrderType::Fok,
333 market_slug: market_slug.to_string(),
334 client_order_id: None,
335 on_behalf_of: None,
336 };
337
338 let body = serde_json::to_string(&request).map_err(|e| LimitlessError::Json(e))?;
339
340 self.create_order(&body).await
341 }
342}
343
344impl Limitless for Trader {
345 fn new(api_key: Option<String>, secret: Option<String>) -> Self {
346 Self::new_with_config(&Config::default(), api_key, secret)
347 }
348
349 fn new_with_config(config: &Config, api_key: Option<String>, secret: Option<String>) -> Self {
350 Self {
351 client: Client::new(api_key, secret, config.rest_api_endpoint.to_string()),
352 }
353 }
354}