1use crate::auth::{create_l1_headers, create_l2_headers};
7use crate::errors::{PolyfillError, Result};
8use crate::types::{OrderOptions, PostOrder, SignedOrderRequest};
9use reqwest::Client;
10use serde_json::Value;
11use std::str::FromStr;
12use rust_decimal::Decimal;
13use rust_decimal::prelude::FromPrimitive;
14use alloy_primitives::U256;
15use alloy_signer_local::PrivateKeySigner;
16use reqwest::{Method, RequestBuilder};
17use reqwest::header::HeaderName;
18
19pub use crate::types::{
21 ApiCredentials as ApiCreds, Side, OrderType,
22};
23
24#[derive(Debug)]
26pub struct OrderArgs {
27 pub token_id: String,
28 pub price: Decimal,
29 pub size: Decimal,
30 pub side: Side,
31}
32
33impl OrderArgs {
34 pub fn new(token_id: &str, price: Decimal, size: Decimal, side: Side) -> Self {
35 Self {
36 token_id: token_id.to_string(),
37 price,
38 size,
39 side,
40 }
41 }
42}
43
44impl Default for OrderArgs {
45 fn default() -> Self {
46 Self {
47 token_id: "".to_string(),
48 price: Decimal::ZERO,
49 size: Decimal::ZERO,
50 side: Side::BUY,
51 }
52 }
53}
54
55pub struct ClobClient {
57 http_client: Client,
58 base_url: String,
59 chain_id: u64,
60 signer: Option<PrivateKeySigner>,
61 api_creds: Option<ApiCreds>,
62 order_builder: Option<crate::orders::OrderBuilder>,
63}
64
65impl ClobClient {
66 pub fn new(host: &str) -> Self {
68 Self {
69 http_client: Client::new(),
70 base_url: host.to_string(),
71 chain_id: 137, signer: None,
73 api_creds: None,
74 order_builder: None,
75 }
76 }
77
78 pub fn with_l1_headers(host: &str, private_key: &str, chain_id: u64) -> Self {
80 let signer = private_key.parse::<PrivateKeySigner>()
81 .expect("Invalid private key");
82
83 let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);
84
85 Self {
86 http_client: Client::new(),
87 base_url: host.to_string(),
88 chain_id,
89 signer: Some(signer),
90 api_creds: None,
91 order_builder: Some(order_builder),
92 }
93 }
94
95 pub fn with_l2_headers(host: &str, private_key: &str, chain_id: u64, api_creds: ApiCreds) -> Self {
97 let signer = private_key.parse::<PrivateKeySigner>()
98 .expect("Invalid private key");
99
100 let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);
101
102 Self {
103 http_client: Client::new(),
104 base_url: host.to_string(),
105 chain_id,
106 signer: Some(signer),
107 api_creds: Some(api_creds),
108 order_builder: Some(order_builder),
109 }
110 }
111
112 pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
114 self.api_creds = Some(api_creds);
115 }
116
117 pub fn get_address(&self) -> Option<String> {
119 use alloy_primitives::hex;
120 self.signer.as_ref().map(|s| hex::encode_prefixed(s.address().as_slice()))
121 }
122
123 pub fn get_collateral_address(&self) -> Option<String> {
125 let config = crate::orders::get_contract_config(self.chain_id, false)?;
126 Some(config.collateral)
127 }
128
129 pub fn get_conditional_address(&self) -> Option<String> {
131 let config = crate::orders::get_contract_config(self.chain_id, false)?;
132 Some(config.conditional_tokens)
133 }
134
135 pub fn get_exchange_address(&self) -> Option<String> {
137 let config = crate::orders::get_contract_config(self.chain_id, false)?;
138 Some(config.exchange)
139 }
140
141 pub async fn get_ok(&self) -> bool {
143 match self.http_client.get(&format!("{}/ok", self.base_url)).send().await {
144 Ok(response) => response.status().is_success(),
145 Err(_) => false,
146 }
147 }
148
149 pub async fn get_server_time(&self) -> Result<u64> {
151 let response = self.http_client
152 .get(&format!("{}/time", self.base_url))
153 .send()
154 .await?;
155
156 if !response.status().is_success() {
157 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get server time"));
158 }
159
160 let time_text = response.text().await?;
161 let timestamp = time_text.trim()
162 .parse::<u64>()
163 .map_err(|e| PolyfillError::parse(format!("Invalid timestamp format: {}", e), None))?;
164
165 Ok(timestamp)
166 }
167
168
169 pub async fn get_order_book(&self, token_id: &str) -> Result<OrderBookSummary> {
171 let response = self.http_client
172 .get(&format!("{}/book", self.base_url))
173 .query(&[("token_id", token_id)])
174 .send()
175 .await?;
176
177 if !response.status().is_success() {
178 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get order book"));
179 }
180
181 let order_book: OrderBookSummary = response.json().await?;
182 Ok(order_book)
183 }
184
185 pub async fn get_midpoint(&self, token_id: &str) -> Result<MidpointResponse> {
187 let response = self.http_client
188 .get(&format!("{}/midpoint", self.base_url))
189 .query(&[("token_id", token_id)])
190 .send()
191 .await?;
192
193 if !response.status().is_success() {
194 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get midpoint"));
195 }
196
197 let midpoint: MidpointResponse = response.json().await?;
198 Ok(midpoint)
199 }
200
201 pub async fn get_spread(&self, token_id: &str) -> Result<SpreadResponse> {
203 let response = self.http_client
204 .get(&format!("{}/spread", self.base_url))
205 .query(&[("token_id", token_id)])
206 .send()
207 .await?;
208
209 if !response.status().is_success() {
210 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get spread"));
211 }
212
213 let spread: SpreadResponse = response.json().await?;
214 Ok(spread)
215 }
216
217 pub async fn get_spreads(&self, token_ids: &[String]) -> Result<std::collections::HashMap<String, Decimal>> {
219 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
220 .iter()
221 .map(|id| {
222 let mut map = std::collections::HashMap::new();
223 map.insert("token_id", id.clone());
224 map
225 })
226 .collect();
227
228 let response = self.http_client
229 .post(&format!("{}/spreads", self.base_url))
230 .json(&request_data)
231 .send()
232 .await?;
233
234 if !response.status().is_success() {
235 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get batch spreads"));
236 }
237
238 response.json::<std::collections::HashMap<String, Decimal>>().await
239 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
240 }
241
242 pub async fn get_price(&self, token_id: &str, side: Side) -> Result<PriceResponse> {
244 let response = self.http_client
245 .get(&format!("{}/price", self.base_url))
246 .query(&[
247 ("token_id", token_id),
248 ("side", side.as_str()),
249 ])
250 .send()
251 .await?;
252
253 if !response.status().is_success() {
254 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get price"));
255 }
256
257 let price: PriceResponse = response.json().await?;
258 Ok(price)
259 }
260
261 pub async fn get_tick_size(&self, token_id: &str) -> Result<Decimal> {
263 let response = self.http_client
264 .get(&format!("{}/tick-size", self.base_url))
265 .query(&[("token_id", token_id)])
266 .send()
267 .await?;
268
269 if !response.status().is_success() {
270 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get tick size"));
271 }
272
273 let tick_size_response: Value = response.json().await?;
274 let tick_size = tick_size_response["minimum_tick_size"]
275 .as_str()
276 .and_then(|s| Decimal::from_str(s).ok())
277 .or_else(|| tick_size_response["minimum_tick_size"].as_f64().map(|f| Decimal::from_f64(f).unwrap_or(Decimal::ZERO)))
278 .ok_or_else(|| PolyfillError::parse("Invalid tick size format", None))?;
279
280 Ok(tick_size)
281 }
282
283 pub async fn create_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
285 let signer = self.signer.as_ref()
286 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
287
288 let headers = create_l1_headers(signer, nonce)?;
289 let req = self.create_request_with_headers(Method::POST, "/auth/api-key", headers.into_iter());
290
291 let response = req.send().await?;
292 if !response.status().is_success() {
293 return Err(PolyfillError::api(response.status().as_u16(), "Failed to create API key"));
294 }
295
296 Ok(response.json::<ApiCreds>().await?)
297 }
298
299 pub async fn derive_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
301 let signer = self.signer.as_ref()
302 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
303
304 let headers = create_l1_headers(signer, nonce)?;
305 let req = self.create_request_with_headers(Method::GET, "/auth/derive-api-key", headers.into_iter());
306
307 let response = req.send().await?;
308 if !response.status().is_success() {
309 return Err(PolyfillError::api(response.status().as_u16(), "Failed to derive API key"));
310 }
311
312 Ok(response.json::<ApiCreds>().await?)
313 }
314
315 pub async fn create_or_derive_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
317 match self.create_api_key(nonce).await {
318 Ok(creds) => Ok(creds),
319 Err(_) => self.derive_api_key(nonce).await,
320 }
321 }
322
323 pub async fn get_api_keys(&self) -> Result<Vec<String>> {
325 let signer = self.signer.as_ref()
326 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
327 let api_creds = self.api_creds.as_ref()
328 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
329
330 let method = Method::GET;
331 let endpoint = "/auth/api-keys";
332 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
333
334 let response = self.http_client
335 .request(method, format!("{}{}", self.base_url, endpoint))
336 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
337 .send()
338 .await
339 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
340
341 let api_keys_response: crate::types::ApiKeysResponse = response.json().await
342 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
343
344 Ok(api_keys_response.api_keys)
345 }
346
347 pub async fn delete_api_key(&self) -> Result<String> {
349 let signer = self.signer.as_ref()
350 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
351 let api_creds = self.api_creds.as_ref()
352 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
353
354 let method = Method::DELETE;
355 let endpoint = "/auth/api-key";
356 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
357
358 let response = self.http_client
359 .request(method, format!("{}{}", self.base_url, endpoint))
360 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
361 .send()
362 .await
363 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
364
365 response.text().await
366 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
367 }
368
369 fn create_request_with_headers(
371 &self,
372 method: Method,
373 endpoint: &str,
374 headers: impl Iterator<Item = (&'static str, String)>,
375 ) -> RequestBuilder {
376 let req = self.http_client.request(method, format!("{}{}", &self.base_url, endpoint));
377 headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))
378 }
379
380 pub async fn get_neg_risk(&self, token_id: &str) -> Result<bool> {
382 let response = self.http_client
383 .get(&format!("{}/neg-risk", self.base_url))
384 .query(&[("token_id", token_id)])
385 .send()
386 .await?;
387
388 if !response.status().is_success() {
389 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get neg risk"));
390 }
391
392 let neg_risk_response: Value = response.json().await?;
393 let neg_risk = neg_risk_response["neg_risk"]
394 .as_bool()
395 .ok_or_else(|| PolyfillError::parse("Invalid neg risk format", None))?;
396
397 Ok(neg_risk)
398 }
399
400 async fn resolve_tick_size(
402 &self,
403 token_id: &str,
404 tick_size: Option<Decimal>,
405 ) -> Result<Decimal> {
406 let min_tick_size = self.get_tick_size(token_id).await?;
407
408 match tick_size {
409 None => Ok(min_tick_size),
410 Some(t) => {
411 if t < min_tick_size {
412 Err(PolyfillError::validation(format!(
413 "Tick size {} is smaller than min_tick_size {} for token_id: {}",
414 t, min_tick_size, token_id
415 )))
416 } else {
417 Ok(t)
418 }
419 }
420 }
421 }
422
423 async fn get_filled_order_options(
425 &self,
426 token_id: &str,
427 options: Option<&OrderOptions>,
428 ) -> Result<OrderOptions> {
429 let (tick_size, neg_risk, fee_rate_bps) = match options {
430 Some(o) => (o.tick_size, o.neg_risk, o.fee_rate_bps),
431 None => (None, None, None),
432 };
433
434 let tick_size = self.resolve_tick_size(token_id, tick_size).await?;
435 let neg_risk = match neg_risk {
436 Some(nr) => nr,
437 None => self.get_neg_risk(token_id).await?,
438 };
439
440 Ok(OrderOptions {
441 tick_size: Some(tick_size),
442 neg_risk: Some(neg_risk),
443 fee_rate_bps,
444 })
445 }
446
447 fn is_price_in_range(&self, price: Decimal, tick_size: Decimal) -> bool {
449 let min_price = tick_size;
450 let max_price = Decimal::ONE - tick_size;
451 price >= min_price && price <= max_price
452 }
453
454 pub async fn create_order(
456 &self,
457 order_args: &OrderArgs,
458 expiration: Option<u64>,
459 extras: Option<crate::types::ExtraOrderArgs>,
460 options: Option<&OrderOptions>,
461 ) -> Result<SignedOrderRequest> {
462 let order_builder = self.order_builder.as_ref()
463 .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
464
465 let create_order_options = self
466 .get_filled_order_options(&order_args.token_id, options)
467 .await?;
468
469 let expiration = expiration.unwrap_or(0);
470 let extras = extras.unwrap_or_default();
471
472 if !self.is_price_in_range(
473 order_args.price,
474 create_order_options.tick_size.expect("Should be filled"),
475 ) {
476 return Err(PolyfillError::validation("Price is not in range of tick_size"));
477 }
478
479 order_builder.create_order(
480 self.chain_id,
481 order_args,
482 expiration,
483 &extras,
484 &create_order_options,
485 )
486 }
487
488 async fn calculate_market_price(
490 &self,
491 token_id: &str,
492 side: Side,
493 amount: Decimal,
494 ) -> Result<Decimal> {
495 let book = self.get_order_book(token_id).await?;
496 let order_builder = self.order_builder.as_ref()
497 .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
498
499 let levels: Vec<crate::types::BookLevel> = match side {
501 Side::BUY => book.asks.into_iter().map(|s| crate::types::BookLevel {
502 price: s.price,
503 size: s.size,
504 }).collect(),
505 Side::SELL => book.bids.into_iter().map(|s| crate::types::BookLevel {
506 price: s.price,
507 size: s.size,
508 }).collect(),
509 };
510
511 order_builder.calculate_market_price(&levels, amount)
512 }
513
514 pub async fn create_market_order(
516 &self,
517 order_args: &crate::types::MarketOrderArgs,
518 extras: Option<crate::types::ExtraOrderArgs>,
519 options: Option<&OrderOptions>,
520 ) -> Result<SignedOrderRequest> {
521 let order_builder = self.order_builder.as_ref()
522 .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
523
524 let create_order_options = self
525 .get_filled_order_options(&order_args.token_id, options)
526 .await?;
527
528 let extras = extras.unwrap_or_default();
529 let price = self
530 .calculate_market_price(&order_args.token_id, Side::BUY, order_args.amount)
531 .await?;
532
533 if !self.is_price_in_range(
534 price,
535 create_order_options.tick_size.expect("Should be filled"),
536 ) {
537 return Err(PolyfillError::validation("Price is not in range of tick_size"));
538 }
539
540 order_builder.create_market_order(
541 self.chain_id,
542 order_args,
543 price,
544 &extras,
545 &create_order_options,
546 )
547 }
548
549 pub async fn post_order(
551 &self,
552 order: SignedOrderRequest,
553 order_type: OrderType,
554 ) -> Result<Value> {
555 let signer = self.signer.as_ref()
556 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
557 let api_creds = self.api_creds.as_ref()
558 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
559
560 let body = PostOrder::new(order, api_creds.api_key.clone(), order_type);
561
562 let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
563 let req = self.create_request_with_headers(Method::POST, "/order", headers.into_iter());
564
565 let response = req.json(&body).send().await?;
566 if !response.status().is_success() {
567 return Err(PolyfillError::api(response.status().as_u16(), "Failed to post order"));
568 }
569
570 Ok(response.json::<Value>().await?)
571 }
572
573 pub async fn create_and_post_order(&self, order_args: &OrderArgs) -> Result<Value> {
575 let order = self.create_order(order_args, None, None, None).await?;
576 self.post_order(order, OrderType::GTC).await
577 }
578
579 pub async fn cancel(&self, order_id: &str) -> Result<Value> {
581 let signer = self.signer.as_ref()
582 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
583 let api_creds = self.api_creds.as_ref()
584 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
585
586 let body = std::collections::HashMap::from([("orderID", order_id)]);
587
588 let headers = create_l2_headers(signer, api_creds, "DELETE", "/order", Some(&body))?;
589 let req = self.create_request_with_headers(Method::DELETE, "/order", headers.into_iter());
590
591 let response = req.json(&body).send().await?;
592 if !response.status().is_success() {
593 return Err(PolyfillError::api(response.status().as_u16(), "Failed to cancel order"));
594 }
595
596 Ok(response.json::<Value>().await?)
597 }
598
599 pub async fn cancel_orders(&self, order_ids: &[String]) -> Result<Value> {
601 let signer = self.signer.as_ref()
602 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
603 let api_creds = self.api_creds.as_ref()
604 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
605
606 let headers = create_l2_headers(signer, api_creds, "DELETE", "/orders", Some(order_ids))?;
607 let req = self.create_request_with_headers(Method::DELETE, "/orders", headers.into_iter());
608
609 let response = req.json(order_ids).send().await?;
610 if !response.status().is_success() {
611 return Err(PolyfillError::api(response.status().as_u16(), "Failed to cancel orders"));
612 }
613
614 Ok(response.json::<Value>().await?)
615 }
616
617 pub async fn cancel_all(&self) -> Result<Value> {
619 let signer = self.signer.as_ref()
620 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
621 let api_creds = self.api_creds.as_ref()
622 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
623
624 let headers = create_l2_headers::<Value>(signer, api_creds, "DELETE", "/cancel-all", None)?;
625 let req = self.create_request_with_headers(Method::DELETE, "/cancel-all", headers.into_iter());
626
627 let response = req.send().await?;
628 if !response.status().is_success() {
629 return Err(PolyfillError::api(response.status().as_u16(), "Failed to cancel all orders"));
630 }
631
632 Ok(response.json::<Value>().await?)
633 }
634
635 pub async fn get_orders(&self, params: Option<&crate::types::OpenOrderParams>, next_cursor: Option<&str>) -> Result<Vec<crate::types::OpenOrder>> {
644 let signer = self.signer.as_ref()
645 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
646 let api_creds = self.api_creds.as_ref()
647 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
648
649 let method = Method::GET;
650 let endpoint = "/data/orders";
651 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
652
653 let query_params = match params {
654 None => Vec::new(),
655 Some(p) => p.to_query_params(),
656 };
657
658 let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); let mut output = Vec::new();
660
661 while next_cursor != "LTE=" { let req = self.http_client
663 .request(method.clone(), format!("{}{}", self.base_url, endpoint))
664 .query(&query_params)
665 .query(&[("next_cursor", &next_cursor)]);
666
667 let r = headers
668 .clone()
669 .into_iter()
670 .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
671
672 let resp = r.send().await
673 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
674 .json::<Value>().await
675 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
676
677 let new_cursor = resp["next_cursor"]
678 .as_str()
679 .ok_or_else(|| PolyfillError::parse("Failed to parse next cursor".to_string(), None))?
680 .to_owned();
681
682 next_cursor = new_cursor;
683
684 let results = resp["data"].clone();
685 let orders = serde_json::from_value::<Vec<crate::types::OpenOrder>>(results)
686 .map_err(|e| PolyfillError::parse(format!("Failed to parse data from order response: {}", e), None))?;
687 output.extend(orders);
688 }
689
690 Ok(output)
691 }
692
693 pub async fn get_trades(&self, trade_params: Option<&crate::types::TradeParams>, next_cursor: Option<&str>) -> Result<Vec<Value>> {
704 let signer = self.signer.as_ref()
705 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
706 let api_creds = self.api_creds.as_ref()
707 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
708
709 let method = Method::GET;
710 let endpoint = "/data/trades";
711 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
712
713 let query_params = match trade_params {
714 None => Vec::new(),
715 Some(p) => p.to_query_params(),
716 };
717
718 let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); let mut output = Vec::new();
720
721 while next_cursor != "LTE=" { let req = self.http_client
723 .request(method.clone(), format!("{}{}", self.base_url, endpoint))
724 .query(&query_params)
725 .query(&[("next_cursor", &next_cursor)]);
726
727 let r = headers
728 .clone()
729 .into_iter()
730 .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
731
732 let resp = r.send().await
733 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
734 .json::<Value>().await
735 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
736
737 let new_cursor = resp["next_cursor"]
738 .as_str()
739 .ok_or_else(|| PolyfillError::parse("Failed to parse next cursor".to_string(), None))?
740 .to_owned();
741
742 next_cursor = new_cursor;
743
744 let results = resp["data"].clone();
745 output.push(results);
746 }
747
748 Ok(output)
749 }
750
751 pub async fn get_balance_allowance(&self, params: Option<crate::types::BalanceAllowanceParams>) -> Result<Value> {
759 let signer = self.signer.as_ref()
760 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
761 let api_creds = self.api_creds.as_ref()
762 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
763
764 let mut params = params.unwrap_or_default();
765 if params.signature_type.is_none() {
766 params.set_signature_type(
767 self.order_builder
768 .as_ref()
769 .expect("OrderBuilder not set")
770 .get_sig_type(),
771 );
772 }
773
774 let query_params = params.to_query_params();
775
776 let method = Method::GET;
777 let endpoint = "/balance-allowance";
778 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
779
780 let response = self.http_client
781 .request(method, format!("{}{}", self.base_url, endpoint))
782 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
783 .query(&query_params)
784 .send()
785 .await
786 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
787
788 response.json::<Value>().await
789 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
790 }
791
792 pub async fn get_notifications(&self) -> Result<Value> {
801 let signer = self.signer.as_ref()
802 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
803 let api_creds = self.api_creds.as_ref()
804 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
805
806 let method = Method::GET;
807 let endpoint = "/notifications";
808 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
809
810 let response = self.http_client
811 .request(method, format!("{}{}", self.base_url, endpoint))
812 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
813 .query(&[(
814 "signature_type",
815 &self
816 .order_builder
817 .as_ref()
818 .expect("OrderBuilder not set")
819 .get_sig_type().to_string(),
820 )])
821 .send()
822 .await
823 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
824
825 response.json::<Value>().await
826 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
827 }
828
829 pub async fn get_midpoints(&self, token_ids: &[String]) -> Result<std::collections::HashMap<String, Decimal>> {
837 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
838 .iter()
839 .map(|id| {
840 let mut map = std::collections::HashMap::new();
841 map.insert("token_id", id.clone());
842 map
843 })
844 .collect();
845
846 let response = self.http_client
847 .post(&format!("{}/midpoints", self.base_url))
848 .json(&request_data)
849 .send()
850 .await?;
851
852 if !response.status().is_success() {
853 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get batch midpoints"));
854 }
855
856 let midpoints: std::collections::HashMap<String, Decimal> = response.json().await?;
857 Ok(midpoints)
858 }
859
860 pub async fn get_prices(&self, book_params: &[crate::types::BookParams]) -> Result<std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>>> {
868 let request_data: Vec<std::collections::HashMap<&str, String>> = book_params
869 .iter()
870 .map(|params| {
871 let mut map = std::collections::HashMap::new();
872 map.insert("token_id", params.token_id.clone());
873 map.insert("side", params.side.as_str().to_string());
874 map
875 })
876 .collect();
877
878 let response = self.http_client
879 .post(&format!("{}/prices", self.base_url))
880 .json(&request_data)
881 .send()
882 .await?;
883
884 if !response.status().is_success() {
885 return Err(PolyfillError::api(response.status().as_u16(), "Failed to get batch prices"));
886 }
887
888 let prices: std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>> = response.json().await?;
889 Ok(prices)
890 }
891
892 pub async fn get_order_books(&self, token_ids: &[String]) -> Result<Vec<OrderBookSummary>> {
894 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
895 .iter()
896 .map(|id| {
897 let mut map = std::collections::HashMap::new();
898 map.insert("token_id", id.clone());
899 map
900 })
901 .collect();
902
903 let response = self.http_client
904 .post(&format!("{}/books", self.base_url))
905 .json(&request_data)
906 .send()
907 .await
908 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
909
910 response.json::<Vec<OrderBookSummary>>().await
911 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
912 }
913
914 pub async fn get_order(&self, order_id: &str) -> Result<crate::types::OpenOrder> {
916 let signer = self.signer.as_ref()
917 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
918 let api_creds = self.api_creds.as_ref()
919 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
920
921 let method = Method::GET;
922 let endpoint = &format!("/data/order/{}", order_id);
923 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
924
925 let response = self.http_client
926 .request(method, format!("{}{}", self.base_url, endpoint))
927 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
928 .send()
929 .await
930 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
931
932 response.json::<crate::types::OpenOrder>().await
933 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
934 }
935
936 pub async fn get_last_trade_price(&self, token_id: &str) -> Result<Value> {
938 let response = self.http_client
939 .get(&format!("{}/last-trade-price", self.base_url))
940 .query(&[("token_id", token_id)])
941 .send()
942 .await
943 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
944
945 response.json::<Value>().await
946 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
947 }
948
949 pub async fn get_last_trade_prices(&self, token_ids: &[String]) -> Result<Value> {
951 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
952 .iter()
953 .map(|id| {
954 let mut map = std::collections::HashMap::new();
955 map.insert("token_id", id.clone());
956 map
957 })
958 .collect();
959
960 let response = self.http_client
961 .post(&format!("{}/last-trades-prices", self.base_url))
962 .json(&request_data)
963 .send()
964 .await
965 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
966
967 response.json::<Value>().await
968 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
969 }
970
971 pub async fn cancel_market_orders(&self, market: Option<&str>, asset_id: Option<&str>) -> Result<Value> {
973 let signer = self.signer.as_ref()
974 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
975 let api_creds = self.api_creds.as_ref()
976 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
977
978 let method = Method::DELETE;
979 let endpoint = "/cancel-market-orders";
980 let body = std::collections::HashMap::from([
981 ("market", market.unwrap_or("")),
982 ("asset_id", asset_id.unwrap_or("")),
983 ]);
984
985 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
986
987 let response = self.http_client
988 .request(method, format!("{}{}", self.base_url, endpoint))
989 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
990 .json(&body)
991 .send()
992 .await
993 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
994
995 response.json::<Value>().await
996 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
997 }
998
999 pub async fn drop_notifications(&self, ids: &[String]) -> Result<Value> {
1001 let signer = self.signer.as_ref()
1002 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1003 let api_creds = self.api_creds.as_ref()
1004 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1005
1006 let method = Method::DELETE;
1007 let endpoint = "/notifications";
1008 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1009
1010 let response = self.http_client
1011 .request(method, format!("{}{}", self.base_url, endpoint))
1012 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
1013 .query(&[("ids", ids.join(","))])
1014 .send()
1015 .await
1016 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1017
1018 response.json::<Value>().await
1019 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1020 }
1021
1022 pub async fn update_balance_allowance(&self, params: Option<crate::types::BalanceAllowanceParams>) -> Result<Value> {
1024 let signer = self.signer.as_ref()
1025 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1026 let api_creds = self.api_creds.as_ref()
1027 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1028
1029 let mut params = params.unwrap_or_default();
1030 if params.signature_type.is_none() {
1031 params.set_signature_type(
1032 self.order_builder
1033 .as_ref()
1034 .expect("OrderBuilder not set")
1035 .get_sig_type(),
1036 );
1037 }
1038
1039 let query_params = params.to_query_params();
1040
1041 let method = Method::GET;
1042 let endpoint = "/balance-allowance/update";
1043 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1044
1045 let response = self.http_client
1046 .request(method, format!("{}{}", self.base_url, endpoint))
1047 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
1048 .query(&query_params)
1049 .send()
1050 .await
1051 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1052
1053 response.json::<Value>().await
1054 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1055 }
1056
1057 pub async fn is_order_scoring(&self, order_id: &str) -> Result<bool> {
1059 let signer = self.signer.as_ref()
1060 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1061 let api_creds = self.api_creds.as_ref()
1062 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1063
1064 let method = Method::GET;
1065 let endpoint = "/order-scoring";
1066 let headers = create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1067
1068 let response = self.http_client
1069 .request(method, format!("{}{}", self.base_url, endpoint))
1070 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
1071 .query(&[("order_id", order_id)])
1072 .send()
1073 .await
1074 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1075
1076 let result: Value = response.json().await
1077 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
1078
1079 Ok(result["scoring"].as_bool().unwrap_or(false))
1080 }
1081
1082 pub async fn are_orders_scoring(&self, order_ids: &[&str]) -> Result<std::collections::HashMap<String, bool>> {
1084 let signer = self.signer.as_ref()
1085 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1086 let api_creds = self.api_creds.as_ref()
1087 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1088
1089 let method = Method::POST;
1090 let endpoint = "/orders-scoring";
1091 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(order_ids))?;
1092
1093 let response = self.http_client
1094 .request(method, format!("{}{}", self.base_url, endpoint))
1095 .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect())
1096 .json(order_ids)
1097 .send()
1098 .await
1099 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1100
1101 response.json::<std::collections::HashMap<String, bool>>().await
1102 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1103 }
1104
1105 pub async fn get_sampling_markets(&self, next_cursor: Option<&str>) -> Result<crate::types::MarketsResponse> {
1107 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self.http_client
1110 .get(&format!("{}/sampling-markets", self.base_url))
1111 .query(&[("next_cursor", next_cursor)])
1112 .send()
1113 .await
1114 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1115
1116 response.json::<crate::types::MarketsResponse>().await
1117 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1118 }
1119
1120 pub async fn get_sampling_simplified_markets(&self, next_cursor: Option<&str>) -> Result<crate::types::SimplifiedMarketsResponse> {
1122 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self.http_client
1125 .get(&format!("{}/sampling-simplified-markets", self.base_url))
1126 .query(&[("next_cursor", next_cursor)])
1127 .send()
1128 .await
1129 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1130
1131 response.json::<crate::types::SimplifiedMarketsResponse>().await
1132 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1133 }
1134
1135 pub async fn get_markets(&self, next_cursor: Option<&str>) -> Result<crate::types::MarketsResponse> {
1137 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self.http_client
1140 .get(&format!("{}/markets", self.base_url))
1141 .query(&[("next_cursor", next_cursor)])
1142 .send()
1143 .await
1144 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1145
1146 response.json::<crate::types::MarketsResponse>().await
1147 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1148 }
1149
1150 pub async fn get_simplified_markets(&self, next_cursor: Option<&str>) -> Result<crate::types::SimplifiedMarketsResponse> {
1152 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self.http_client
1155 .get(&format!("{}/simplified-markets", self.base_url))
1156 .query(&[("next_cursor", next_cursor)])
1157 .send()
1158 .await
1159 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1160
1161 response.json::<crate::types::SimplifiedMarketsResponse>().await
1162 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1163 }
1164
1165 pub async fn get_market(&self, condition_id: &str) -> Result<crate::types::Market> {
1167 let response = self.http_client
1168 .get(&format!("{}/markets/{}", self.base_url, condition_id))
1169 .send()
1170 .await
1171 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1172
1173 response.json::<crate::types::Market>().await
1174 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1175 }
1176
1177 pub async fn get_market_trades_events(&self, condition_id: &str) -> Result<Value> {
1179 let response = self.http_client
1180 .get(&format!("{}/live-activity/events/{}", self.base_url, condition_id))
1181 .send()
1182 .await
1183 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1184
1185 response.json::<Value>().await
1186 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1187 }
1188}
1189
1190pub use crate::types::{
1192 ExtraOrderArgs, MarketOrderArgs, OrderBookSummary, OrderSummary,
1193 MidpointResponse, SpreadResponse, PriceResponse, TickSizeResponse,
1194 NegRiskResponse, MarketsResponse, Market, Token, Rewards,
1195};
1196
1197#[derive(Debug, Default)]
1199pub struct CreateOrderOptions {
1200 pub tick_size: Option<Decimal>,
1201 pub neg_risk: Option<bool>,
1202}
1203
1204pub type PolyfillClient = ClobClient;
1206
1207#[cfg(test)]
1208mod tests {
1209 use super::*;
1210 use crate::types::*;
1211 use mockito::{Matcher, Server};
1212 use rust_decimal::Decimal;
1213 use std::str::FromStr;
1214 use tokio;
1215
1216 fn create_test_client(base_url: &str) -> ClobClient {
1217 ClobClient::new(base_url)
1218 }
1219
1220 fn create_test_client_with_auth(base_url: &str) -> ClobClient {
1221 ClobClient::with_l1_headers(
1222 base_url,
1223 "0x1234567890123456789012345678901234567890123456789012345678901234",
1224 137,
1225 )
1226 }
1227
1228 #[tokio::test]
1229 async fn test_client_creation() {
1230 let client = create_test_client("https://test.example.com");
1231 assert_eq!(client.base_url, "https://test.example.com");
1232 assert!(client.signer.is_none());
1233 assert!(client.api_creds.is_none());
1234 }
1235
1236 #[tokio::test]
1237 async fn test_client_with_l1_headers() {
1238 let client = create_test_client_with_auth("https://test.example.com");
1239 assert_eq!(client.base_url, "https://test.example.com");
1240 assert!(client.signer.is_some());
1241 assert_eq!(client.chain_id, 137);
1242 }
1243
1244 #[tokio::test]
1245 async fn test_client_with_l2_headers() {
1246 let api_creds = ApiCredentials {
1247 api_key: "test_key".to_string(),
1248 secret: "test_secret".to_string(),
1249 passphrase: "test_passphrase".to_string(),
1250 };
1251
1252 let client = ClobClient::with_l2_headers(
1253 "https://test.example.com",
1254 "0x1234567890123456789012345678901234567890123456789012345678901234",
1255 137,
1256 api_creds.clone(),
1257 );
1258
1259 assert_eq!(client.base_url, "https://test.example.com");
1260 assert!(client.signer.is_some());
1261 assert!(client.api_creds.is_some());
1262 assert_eq!(client.chain_id, 137);
1263 }
1264
1265 #[tokio::test]
1266 async fn test_set_api_creds() {
1267 let mut client = create_test_client("https://test.example.com");
1268 assert!(client.api_creds.is_none());
1269
1270 let api_creds = ApiCredentials {
1271 api_key: "test_key".to_string(),
1272 secret: "test_secret".to_string(),
1273 passphrase: "test_passphrase".to_string(),
1274 };
1275
1276 client.set_api_creds(api_creds.clone());
1277 assert!(client.api_creds.is_some());
1278 assert_eq!(client.api_creds.unwrap().api_key, "test_key");
1279 }
1280
1281 #[tokio::test]
1282 async fn test_get_sampling_markets_success() {
1283 let mut server = Server::new_async().await;
1284 let mock_response = r#"{
1285 "limit": "10",
1286 "count": "2",
1287 "next_cursor": null,
1288 "data": [
1289 {
1290 "condition_id": "0x123",
1291 "tokens": [
1292 {"token_id": "0x456", "outcome": "Yes"},
1293 {"token_id": "0x789", "outcome": "No"}
1294 ],
1295 "rewards": {
1296 "rates": null,
1297 "min_size": "1.0",
1298 "max_spread": "0.1",
1299 "event_start_date": null,
1300 "event_end_date": null,
1301 "in_game_multiplier": null,
1302 "reward_epoch": null
1303 },
1304 "min_incentive_size": null,
1305 "max_incentive_spread": null,
1306 "active": true,
1307 "closed": false,
1308 "question_id": "0x123",
1309 "minimum_order_size": "1.0",
1310 "minimum_tick_size": "0.01",
1311 "description": "Test market",
1312 "category": "test",
1313 "end_date_iso": null,
1314 "game_start_time": null,
1315 "question": "Will this test pass?",
1316 "market_slug": "test-market",
1317 "seconds_delay": "0",
1318 "icon": "",
1319 "fpmm": ""
1320 }
1321 ]
1322 }"#;
1323
1324 let mock = server
1325 .mock("GET", "/sampling-markets")
1326 .match_query(Matcher::UrlEncoded("next_cursor".into(), "MA==".into()))
1327 .with_status(200)
1328 .with_header("content-type", "application/json")
1329 .with_body(mock_response)
1330 .create_async()
1331 .await;
1332
1333 let client = create_test_client(&server.url());
1334 let result = client.get_sampling_markets(None).await;
1335
1336 mock.assert_async().await;
1337 assert!(result.is_ok());
1338 let markets = result.unwrap();
1339 assert_eq!(markets.data.len(), 1);
1340 assert_eq!(markets.data[0].question, "Will this test pass?");
1341 }
1342
1343 #[tokio::test]
1344 async fn test_get_sampling_markets_with_cursor() {
1345 let mut server = Server::new_async().await;
1346 let mock_response = r#"{
1347 "limit": "5",
1348 "count": "0",
1349 "next_cursor": null,
1350 "data": []
1351 }"#;
1352
1353 let mock = server
1354 .mock("GET", "/sampling-markets")
1355 .match_query(Matcher::AllOf(vec![
1356 Matcher::UrlEncoded("next_cursor".into(), "test_cursor".into()),
1357 ]))
1358 .with_status(200)
1359 .with_header("content-type", "application/json")
1360 .with_body(mock_response)
1361 .create_async()
1362 .await;
1363
1364 let client = create_test_client(&server.url());
1365 let result = client.get_sampling_markets(Some("test_cursor")).await;
1366
1367 mock.assert_async().await;
1368 assert!(result.is_ok());
1369 let markets = result.unwrap();
1370 assert_eq!(markets.data.len(), 0);
1371 }
1372
1373 #[tokio::test]
1374 async fn test_get_order_book_success() {
1375 let mut server = Server::new_async().await;
1376 let mock_response = r#"{
1377 "market": "0x123",
1378 "asset_id": "0x123",
1379 "hash": "0xabc123",
1380 "timestamp": "1234567890",
1381 "bids": [
1382 {"price": "0.75", "size": "100.0"}
1383 ],
1384 "asks": [
1385 {"price": "0.76", "size": "50.0"}
1386 ]
1387 }"#;
1388
1389 let mock = server
1390 .mock("GET", "/book")
1391 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1392 .with_status(200)
1393 .with_header("content-type", "application/json")
1394 .with_body(mock_response)
1395 .create_async()
1396 .await;
1397
1398 let client = create_test_client(&server.url());
1399 let result = client.get_order_book("0x123").await;
1400
1401 mock.assert_async().await;
1402 assert!(result.is_ok());
1403 let book = result.unwrap();
1404 assert_eq!(book.market, "0x123");
1405 assert_eq!(book.bids.len(), 1);
1406 assert_eq!(book.asks.len(), 1);
1407 }
1408
1409 #[tokio::test]
1410 async fn test_get_midpoint_success() {
1411 let mut server = Server::new_async().await;
1412 let mock_response = r#"{
1413 "mid": "0.755"
1414 }"#;
1415
1416 let mock = server
1417 .mock("GET", "/midpoint")
1418 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1419 .with_status(200)
1420 .with_header("content-type", "application/json")
1421 .with_body(mock_response)
1422 .create_async()
1423 .await;
1424
1425 let client = create_test_client(&server.url());
1426 let result = client.get_midpoint("0x123").await;
1427
1428 mock.assert_async().await;
1429 assert!(result.is_ok());
1430 let response = result.unwrap();
1431 assert_eq!(response.mid, Decimal::from_str("0.755").unwrap());
1432 }
1433
1434 #[tokio::test]
1435 async fn test_get_spread_success() {
1436 let mut server = Server::new_async().await;
1437 let mock_response = r#"{
1438 "spread": "0.01"
1439 }"#;
1440
1441 let mock = server
1442 .mock("GET", "/spread")
1443 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1444 .with_status(200)
1445 .with_header("content-type", "application/json")
1446 .with_body(mock_response)
1447 .create_async()
1448 .await;
1449
1450 let client = create_test_client(&server.url());
1451 let result = client.get_spread("0x123").await;
1452
1453 mock.assert_async().await;
1454 assert!(result.is_ok());
1455 let response = result.unwrap();
1456 assert_eq!(response.spread, Decimal::from_str("0.01").unwrap());
1457 }
1458
1459 #[tokio::test]
1460 async fn test_get_price_success() {
1461 let mut server = Server::new_async().await;
1462 let mock_response = r#"{
1463 "price": "0.76"
1464 }"#;
1465
1466 let mock = server
1467 .mock("GET", "/price")
1468 .match_query(Matcher::AllOf(vec![
1469 Matcher::UrlEncoded("token_id".into(), "0x123".into()),
1470 Matcher::UrlEncoded("side".into(), "BUY".into()),
1471 ]))
1472 .with_status(200)
1473 .with_header("content-type", "application/json")
1474 .with_body(mock_response)
1475 .create_async()
1476 .await;
1477
1478 let client = create_test_client(&server.url());
1479 let result = client.get_price("0x123", Side::BUY).await;
1480
1481 mock.assert_async().await;
1482 assert!(result.is_ok());
1483 let response = result.unwrap();
1484 assert_eq!(response.price, Decimal::from_str("0.76").unwrap());
1485 }
1486
1487 #[tokio::test]
1488 async fn test_get_tick_size_success() {
1489 let mut server = Server::new_async().await;
1490 let mock_response = r#"{
1491 "minimum_tick_size": "0.01"
1492 }"#;
1493
1494 let mock = server
1495 .mock("GET", "/tick-size")
1496 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1497 .with_status(200)
1498 .with_header("content-type", "application/json")
1499 .with_body(mock_response)
1500 .create_async()
1501 .await;
1502
1503 let client = create_test_client(&server.url());
1504 let result = client.get_tick_size("0x123").await;
1505
1506 mock.assert_async().await;
1507 assert!(result.is_ok());
1508 let tick_size = result.unwrap();
1509 assert_eq!(tick_size, Decimal::from_str("0.01").unwrap());
1510 }
1511
1512 #[tokio::test]
1513 async fn test_get_neg_risk_success() {
1514 let mut server = Server::new_async().await;
1515 let mock_response = r#"{
1516 "neg_risk": false
1517 }"#;
1518
1519 let mock = server
1520 .mock("GET", "/neg-risk")
1521 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
1522 .with_status(200)
1523 .with_header("content-type", "application/json")
1524 .with_body(mock_response)
1525 .create_async()
1526 .await;
1527
1528 let client = create_test_client(&server.url());
1529 let result = client.get_neg_risk("0x123").await;
1530
1531 mock.assert_async().await;
1532 assert!(result.is_ok());
1533 let neg_risk = result.unwrap();
1534 assert!(!neg_risk);
1535 }
1536
1537 #[tokio::test]
1538 async fn test_api_error_handling() {
1539 let mut server = Server::new_async().await;
1540
1541 let mock = server
1542 .mock("GET", "/book")
1543 .match_query(Matcher::UrlEncoded("token_id".into(), "invalid_token".into()))
1544 .with_status(404)
1545 .with_header("content-type", "application/json")
1546 .with_body(r#"{"error": "Market not found"}"#)
1547 .create_async()
1548 .await;
1549
1550 let client = create_test_client(&server.url());
1551 let result = client.get_order_book("invalid_token").await;
1552
1553 mock.assert_async().await;
1554 assert!(result.is_err());
1555
1556 let error = result.unwrap_err();
1557 assert!(matches!(error, PolyfillError::Network { .. }) || matches!(error, PolyfillError::Api { .. }));
1559 }
1560
1561 #[tokio::test]
1562 async fn test_network_error_handling() {
1563 let client = create_test_client("http://invalid-host-that-does-not-exist.com");
1565 let result = client.get_order_book("0x123").await;
1566
1567 assert!(result.is_err());
1568 let error = result.unwrap_err();
1569 assert!(matches!(error, PolyfillError::Network { .. }));
1570 }
1571
1572 #[test]
1573 fn test_client_url_validation() {
1574 let client = create_test_client("https://test.example.com");
1575 assert_eq!(client.base_url, "https://test.example.com");
1576
1577 let client2 = create_test_client("http://localhost:8080");
1578 assert_eq!(client2.base_url, "http://localhost:8080");
1579 }
1580
1581 #[tokio::test]
1582 async fn test_get_midpoints_batch() {
1583 let mut server = Server::new_async().await;
1584 let mock_response = r#"{
1585 "0x123": "0.755",
1586 "0x456": "0.623"
1587 }"#;
1588
1589 let mock = server
1590 .mock("POST", "/midpoints")
1591 .with_header("content-type", "application/json")
1592 .with_status(200)
1593 .with_header("content-type", "application/json")
1594 .with_body(mock_response)
1595 .create_async()
1596 .await;
1597
1598 let client = create_test_client(&server.url());
1599 let token_ids = vec!["0x123".to_string(), "0x456".to_string()];
1600 let result = client.get_midpoints(&token_ids).await;
1601
1602 mock.assert_async().await;
1603 assert!(result.is_ok());
1604 let midpoints = result.unwrap();
1605 assert_eq!(midpoints.len(), 2);
1606 assert_eq!(midpoints.get("0x123").unwrap(), &Decimal::from_str("0.755").unwrap());
1607 assert_eq!(midpoints.get("0x456").unwrap(), &Decimal::from_str("0.623").unwrap());
1608 }
1609
1610 #[test]
1611 fn test_client_configuration() {
1612 let client = create_test_client("https://test.example.com");
1613
1614 assert!(client.signer.is_none());
1616 assert!(client.api_creds.is_none());
1617
1618 let auth_client = create_test_client_with_auth("https://test.example.com");
1620 assert!(auth_client.signer.is_some());
1621 assert_eq!(auth_client.chain_id, 137);
1622 }
1623
1624 #[tokio::test]
1625 async fn test_get_ok() {
1626 let mut server = Server::new_async().await;
1627 let mock_response = r#"{"status": "ok"}"#;
1628
1629 let mock = server
1630 .mock("GET", "/ok")
1631 .with_header("content-type", "application/json")
1632 .with_status(200)
1633 .with_body(mock_response)
1634 .create_async()
1635 .await;
1636
1637 let client = create_test_client(&server.url());
1638 let result = client.get_ok().await;
1639
1640 mock.assert_async().await;
1641 assert!(result);
1642 }
1643
1644 #[tokio::test]
1645 async fn test_get_prices_batch() {
1646 let mut server = Server::new_async().await;
1647 let mock_response = r#"{
1648 "0x123": {
1649 "BUY": "0.755",
1650 "SELL": "0.745"
1651 },
1652 "0x456": {
1653 "BUY": "0.623",
1654 "SELL": "0.613"
1655 }
1656 }"#;
1657
1658 let mock = server
1659 .mock("POST", "/prices")
1660 .with_header("content-type", "application/json")
1661 .with_status(200)
1662 .with_body(mock_response)
1663 .create_async()
1664 .await;
1665
1666 let client = create_test_client(&server.url());
1667 let book_params = vec![
1668 crate::types::BookParams {
1669 token_id: "0x123".to_string(),
1670 side: Side::BUY,
1671 },
1672 crate::types::BookParams {
1673 token_id: "0x456".to_string(),
1674 side: Side::SELL,
1675 },
1676 ];
1677 let result = client.get_prices(&book_params).await;
1678
1679 mock.assert_async().await;
1680 assert!(result.is_ok());
1681 let prices = result.unwrap();
1682 assert_eq!(prices.len(), 2);
1683 assert!(prices.contains_key("0x123"));
1684 assert!(prices.contains_key("0x456"));
1685 }
1686
1687 #[tokio::test]
1688 async fn test_get_server_time() {
1689 let mut server = Server::new_async().await;
1690 let mock_response = "1234567890"; let mock = server
1693 .mock("GET", "/time")
1694 .with_status(200)
1695 .with_body(mock_response)
1696 .create_async()
1697 .await;
1698
1699 let client = create_test_client(&server.url());
1700 let result = client.get_server_time().await;
1701
1702 mock.assert_async().await;
1703 assert!(result.is_ok());
1704 let timestamp = result.unwrap();
1705 assert_eq!(timestamp, 1234567890);
1706 }
1707
1708 #[tokio::test]
1709 async fn test_create_or_derive_api_key() {
1710 let mut server = Server::new_async().await;
1711 let mock_response = r#"{
1712 "apiKey": "test-api-key-123",
1713 "secret": "test-secret-456",
1714 "passphrase": "test-passphrase"
1715 }"#;
1716
1717 let create_mock = server
1719 .mock("POST", "/auth/api-key")
1720 .with_header("content-type", "application/json")
1721 .with_status(200)
1722 .with_body(mock_response)
1723 .create_async()
1724 .await;
1725
1726 let client = create_test_client_with_auth(&server.url());
1727 let result = client.create_or_derive_api_key(None).await;
1728
1729 create_mock.assert_async().await;
1730 assert!(result.is_ok());
1731 let api_creds = result.unwrap();
1732 assert_eq!(api_creds.api_key, "test-api-key-123");
1733 }
1734
1735 #[tokio::test]
1736 async fn test_get_order_books_batch() {
1737 let mut server = Server::new_async().await;
1738 let mock_response = r#"[
1739 {
1740 "market": "0x123",
1741 "asset_id": "0x123",
1742 "hash": "test-hash",
1743 "timestamp": "1234567890",
1744 "bids": [{"price": "0.75", "size": "100.0"}],
1745 "asks": [{"price": "0.76", "size": "50.0"}]
1746 }
1747 ]"#;
1748
1749 let mock = server
1750 .mock("POST", "/books")
1751 .with_header("content-type", "application/json")
1752 .with_status(200)
1753 .with_body(mock_response)
1754 .create_async()
1755 .await;
1756
1757 let client = create_test_client(&server.url());
1758 let token_ids = vec!["0x123".to_string()];
1759 let result = client.get_order_books(&token_ids).await;
1760
1761 mock.assert_async().await;
1762 if let Err(e) = &result {
1763 println!("Error: {:?}", e);
1764 }
1765 assert!(result.is_ok());
1766 let books = result.unwrap();
1767 assert_eq!(books.len(), 1);
1768 }
1769
1770 #[tokio::test]
1771 async fn test_order_args_creation() {
1772 let order_args = OrderArgs::new(
1774 "0x123",
1775 Decimal::from_str("0.75").unwrap(),
1776 Decimal::from_str("100.0").unwrap(),
1777 Side::BUY,
1778 );
1779
1780 assert_eq!(order_args.token_id, "0x123");
1781 assert_eq!(order_args.price, Decimal::from_str("0.75").unwrap());
1782 assert_eq!(order_args.size, Decimal::from_str("100.0").unwrap());
1783 assert_eq!(order_args.side, Side::BUY);
1784
1785 let default_args = OrderArgs::default();
1787 assert_eq!(default_args.token_id, "");
1788 assert_eq!(default_args.price, Decimal::ZERO);
1789 assert_eq!(default_args.size, Decimal::ZERO);
1790 assert_eq!(default_args.side, Side::BUY);
1791 }
1792}