1use crate::auth::{create_l1_headers, create_l2_headers};
7use crate::errors::{PolyfillError, Result};
8use crate::http_config::{
9 create_colocated_client, create_internet_client, create_optimized_client, prewarm_connections,
10};
11use crate::types::{OrderOptions, PostOrder, SignedOrderRequest};
12use alloy_primitives::U256;
13use alloy_signer_local::PrivateKeySigner;
14use reqwest::header::HeaderName;
15use reqwest::Client;
16use reqwest::{Method, RequestBuilder};
17use rust_decimal::prelude::FromPrimitive;
18use rust_decimal::Decimal;
19use serde_json::Value;
20use std::str::FromStr;
21
22pub use crate::types::{ApiCredentials as ApiCreds, OrderType, Side};
24
25#[derive(Debug)]
27pub struct OrderArgs {
28 pub token_id: String,
29 pub price: Decimal,
30 pub size: Decimal,
31 pub side: Side,
32}
33
34impl OrderArgs {
35 pub fn new(token_id: &str, price: Decimal, size: Decimal, side: Side) -> Self {
36 Self {
37 token_id: token_id.to_string(),
38 price,
39 size,
40 side,
41 }
42 }
43}
44
45impl Default for OrderArgs {
46 fn default() -> Self {
47 Self {
48 token_id: "".to_string(),
49 price: Decimal::ZERO,
50 size: Decimal::ZERO,
51 side: Side::BUY,
52 }
53 }
54}
55
56pub struct ClobClient {
58 pub http_client: Client,
59 pub base_url: String,
60 chain_id: u64,
61 signer: Option<PrivateKeySigner>,
62 api_creds: Option<ApiCreds>,
63 order_builder: Option<crate::orders::OrderBuilder>,
64 #[allow(dead_code)]
65 dns_cache: Option<std::sync::Arc<crate::dns_cache::DnsCache>>,
66 #[allow(dead_code)]
67 connection_manager: Option<std::sync::Arc<crate::connection_manager::ConnectionManager>>,
68 #[allow(dead_code)]
69 buffer_pool: std::sync::Arc<crate::buffer_pool::BufferPool>,
70}
71
72impl ClobClient {
73 pub fn new(host: &str) -> Self {
76 let optimized_client = reqwest::ClientBuilder::new()
79 .no_proxy()
81 .http2_adaptive_window(true)
82 .http2_initial_stream_window_size(512 * 1024) .tcp_nodelay(true)
84 .pool_max_idle_per_host(10)
85 .pool_idle_timeout(std::time::Duration::from_secs(90))
86 .build()
87 .unwrap_or_else(|_| {
88 reqwest::ClientBuilder::new()
89 .no_proxy()
90 .build()
91 .expect("Failed to build reqwest client")
92 });
93
94 let dns_cache = tokio::runtime::Handle::try_current().ok().and_then(|_| {
96 tokio::task::block_in_place(|| {
97 tokio::runtime::Handle::current().block_on(async {
98 let cache = crate::dns_cache::DnsCache::new().await.ok()?;
99 let hostname = host
100 .trim_start_matches("https://")
101 .trim_start_matches("http://")
102 .split('/')
103 .next()?;
104 cache.prewarm(hostname).await.ok()?;
105 Some(std::sync::Arc::new(cache))
106 })
107 })
108 });
109
110 let connection_manager = Some(std::sync::Arc::new(
112 crate::connection_manager::ConnectionManager::new(
113 optimized_client.clone(),
114 host.to_string(),
115 ),
116 ));
117
118 let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10));
120
121 let pool_clone = buffer_pool.clone();
123 if let Ok(_handle) = tokio::runtime::Handle::try_current() {
124 tokio::spawn(async move {
125 pool_clone.prewarm(3).await;
126 });
127 }
128
129 Self {
130 http_client: optimized_client,
131 base_url: host.to_string(),
132 chain_id: 137, signer: None,
134 api_creds: None,
135 order_builder: None,
136 dns_cache,
137 connection_manager,
138 buffer_pool,
139 }
140 }
141
142 pub fn new_colocated(host: &str) -> Self {
144 let http_client = create_colocated_client().unwrap_or_else(|_| {
145 reqwest::ClientBuilder::new()
146 .no_proxy()
147 .build()
148 .expect("Failed to build reqwest client")
149 });
150
151 let connection_manager = Some(std::sync::Arc::new(
152 crate::connection_manager::ConnectionManager::new(
153 http_client.clone(),
154 host.to_string(),
155 ),
156 ));
157 let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10));
158
159 Self {
160 http_client,
161 base_url: host.to_string(),
162 chain_id: 137,
163 signer: None,
164 api_creds: None,
165 order_builder: None,
166 dns_cache: None,
167 connection_manager,
168 buffer_pool,
169 }
170 }
171
172 pub fn new_internet(host: &str) -> Self {
174 let http_client = create_internet_client().unwrap_or_else(|_| {
175 reqwest::ClientBuilder::new()
176 .no_proxy()
177 .build()
178 .expect("Failed to build reqwest client")
179 });
180
181 let connection_manager = Some(std::sync::Arc::new(
182 crate::connection_manager::ConnectionManager::new(
183 http_client.clone(),
184 host.to_string(),
185 ),
186 ));
187 let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10));
188
189 Self {
190 http_client,
191 base_url: host.to_string(),
192 chain_id: 137,
193 signer: None,
194 api_creds: None,
195 order_builder: None,
196 dns_cache: None,
197 connection_manager,
198 buffer_pool,
199 }
200 }
201
202 pub fn with_l1_headers(host: &str, private_key: &str, chain_id: u64) -> Self {
204 let signer = private_key
205 .parse::<PrivateKeySigner>()
206 .expect("Invalid private key");
207
208 let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);
209
210 let http_client = create_optimized_client().unwrap_or_else(|_| {
211 reqwest::ClientBuilder::new()
212 .no_proxy()
213 .build()
214 .expect("Failed to build reqwest client")
215 });
216
217 let dns_cache = None; let connection_manager = Some(std::sync::Arc::new(
220 crate::connection_manager::ConnectionManager::new(
221 http_client.clone(),
222 host.to_string(),
223 ),
224 ));
225 let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10));
226
227 Self {
228 http_client,
229 base_url: host.to_string(),
230 chain_id,
231 signer: Some(signer),
232 api_creds: None,
233 order_builder: Some(order_builder),
234 dns_cache,
235 connection_manager,
236 buffer_pool,
237 }
238 }
239
240 pub fn with_l2_headers(
242 host: &str,
243 private_key: &str,
244 chain_id: u64,
245 api_creds: ApiCreds,
246 ) -> Self {
247 let signer = private_key
248 .parse::<PrivateKeySigner>()
249 .expect("Invalid private key");
250
251 let order_builder = crate::orders::OrderBuilder::new(signer.clone(), None, None);
252
253 let http_client = create_optimized_client().unwrap_or_else(|_| {
254 reqwest::ClientBuilder::new()
255 .no_proxy()
256 .build()
257 .expect("Failed to build reqwest client")
258 });
259
260 let dns_cache = None; let connection_manager = Some(std::sync::Arc::new(
263 crate::connection_manager::ConnectionManager::new(
264 http_client.clone(),
265 host.to_string(),
266 ),
267 ));
268 let buffer_pool = std::sync::Arc::new(crate::buffer_pool::BufferPool::new(512 * 1024, 10));
269
270 Self {
271 http_client,
272 base_url: host.to_string(),
273 chain_id,
274 signer: Some(signer),
275 api_creds: Some(api_creds),
276 order_builder: Some(order_builder),
277 dns_cache,
278 connection_manager,
279 buffer_pool,
280 }
281 }
282
283 pub fn set_api_creds(&mut self, api_creds: ApiCreds) {
285 self.api_creds = Some(api_creds);
286 }
287
288 pub async fn start_keepalive(&self, interval: std::time::Duration) {
291 if let Some(manager) = &self.connection_manager {
292 manager.start_keepalive(interval).await;
293 }
294 }
295
296 pub async fn stop_keepalive(&self) {
298 if let Some(manager) = &self.connection_manager {
299 manager.stop_keepalive().await;
300 }
301 }
302
303 pub async fn prewarm_connections(&self) -> Result<()> {
305 prewarm_connections(&self.http_client, &self.base_url)
306 .await
307 .map_err(|e| {
308 PolyfillError::network(format!("Failed to prewarm connections: {}", e), e)
309 })?;
310 Ok(())
311 }
312
313 pub fn get_address(&self) -> Option<String> {
315 use alloy_primitives::hex;
316 self.signer
317 .as_ref()
318 .map(|s| hex::encode_prefixed(s.address().as_slice()))
319 }
320
321 pub fn get_collateral_address(&self) -> Option<String> {
323 let config = crate::orders::get_contract_config(self.chain_id, false)?;
324 Some(config.collateral)
325 }
326
327 pub fn get_conditional_address(&self) -> Option<String> {
329 let config = crate::orders::get_contract_config(self.chain_id, false)?;
330 Some(config.conditional_tokens)
331 }
332
333 pub fn get_exchange_address(&self) -> Option<String> {
335 let config = crate::orders::get_contract_config(self.chain_id, false)?;
336 Some(config.exchange)
337 }
338
339 pub async fn get_ok(&self) -> bool {
341 match self
342 .http_client
343 .get(format!("{}/ok", self.base_url))
344 .send()
345 .await
346 {
347 Ok(response) => response.status().is_success(),
348 Err(_) => false,
349 }
350 }
351
352 pub async fn get_server_time(&self) -> Result<u64> {
354 let response = self
355 .http_client
356 .get(format!("{}/time", self.base_url))
357 .send()
358 .await?;
359
360 if !response.status().is_success() {
361 return Err(PolyfillError::api(
362 response.status().as_u16(),
363 "Failed to get server time",
364 ));
365 }
366
367 let time_text = response.text().await?;
368 let timestamp = time_text
369 .trim()
370 .parse::<u64>()
371 .map_err(|e| PolyfillError::parse(format!("Invalid timestamp format: {}", e), None))?;
372
373 Ok(timestamp)
374 }
375
376 pub async fn get_order_book(&self, token_id: &str) -> Result<OrderBookSummary> {
378 let response = self
379 .http_client
380 .get(format!("{}/book", self.base_url))
381 .query(&[("token_id", token_id)])
382 .send()
383 .await?;
384
385 if !response.status().is_success() {
386 return Err(PolyfillError::api(
387 response.status().as_u16(),
388 "Failed to get order book",
389 ));
390 }
391
392 let order_book: OrderBookSummary = response.json().await?;
393 Ok(order_book)
394 }
395
396 pub async fn get_midpoint(&self, token_id: &str) -> Result<MidpointResponse> {
398 let response = self
399 .http_client
400 .get(format!("{}/midpoint", self.base_url))
401 .query(&[("token_id", token_id)])
402 .send()
403 .await?;
404
405 if !response.status().is_success() {
406 return Err(PolyfillError::api(
407 response.status().as_u16(),
408 "Failed to get midpoint",
409 ));
410 }
411
412 let midpoint: MidpointResponse = response.json().await?;
413 Ok(midpoint)
414 }
415
416 pub async fn get_spread(&self, token_id: &str) -> Result<SpreadResponse> {
418 let response = self
419 .http_client
420 .get(format!("{}/spread", self.base_url))
421 .query(&[("token_id", token_id)])
422 .send()
423 .await?;
424
425 if !response.status().is_success() {
426 return Err(PolyfillError::api(
427 response.status().as_u16(),
428 "Failed to get spread",
429 ));
430 }
431
432 let spread: SpreadResponse = response.json().await?;
433 Ok(spread)
434 }
435
436 pub async fn get_spreads(
438 &self,
439 token_ids: &[String],
440 ) -> Result<std::collections::HashMap<String, Decimal>> {
441 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
442 .iter()
443 .map(|id| {
444 let mut map = std::collections::HashMap::new();
445 map.insert("token_id", id.clone());
446 map
447 })
448 .collect();
449
450 let response = self
451 .http_client
452 .post(format!("{}/spreads", self.base_url))
453 .json(&request_data)
454 .send()
455 .await?;
456
457 if !response.status().is_success() {
458 return Err(PolyfillError::api(
459 response.status().as_u16(),
460 "Failed to get batch spreads",
461 ));
462 }
463
464 response
465 .json::<std::collections::HashMap<String, Decimal>>()
466 .await
467 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
468 }
469
470 pub async fn get_price(&self, token_id: &str, side: Side) -> Result<PriceResponse> {
472 let response = self
473 .http_client
474 .get(format!("{}/price", self.base_url))
475 .query(&[("token_id", token_id), ("side", side.as_str())])
476 .send()
477 .await?;
478
479 if !response.status().is_success() {
480 return Err(PolyfillError::api(
481 response.status().as_u16(),
482 "Failed to get price",
483 ));
484 }
485
486 let price: PriceResponse = response.json().await?;
487 Ok(price)
488 }
489
490 fn validate_prices_history_asset_id(asset_id: &str) -> Result<()> {
491 if asset_id.is_empty() {
492 return Err(PolyfillError::validation(
493 "asset_id is required (use the decimal token_id / asset_id)",
494 ));
495 }
496
497 if asset_id.starts_with("0x") || asset_id.starts_with("0X") {
499 return Err(PolyfillError::validation(
500 "`/prices-history` expects a decimal token_id/asset_id, not a hex condition_id",
501 ));
502 }
503
504 if !asset_id.as_bytes().iter().all(u8::is_ascii_digit) {
505 return Err(PolyfillError::validation(
506 "asset_id must be a decimal string (token_id / asset_id)",
507 ));
508 }
509
510 Ok(())
511 }
512
513 pub async fn get_prices_history_interval(
518 &self,
519 asset_id: &str,
520 interval: PricesHistoryInterval,
521 fidelity: Option<u32>,
522 ) -> Result<PricesHistoryResponse> {
523 Self::validate_prices_history_asset_id(asset_id)?;
524
525 let mut request = self
526 .http_client
527 .get(format!("{}/prices-history", self.base_url))
528 .query(&[("market", asset_id), ("interval", interval.as_str())]);
529
530 if let Some(fidelity) = fidelity {
531 request = request.query(&[("fidelity", fidelity)]);
532 }
533
534 let response = request.send().await?;
535 if !response.status().is_success() {
536 let status = response.status().as_u16();
537 let body = response.text().await.unwrap_or_default();
538 let message = serde_json::from_str::<Value>(&body)
539 .ok()
540 .and_then(|v| {
541 v.get("error")
542 .and_then(Value::as_str)
543 .map(|s| s.to_string())
544 })
545 .unwrap_or_else(|| {
546 if body.is_empty() {
547 "Failed to get prices history".to_string()
548 } else {
549 body
550 }
551 });
552 return Err(PolyfillError::api(status, message));
553 }
554
555 Ok(response.json::<PricesHistoryResponse>().await?)
556 }
557
558 pub async fn get_prices_history_range(
562 &self,
563 asset_id: &str,
564 start_ts: u64,
565 end_ts: u64,
566 fidelity: Option<u32>,
567 ) -> Result<PricesHistoryResponse> {
568 Self::validate_prices_history_asset_id(asset_id)?;
569
570 if start_ts >= end_ts {
571 return Err(PolyfillError::validation(
572 "start_ts must be < end_ts for prices history",
573 ));
574 }
575
576 let mut request = self
577 .http_client
578 .get(format!("{}/prices-history", self.base_url))
579 .query(&[("market", asset_id)])
580 .query(&[("startTs", start_ts), ("endTs", end_ts)]);
581
582 if let Some(fidelity) = fidelity {
583 request = request.query(&[("fidelity", fidelity)]);
584 }
585
586 let response = request.send().await?;
587 if !response.status().is_success() {
588 let status = response.status().as_u16();
589 let body = response.text().await.unwrap_or_default();
590 let message = serde_json::from_str::<Value>(&body)
591 .ok()
592 .and_then(|v| {
593 v.get("error")
594 .and_then(Value::as_str)
595 .map(|s| s.to_string())
596 })
597 .unwrap_or_else(|| {
598 if body.is_empty() {
599 "Failed to get prices history".to_string()
600 } else {
601 body
602 }
603 });
604 return Err(PolyfillError::api(status, message));
605 }
606
607 Ok(response.json::<PricesHistoryResponse>().await?)
608 }
609
610 pub async fn get_tick_size(&self, token_id: &str) -> Result<Decimal> {
612 let response = self
613 .http_client
614 .get(format!("{}/tick-size", self.base_url))
615 .query(&[("token_id", token_id)])
616 .send()
617 .await?;
618
619 if !response.status().is_success() {
620 return Err(PolyfillError::api(
621 response.status().as_u16(),
622 "Failed to get tick size",
623 ));
624 }
625
626 let tick_size_response: Value = response.json().await?;
627 let tick_size = tick_size_response["minimum_tick_size"]
628 .as_str()
629 .and_then(|s| Decimal::from_str(s).ok())
630 .or_else(|| {
631 tick_size_response["minimum_tick_size"]
632 .as_f64()
633 .map(|f| Decimal::from_f64(f).unwrap_or(Decimal::ZERO))
634 })
635 .ok_or_else(|| PolyfillError::parse("Invalid tick size format", None))?;
636
637 Ok(tick_size)
638 }
639
640 pub async fn get_fee_rate_bps(&self, token_id: &str) -> Result<u32> {
642 let response = self
643 .http_client
644 .get(format!("{}/fee-rate", self.base_url))
645 .query(&[("token_id", token_id)])
646 .send()
647 .await
648 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
649
650 if !response.status().is_success() {
651 return Err(PolyfillError::api(
652 response.status().as_u16(),
653 "Failed to get fee rate",
654 ));
655 }
656
657 let fee_rate: crate::types::FeeRateResponse = response
658 .json()
659 .await
660 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
661 Ok(fee_rate.fee_rate_bps)
662 }
663
664 pub async fn create_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
666 let signer = self
667 .signer
668 .as_ref()
669 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
670
671 let headers = create_l1_headers(signer, nonce)?;
672 let req =
673 self.create_request_with_headers(Method::POST, "/auth/api-key", headers.into_iter());
674
675 let response = req.send().await?;
676 if !response.status().is_success() {
677 return Err(PolyfillError::api(
678 response.status().as_u16(),
679 "Failed to create API key",
680 ));
681 }
682
683 Ok(response.json::<ApiCreds>().await?)
684 }
685
686 pub async fn derive_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
688 let signer = self
689 .signer
690 .as_ref()
691 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
692
693 let headers = create_l1_headers(signer, nonce)?;
694 let req = self.create_request_with_headers(
695 Method::GET,
696 "/auth/derive-api-key",
697 headers.into_iter(),
698 );
699
700 let response = req.send().await?;
701 if !response.status().is_success() {
702 return Err(PolyfillError::api(
703 response.status().as_u16(),
704 "Failed to derive API key",
705 ));
706 }
707
708 Ok(response.json::<ApiCreds>().await?)
709 }
710
711 pub async fn create_or_derive_api_key(&self, nonce: Option<U256>) -> Result<ApiCreds> {
713 match self.create_api_key(nonce).await {
714 Ok(creds) => Ok(creds),
715 Err(PolyfillError::Api { .. }) => self.derive_api_key(nonce).await,
718 Err(err) => Err(err),
719 }
720 }
721
722 pub async fn get_api_keys(&self) -> Result<Vec<String>> {
724 let signer = self
725 .signer
726 .as_ref()
727 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
728 let api_creds = self
729 .api_creds
730 .as_ref()
731 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
732
733 let method = Method::GET;
734 let endpoint = "/auth/api-keys";
735 let headers =
736 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
737
738 let response = self
739 .http_client
740 .request(method, format!("{}{}", self.base_url, endpoint))
741 .headers(
742 headers
743 .into_iter()
744 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
745 .collect(),
746 )
747 .send()
748 .await
749 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
750
751 let api_keys_response: crate::types::ApiKeysResponse = response
752 .json()
753 .await
754 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
755
756 Ok(api_keys_response.api_keys)
757 }
758
759 pub async fn delete_api_key(&self) -> Result<String> {
761 let signer = self
762 .signer
763 .as_ref()
764 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
765 let api_creds = self
766 .api_creds
767 .as_ref()
768 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
769
770 let method = Method::DELETE;
771 let endpoint = "/auth/api-key";
772 let headers =
773 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
774
775 let response = self
776 .http_client
777 .request(method, format!("{}{}", self.base_url, endpoint))
778 .headers(
779 headers
780 .into_iter()
781 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
782 .collect(),
783 )
784 .send()
785 .await
786 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
787
788 response
789 .text()
790 .await
791 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
792 }
793
794 fn create_request_with_headers(
796 &self,
797 method: Method,
798 endpoint: &str,
799 headers: impl Iterator<Item = (&'static str, String)>,
800 ) -> RequestBuilder {
801 let req = self
802 .http_client
803 .request(method, format!("{}{}", &self.base_url, endpoint));
804 headers.fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v))
805 }
806
807 pub async fn get_neg_risk(&self, token_id: &str) -> Result<bool> {
809 let response = self
810 .http_client
811 .get(format!("{}/neg-risk", self.base_url))
812 .query(&[("token_id", token_id)])
813 .send()
814 .await?;
815
816 if !response.status().is_success() {
817 return Err(PolyfillError::api(
818 response.status().as_u16(),
819 "Failed to get neg risk",
820 ));
821 }
822
823 let neg_risk_response: Value = response.json().await?;
824 let neg_risk = neg_risk_response["neg_risk"]
825 .as_bool()
826 .ok_or_else(|| PolyfillError::parse("Invalid neg risk format", None))?;
827
828 Ok(neg_risk)
829 }
830
831 async fn resolve_tick_size(
833 &self,
834 token_id: &str,
835 tick_size: Option<Decimal>,
836 ) -> Result<Decimal> {
837 let min_tick_size = self.get_tick_size(token_id).await?;
838
839 match tick_size {
840 None => Ok(min_tick_size),
841 Some(t) => {
842 if t < min_tick_size {
843 Err(PolyfillError::validation(format!(
844 "Tick size {} is smaller than min_tick_size {} for token_id: {}",
845 t, min_tick_size, token_id
846 )))
847 } else {
848 Ok(t)
849 }
850 },
851 }
852 }
853
854 async fn get_filled_order_options(
856 &self,
857 token_id: &str,
858 options: Option<&OrderOptions>,
859 ) -> Result<OrderOptions> {
860 let (tick_size, neg_risk, fee_rate_bps) = match options {
861 Some(o) => (o.tick_size, o.neg_risk, o.fee_rate_bps),
862 None => (None, None, None),
863 };
864
865 let tick_size = self.resolve_tick_size(token_id, tick_size).await?;
866 let neg_risk = match neg_risk {
867 Some(nr) => nr,
868 None => self.get_neg_risk(token_id).await?,
869 };
870
871 Ok(OrderOptions {
872 tick_size: Some(tick_size),
873 neg_risk: Some(neg_risk),
874 fee_rate_bps,
875 })
876 }
877
878 fn is_price_in_range(&self, price: Decimal, tick_size: Decimal) -> bool {
880 let min_price = tick_size;
881 let max_price = Decimal::ONE - tick_size;
882 price >= min_price && price <= max_price
883 }
884
885 pub async fn create_order(
887 &self,
888 order_args: &OrderArgs,
889 expiration: Option<u64>,
890 extras: Option<crate::types::ExtraOrderArgs>,
891 options: Option<&OrderOptions>,
892 ) -> Result<SignedOrderRequest> {
893 let order_builder = self
894 .order_builder
895 .as_ref()
896 .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
897
898 let create_order_options = self
899 .get_filled_order_options(&order_args.token_id, options)
900 .await?;
901
902 let expiration = expiration.unwrap_or(0);
903 let extras = extras.unwrap_or_default();
904
905 if !self.is_price_in_range(
906 order_args.price,
907 create_order_options.tick_size.expect("Should be filled"),
908 ) {
909 return Err(PolyfillError::validation(
910 "Price is not in range of tick_size",
911 ));
912 }
913
914 order_builder.create_order(
915 self.chain_id,
916 order_args,
917 expiration,
918 &extras,
919 &create_order_options,
920 )
921 }
922
923 async fn calculate_market_price(
925 &self,
926 token_id: &str,
927 side: Side,
928 amount: Decimal,
929 ) -> Result<Decimal> {
930 let book = self.get_order_book(token_id).await?;
931 let order_builder = self
932 .order_builder
933 .as_ref()
934 .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
935
936 let levels: Vec<crate::types::BookLevel> = match side {
938 Side::BUY => book
939 .asks
940 .into_iter()
941 .map(|s| crate::types::BookLevel {
942 price: s.price,
943 size: s.size,
944 })
945 .collect(),
946 Side::SELL => book
947 .bids
948 .into_iter()
949 .map(|s| crate::types::BookLevel {
950 price: s.price,
951 size: s.size,
952 })
953 .collect(),
954 };
955
956 order_builder.calculate_market_price(&levels, amount)
957 }
958
959 pub async fn create_market_order(
961 &self,
962 order_args: &crate::types::MarketOrderArgs,
963 extras: Option<crate::types::ExtraOrderArgs>,
964 options: Option<&OrderOptions>,
965 ) -> Result<SignedOrderRequest> {
966 let order_builder = self
967 .order_builder
968 .as_ref()
969 .ok_or_else(|| PolyfillError::auth("Order builder not initialized"))?;
970
971 let create_order_options = self
972 .get_filled_order_options(&order_args.token_id, options)
973 .await?;
974
975 let extras = extras.unwrap_or_default();
976 let price = self
977 .calculate_market_price(&order_args.token_id, Side::BUY, order_args.amount)
978 .await?;
979
980 if !self.is_price_in_range(
981 price,
982 create_order_options.tick_size.expect("Should be filled"),
983 ) {
984 return Err(PolyfillError::validation(
985 "Price is not in range of tick_size",
986 ));
987 }
988
989 order_builder.create_market_order(
990 self.chain_id,
991 order_args,
992 price,
993 &extras,
994 &create_order_options,
995 )
996 }
997
998 pub async fn post_order(
1000 &self,
1001 order: SignedOrderRequest,
1002 order_type: OrderType,
1003 ) -> Result<Value> {
1004 let signer = self
1005 .signer
1006 .as_ref()
1007 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1008 let api_creds = self
1009 .api_creds
1010 .as_ref()
1011 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1012
1013 let body = PostOrder::new(order, api_creds.api_key.clone(), order_type);
1016
1017 let headers = create_l2_headers(signer, api_creds, "POST", "/order", Some(&body))?;
1018 let req = self.create_request_with_headers(Method::POST, "/order", headers.into_iter());
1019
1020 let response = req.json(&body).send().await?;
1021 if !response.status().is_success() {
1022 let status = response.status().as_u16();
1023 let body = response.text().await.unwrap_or_default();
1024 let message = if body.is_empty() {
1025 "Failed to post order".to_string()
1026 } else {
1027 format!("Failed to post order: {}", body)
1028 };
1029 return Err(PolyfillError::api(status, message));
1030 }
1031
1032 Ok(response.json::<Value>().await?)
1033 }
1034
1035 pub async fn create_and_post_order(&self, order_args: &OrderArgs) -> Result<Value> {
1037 let order = self.create_order(order_args, None, None, None).await?;
1038 self.post_order(order, OrderType::GTC).await
1039 }
1040
1041 pub async fn cancel(&self, order_id: &str) -> Result<Value> {
1043 let signer = self
1044 .signer
1045 .as_ref()
1046 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1047 let api_creds = self
1048 .api_creds
1049 .as_ref()
1050 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1051
1052 let body = std::collections::HashMap::from([("orderID", order_id)]);
1053
1054 let headers = create_l2_headers(signer, api_creds, "DELETE", "/order", Some(&body))?;
1055 let req = self.create_request_with_headers(Method::DELETE, "/order", headers.into_iter());
1056
1057 let response = req.json(&body).send().await?;
1058 if !response.status().is_success() {
1059 return Err(PolyfillError::api(
1060 response.status().as_u16(),
1061 "Failed to cancel order",
1062 ));
1063 }
1064
1065 Ok(response.json::<Value>().await?)
1066 }
1067
1068 pub async fn cancel_orders(&self, order_ids: &[String]) -> Result<Value> {
1070 let signer = self
1071 .signer
1072 .as_ref()
1073 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1074 let api_creds = self
1075 .api_creds
1076 .as_ref()
1077 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1078
1079 let headers = create_l2_headers(signer, api_creds, "DELETE", "/orders", Some(order_ids))?;
1080 let req = self.create_request_with_headers(Method::DELETE, "/orders", headers.into_iter());
1081
1082 let response = req.json(order_ids).send().await?;
1083 if !response.status().is_success() {
1084 return Err(PolyfillError::api(
1085 response.status().as_u16(),
1086 "Failed to cancel orders",
1087 ));
1088 }
1089
1090 Ok(response.json::<Value>().await?)
1091 }
1092
1093 pub async fn cancel_all(&self) -> Result<Value> {
1095 let signer = self
1096 .signer
1097 .as_ref()
1098 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1099 let api_creds = self
1100 .api_creds
1101 .as_ref()
1102 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1103
1104 let headers = create_l2_headers::<Value>(signer, api_creds, "DELETE", "/cancel-all", None)?;
1105 let req =
1106 self.create_request_with_headers(Method::DELETE, "/cancel-all", headers.into_iter());
1107
1108 let response = req.send().await?;
1109 if !response.status().is_success() {
1110 return Err(PolyfillError::api(
1111 response.status().as_u16(),
1112 "Failed to cancel all orders",
1113 ));
1114 }
1115
1116 Ok(response.json::<Value>().await?)
1117 }
1118
1119 pub async fn get_orders(
1128 &self,
1129 params: Option<&crate::types::OpenOrderParams>,
1130 next_cursor: Option<&str>,
1131 ) -> Result<Vec<crate::types::OpenOrder>> {
1132 let signer = self
1133 .signer
1134 .as_ref()
1135 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1136 let api_creds = self
1137 .api_creds
1138 .as_ref()
1139 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1140
1141 let method = Method::GET;
1142 let endpoint = "/data/orders";
1143 let headers =
1144 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1145
1146 let query_params = match params {
1147 None => Vec::new(),
1148 Some(p) => p.to_query_params(),
1149 };
1150
1151 let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); let mut output = Vec::new();
1153
1154 while next_cursor != "LTE=" {
1155 let req = self
1157 .http_client
1158 .request(method.clone(), format!("{}{}", self.base_url, endpoint))
1159 .query(&query_params)
1160 .query(&[("next_cursor", &next_cursor)]);
1161
1162 let r = headers
1163 .clone()
1164 .into_iter()
1165 .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
1166
1167 let resp = r
1168 .send()
1169 .await
1170 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
1171 .json::<Value>()
1172 .await
1173 .map_err(|e| {
1174 PolyfillError::parse(format!("Failed to parse response: {}", e), None)
1175 })?;
1176
1177 let new_cursor = resp["next_cursor"]
1178 .as_str()
1179 .ok_or_else(|| {
1180 PolyfillError::parse("Failed to parse next cursor".to_string(), None)
1181 })?
1182 .to_owned();
1183
1184 next_cursor = new_cursor;
1185
1186 let results = resp["data"].clone();
1187 let orders =
1188 serde_json::from_value::<Vec<crate::types::OpenOrder>>(results).map_err(|e| {
1189 PolyfillError::parse(
1190 format!("Failed to parse data from order response: {}", e),
1191 None,
1192 )
1193 })?;
1194 output.extend(orders);
1195 }
1196
1197 Ok(output)
1198 }
1199
1200 pub async fn get_trades(
1211 &self,
1212 trade_params: Option<&crate::types::TradeParams>,
1213 next_cursor: Option<&str>,
1214 ) -> Result<Vec<Value>> {
1215 let signer = self
1216 .signer
1217 .as_ref()
1218 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1219 let api_creds = self
1220 .api_creds
1221 .as_ref()
1222 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1223
1224 let method = Method::GET;
1225 let endpoint = "/data/trades";
1226 let headers =
1227 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1228
1229 let query_params = match trade_params {
1230 None => Vec::new(),
1231 Some(p) => p.to_query_params(),
1232 };
1233
1234 let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); let mut output = Vec::new();
1236
1237 while next_cursor != "LTE=" {
1238 let req = self
1240 .http_client
1241 .request(method.clone(), format!("{}{}", self.base_url, endpoint))
1242 .query(&query_params)
1243 .query(&[("next_cursor", &next_cursor)]);
1244
1245 let r = headers
1246 .clone()
1247 .into_iter()
1248 .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v));
1249
1250 let resp = r
1251 .send()
1252 .await
1253 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?
1254 .json::<Value>()
1255 .await
1256 .map_err(|e| {
1257 PolyfillError::parse(format!("Failed to parse response: {}", e), None)
1258 })?;
1259
1260 let new_cursor = resp["next_cursor"]
1261 .as_str()
1262 .ok_or_else(|| {
1263 PolyfillError::parse("Failed to parse next cursor".to_string(), None)
1264 })?
1265 .to_owned();
1266
1267 next_cursor = new_cursor;
1268
1269 let results = resp["data"].clone();
1270 output.push(results);
1271 }
1272
1273 Ok(output)
1274 }
1275
1276 pub async fn get_balance_allowance(
1284 &self,
1285 params: Option<crate::types::BalanceAllowanceParams>,
1286 ) -> Result<Value> {
1287 let signer = self
1288 .signer
1289 .as_ref()
1290 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1291 let api_creds = self
1292 .api_creds
1293 .as_ref()
1294 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1295
1296 let mut params = params.unwrap_or_default();
1297 if params.signature_type.is_none() {
1298 params.set_signature_type(
1299 self.order_builder
1300 .as_ref()
1301 .expect("OrderBuilder not set")
1302 .get_sig_type(),
1303 );
1304 }
1305
1306 let query_params = params.to_query_params();
1307
1308 let method = Method::GET;
1309 let endpoint = "/balance-allowance";
1310 let headers =
1311 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1312
1313 let response = self
1314 .http_client
1315 .request(method, format!("{}{}", self.base_url, endpoint))
1316 .headers(
1317 headers
1318 .into_iter()
1319 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1320 .collect(),
1321 )
1322 .query(&query_params)
1323 .send()
1324 .await
1325 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1326
1327 response
1328 .json::<Value>()
1329 .await
1330 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1331 }
1332
1333 pub async fn get_notifications(&self) -> Result<Value> {
1342 let signer = self
1343 .signer
1344 .as_ref()
1345 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1346 let api_creds = self
1347 .api_creds
1348 .as_ref()
1349 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1350
1351 let method = Method::GET;
1352 let endpoint = "/notifications";
1353 let headers =
1354 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1355
1356 let response = self
1357 .http_client
1358 .request(method, format!("{}{}", self.base_url, endpoint))
1359 .headers(
1360 headers
1361 .into_iter()
1362 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1363 .collect(),
1364 )
1365 .query(&[(
1366 "signature_type",
1367 &self
1368 .order_builder
1369 .as_ref()
1370 .expect("OrderBuilder not set")
1371 .get_sig_type()
1372 .to_string(),
1373 )])
1374 .send()
1375 .await
1376 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1377
1378 response
1379 .json::<Value>()
1380 .await
1381 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1382 }
1383
1384 pub async fn get_midpoints(
1392 &self,
1393 token_ids: &[String],
1394 ) -> Result<std::collections::HashMap<String, Decimal>> {
1395 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1396 .iter()
1397 .map(|id| {
1398 let mut map = std::collections::HashMap::new();
1399 map.insert("token_id", id.clone());
1400 map
1401 })
1402 .collect();
1403
1404 let response = self
1405 .http_client
1406 .post(format!("{}/midpoints", self.base_url))
1407 .json(&request_data)
1408 .send()
1409 .await?;
1410
1411 if !response.status().is_success() {
1412 return Err(PolyfillError::api(
1413 response.status().as_u16(),
1414 "Failed to get batch midpoints",
1415 ));
1416 }
1417
1418 let midpoints: std::collections::HashMap<String, Decimal> = response.json().await?;
1419 Ok(midpoints)
1420 }
1421
1422 pub async fn get_prices(
1430 &self,
1431 book_params: &[crate::types::BookParams],
1432 ) -> Result<std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>>> {
1433 let request_data: Vec<std::collections::HashMap<&str, String>> = book_params
1434 .iter()
1435 .map(|params| {
1436 let mut map = std::collections::HashMap::new();
1437 map.insert("token_id", params.token_id.clone());
1438 map.insert("side", params.side.as_str().to_string());
1439 map
1440 })
1441 .collect();
1442
1443 let response = self
1444 .http_client
1445 .post(format!("{}/prices", self.base_url))
1446 .json(&request_data)
1447 .send()
1448 .await?;
1449
1450 if !response.status().is_success() {
1451 return Err(PolyfillError::api(
1452 response.status().as_u16(),
1453 "Failed to get batch prices",
1454 ));
1455 }
1456
1457 let prices: std::collections::HashMap<String, std::collections::HashMap<Side, Decimal>> =
1458 response.json().await?;
1459 Ok(prices)
1460 }
1461
1462 pub async fn get_order_books(&self, token_ids: &[String]) -> Result<Vec<OrderBookSummary>> {
1464 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1465 .iter()
1466 .map(|id| {
1467 let mut map = std::collections::HashMap::new();
1468 map.insert("token_id", id.clone());
1469 map
1470 })
1471 .collect();
1472
1473 let response = self
1474 .http_client
1475 .post(format!("{}/books", self.base_url))
1476 .json(&request_data)
1477 .send()
1478 .await
1479 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1480
1481 response
1482 .json::<Vec<OrderBookSummary>>()
1483 .await
1484 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1485 }
1486
1487 pub async fn get_order(&self, order_id: &str) -> Result<crate::types::OpenOrder> {
1489 let signer = self
1490 .signer
1491 .as_ref()
1492 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1493 let api_creds = self
1494 .api_creds
1495 .as_ref()
1496 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1497
1498 let method = Method::GET;
1499 let endpoint = &format!("/data/order/{}", order_id);
1500 let headers =
1501 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1502
1503 let response = self
1504 .http_client
1505 .request(method, format!("{}{}", self.base_url, endpoint))
1506 .headers(
1507 headers
1508 .into_iter()
1509 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1510 .collect(),
1511 )
1512 .send()
1513 .await
1514 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1515
1516 response
1517 .json::<crate::types::OpenOrder>()
1518 .await
1519 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1520 }
1521
1522 pub async fn get_last_trade_price(&self, token_id: &str) -> Result<Value> {
1524 let response = self
1525 .http_client
1526 .get(format!("{}/last-trade-price", self.base_url))
1527 .query(&[("token_id", token_id)])
1528 .send()
1529 .await
1530 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1531
1532 response
1533 .json::<Value>()
1534 .await
1535 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1536 }
1537
1538 pub async fn get_last_trade_prices(&self, token_ids: &[String]) -> Result<Value> {
1540 let request_data: Vec<std::collections::HashMap<&str, String>> = token_ids
1541 .iter()
1542 .map(|id| {
1543 let mut map = std::collections::HashMap::new();
1544 map.insert("token_id", id.clone());
1545 map
1546 })
1547 .collect();
1548
1549 let response = self
1550 .http_client
1551 .post(format!("{}/last-trades-prices", self.base_url))
1552 .json(&request_data)
1553 .send()
1554 .await
1555 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1556
1557 response
1558 .json::<Value>()
1559 .await
1560 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1561 }
1562
1563 pub async fn cancel_market_orders(
1565 &self,
1566 market: Option<&str>,
1567 asset_id: Option<&str>,
1568 ) -> Result<Value> {
1569 let signer = self
1570 .signer
1571 .as_ref()
1572 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1573 let api_creds = self
1574 .api_creds
1575 .as_ref()
1576 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1577
1578 let method = Method::DELETE;
1579 let endpoint = "/cancel-market-orders";
1580 let body = std::collections::HashMap::from([
1581 ("market", market.unwrap_or("")),
1582 ("asset_id", asset_id.unwrap_or("")),
1583 ]);
1584
1585 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
1586
1587 let response = self
1588 .http_client
1589 .request(method, format!("{}{}", self.base_url, endpoint))
1590 .headers(
1591 headers
1592 .into_iter()
1593 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1594 .collect(),
1595 )
1596 .json(&body)
1597 .send()
1598 .await
1599 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1600
1601 response
1602 .json::<Value>()
1603 .await
1604 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1605 }
1606
1607 pub async fn drop_notifications(&self, ids: &[String]) -> Result<Value> {
1609 let signer = self
1610 .signer
1611 .as_ref()
1612 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1613 let api_creds = self
1614 .api_creds
1615 .as_ref()
1616 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1617
1618 let method = Method::DELETE;
1619 let endpoint = "/notifications";
1620 let headers =
1621 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1622
1623 let response = self
1624 .http_client
1625 .request(method, format!("{}{}", self.base_url, endpoint))
1626 .headers(
1627 headers
1628 .into_iter()
1629 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1630 .collect(),
1631 )
1632 .query(&[("ids", ids.join(","))])
1633 .send()
1634 .await
1635 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1636
1637 response
1638 .json::<Value>()
1639 .await
1640 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1641 }
1642
1643 pub async fn update_balance_allowance(
1645 &self,
1646 params: Option<crate::types::BalanceAllowanceParams>,
1647 ) -> Result<Value> {
1648 let signer = self
1649 .signer
1650 .as_ref()
1651 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1652 let api_creds = self
1653 .api_creds
1654 .as_ref()
1655 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1656
1657 let mut params = params.unwrap_or_default();
1658 if params.signature_type.is_none() {
1659 params.set_signature_type(
1660 self.order_builder
1661 .as_ref()
1662 .expect("OrderBuilder not set")
1663 .get_sig_type(),
1664 );
1665 }
1666
1667 let query_params = params.to_query_params();
1668
1669 let method = Method::GET;
1670 let endpoint = "/balance-allowance/update";
1671 let headers =
1672 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1673
1674 let response = self
1675 .http_client
1676 .request(method, format!("{}{}", self.base_url, endpoint))
1677 .headers(
1678 headers
1679 .into_iter()
1680 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1681 .collect(),
1682 )
1683 .query(&query_params)
1684 .send()
1685 .await
1686 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1687
1688 response
1689 .json::<Value>()
1690 .await
1691 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1692 }
1693
1694 pub async fn is_order_scoring(&self, order_id: &str) -> Result<bool> {
1696 let signer = self
1697 .signer
1698 .as_ref()
1699 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1700 let api_creds = self
1701 .api_creds
1702 .as_ref()
1703 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1704
1705 let method = Method::GET;
1706 let endpoint = "/order-scoring";
1707 let headers =
1708 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1709
1710 let response = self
1711 .http_client
1712 .request(method, format!("{}{}", self.base_url, endpoint))
1713 .headers(
1714 headers
1715 .into_iter()
1716 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1717 .collect(),
1718 )
1719 .query(&[("order_id", order_id)])
1720 .send()
1721 .await
1722 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1723
1724 let result: Value = response
1725 .json()
1726 .await
1727 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
1728
1729 Ok(result["scoring"].as_bool().unwrap_or(false))
1730 }
1731
1732 pub async fn are_orders_scoring(
1734 &self,
1735 order_ids: &[&str],
1736 ) -> Result<std::collections::HashMap<String, bool>> {
1737 let signer = self
1738 .signer
1739 .as_ref()
1740 .ok_or_else(|| PolyfillError::config("Signer not configured"))?;
1741 let api_creds = self
1742 .api_creds
1743 .as_ref()
1744 .ok_or_else(|| PolyfillError::config("API credentials not configured"))?;
1745
1746 let method = Method::POST;
1747 let endpoint = "/orders-scoring";
1748 let headers = create_l2_headers(
1749 signer,
1750 api_creds,
1751 method.as_str(),
1752 endpoint,
1753 Some(order_ids),
1754 )?;
1755
1756 let response = self
1757 .http_client
1758 .request(method, format!("{}{}", self.base_url, endpoint))
1759 .headers(
1760 headers
1761 .into_iter()
1762 .map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap()))
1763 .collect(),
1764 )
1765 .json(order_ids)
1766 .send()
1767 .await
1768 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1769
1770 response
1771 .json::<std::collections::HashMap<String, bool>>()
1772 .await
1773 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1774 }
1775
1776 pub async fn create_rfq_request(
1782 &self,
1783 request: &crate::types::RfqCreateRequest,
1784 ) -> Result<crate::types::RfqCreateRequestResponse> {
1785 let signer = self
1786 .signer
1787 .as_ref()
1788 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1789 let api_creds = self
1790 .api_creds
1791 .as_ref()
1792 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1793
1794 let method = Method::POST;
1795 let endpoint = "/rfq/request";
1796 let headers =
1797 create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(request))?;
1798
1799 let response = self
1800 .create_request_with_headers(method, endpoint, headers.into_iter())
1801 .json(request)
1802 .send()
1803 .await
1804 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1805
1806 if !response.status().is_success() {
1807 return Err(PolyfillError::api(
1808 response.status().as_u16(),
1809 "Failed to create RFQ request",
1810 ));
1811 }
1812
1813 response
1814 .json()
1815 .await
1816 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1817 }
1818
1819 pub async fn cancel_rfq_request(&self, request_id: &str) -> Result<()> {
1821 let signer = self
1822 .signer
1823 .as_ref()
1824 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1825 let api_creds = self
1826 .api_creds
1827 .as_ref()
1828 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1829
1830 let method = Method::DELETE;
1831 let endpoint = "/rfq/request";
1832 let body = crate::types::RfqCancelRequest {
1833 request_id: request_id.to_string(),
1834 };
1835 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
1836
1837 let response = self
1838 .create_request_with_headers(method, endpoint, headers.into_iter())
1839 .json(&body)
1840 .send()
1841 .await
1842 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1843
1844 if !response.status().is_success() {
1845 return Err(PolyfillError::api(
1846 response.status().as_u16(),
1847 "Failed to cancel RFQ request",
1848 ));
1849 }
1850
1851 Ok(())
1852 }
1853
1854 pub async fn get_rfq_requests(
1856 &self,
1857 params: Option<&crate::types::RfqRequestsParams>,
1858 ) -> Result<crate::types::RfqListResponse<crate::types::RfqRequestData>> {
1859 let signer = self
1860 .signer
1861 .as_ref()
1862 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1863 let api_creds = self
1864 .api_creds
1865 .as_ref()
1866 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1867
1868 let method = Method::GET;
1869 let endpoint = "/rfq/data/requests";
1870 let headers =
1871 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1872
1873 let query_params = params.cloned().unwrap_or_default().to_query_params();
1874
1875 let response = self
1876 .create_request_with_headers(method, endpoint, headers.into_iter())
1877 .query(&query_params)
1878 .send()
1879 .await
1880 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1881
1882 if !response.status().is_success() {
1883 return Err(PolyfillError::api(
1884 response.status().as_u16(),
1885 "Failed to get RFQ requests",
1886 ));
1887 }
1888
1889 response
1890 .json()
1891 .await
1892 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1893 }
1894
1895 pub async fn create_rfq_quote(
1897 &self,
1898 quote: &crate::types::RfqCreateQuote,
1899 ) -> Result<crate::types::RfqCreateQuoteResponse> {
1900 let signer = self
1901 .signer
1902 .as_ref()
1903 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1904 let api_creds = self
1905 .api_creds
1906 .as_ref()
1907 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1908
1909 let method = Method::POST;
1910 let endpoint = "/rfq/quote";
1911 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(quote))?;
1912
1913 let response = self
1914 .create_request_with_headers(method, endpoint, headers.into_iter())
1915 .json(quote)
1916 .send()
1917 .await
1918 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1919
1920 if !response.status().is_success() {
1921 return Err(PolyfillError::api(
1922 response.status().as_u16(),
1923 "Failed to create RFQ quote",
1924 ));
1925 }
1926
1927 response
1928 .json()
1929 .await
1930 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
1931 }
1932
1933 pub async fn cancel_rfq_quote(&self, quote_id: &str) -> Result<()> {
1935 let signer = self
1936 .signer
1937 .as_ref()
1938 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1939 let api_creds = self
1940 .api_creds
1941 .as_ref()
1942 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1943
1944 let method = Method::DELETE;
1945 let endpoint = "/rfq/quote";
1946 let body = crate::types::RfqCancelQuote {
1947 quote_id: quote_id.to_string(),
1948 };
1949 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?;
1950
1951 let response = self
1952 .create_request_with_headers(method, endpoint, headers.into_iter())
1953 .json(&body)
1954 .send()
1955 .await
1956 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1957
1958 if !response.status().is_success() {
1959 return Err(PolyfillError::api(
1960 response.status().as_u16(),
1961 "Failed to cancel RFQ quote",
1962 ));
1963 }
1964
1965 Ok(())
1966 }
1967
1968 pub async fn get_rfq_requester_quotes(
1970 &self,
1971 params: Option<&crate::types::RfqQuotesParams>,
1972 ) -> Result<crate::types::RfqListResponse<crate::types::RfqQuoteData>> {
1973 let signer = self
1974 .signer
1975 .as_ref()
1976 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
1977 let api_creds = self
1978 .api_creds
1979 .as_ref()
1980 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
1981
1982 let method = Method::GET;
1983 let endpoint = "/rfq/data/requester/quotes";
1984 let headers =
1985 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
1986
1987 let query_params = params.cloned().unwrap_or_default().to_query_params();
1988
1989 let response = self
1990 .create_request_with_headers(method, endpoint, headers.into_iter())
1991 .query(&query_params)
1992 .send()
1993 .await
1994 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
1995
1996 if !response.status().is_success() {
1997 return Err(PolyfillError::api(
1998 response.status().as_u16(),
1999 "Failed to get RFQ requester quotes",
2000 ));
2001 }
2002
2003 response
2004 .json()
2005 .await
2006 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2007 }
2008
2009 pub async fn get_rfq_quoter_quotes(
2011 &self,
2012 params: Option<&crate::types::RfqQuotesParams>,
2013 ) -> Result<crate::types::RfqListResponse<crate::types::RfqQuoteData>> {
2014 let signer = self
2015 .signer
2016 .as_ref()
2017 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
2018 let api_creds = self
2019 .api_creds
2020 .as_ref()
2021 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
2022
2023 let method = Method::GET;
2024 let endpoint = "/rfq/data/quoter/quotes";
2025 let headers =
2026 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
2027
2028 let query_params = params.cloned().unwrap_or_default().to_query_params();
2029
2030 let response = self
2031 .create_request_with_headers(method, endpoint, headers.into_iter())
2032 .query(&query_params)
2033 .send()
2034 .await
2035 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2036
2037 if !response.status().is_success() {
2038 return Err(PolyfillError::api(
2039 response.status().as_u16(),
2040 "Failed to get RFQ quoter quotes",
2041 ));
2042 }
2043
2044 response
2045 .json()
2046 .await
2047 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2048 }
2049
2050 pub async fn get_rfq_best_quote(&self, request_id: &str) -> Result<crate::types::RfqQuoteData> {
2052 let signer = self
2053 .signer
2054 .as_ref()
2055 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
2056 let api_creds = self
2057 .api_creds
2058 .as_ref()
2059 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
2060
2061 let method = Method::GET;
2062 let endpoint = "/rfq/data/best-quote";
2063 let headers =
2064 create_l2_headers::<Value>(signer, api_creds, method.as_str(), endpoint, None)?;
2065
2066 let response = self
2067 .create_request_with_headers(method, endpoint, headers.into_iter())
2068 .query(&[("requestId", request_id)])
2069 .send()
2070 .await
2071 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2072
2073 if !response.status().is_success() {
2074 return Err(PolyfillError::api(
2075 response.status().as_u16(),
2076 "Failed to get RFQ best quote",
2077 ));
2078 }
2079
2080 response
2081 .json()
2082 .await
2083 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2084 }
2085
2086 pub async fn accept_rfq_quote(
2088 &self,
2089 body: &crate::types::RfqOrderExecutionRequest,
2090 ) -> Result<()> {
2091 let signer = self
2092 .signer
2093 .as_ref()
2094 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
2095 let api_creds = self
2096 .api_creds
2097 .as_ref()
2098 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
2099
2100 let method = Method::POST;
2101 let endpoint = "/rfq/request/accept";
2102 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(body))?;
2103
2104 let response = self
2105 .create_request_with_headers(method, endpoint, headers.into_iter())
2106 .json(body)
2107 .send()
2108 .await
2109 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2110
2111 if !response.status().is_success() {
2112 return Err(PolyfillError::api(
2113 response.status().as_u16(),
2114 "Failed to accept RFQ quote",
2115 ));
2116 }
2117
2118 Ok(())
2119 }
2120
2121 pub async fn approve_rfq_order(
2123 &self,
2124 body: &crate::types::RfqOrderExecutionRequest,
2125 ) -> Result<crate::types::RfqApproveOrderResponse> {
2126 let signer = self
2127 .signer
2128 .as_ref()
2129 .ok_or_else(|| PolyfillError::auth("Signer not set"))?;
2130 let api_creds = self
2131 .api_creds
2132 .as_ref()
2133 .ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
2134
2135 let method = Method::POST;
2136 let endpoint = "/rfq/quote/approve";
2137 let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(body))?;
2138
2139 let response = self
2140 .create_request_with_headers(method, endpoint, headers.into_iter())
2141 .json(body)
2142 .send()
2143 .await
2144 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2145
2146 if !response.status().is_success() {
2147 return Err(PolyfillError::api(
2148 response.status().as_u16(),
2149 "Failed to approve RFQ order",
2150 ));
2151 }
2152
2153 response
2154 .json()
2155 .await
2156 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2157 }
2158
2159 pub async fn get_sampling_markets(
2161 &self,
2162 next_cursor: Option<&str>,
2163 ) -> Result<crate::types::MarketsResponse> {
2164 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
2167 .http_client
2168 .get(format!("{}/sampling-markets", self.base_url))
2169 .query(&[("next_cursor", next_cursor)])
2170 .send()
2171 .await
2172 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2173
2174 response
2175 .json::<crate::types::MarketsResponse>()
2176 .await
2177 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2178 }
2179
2180 pub async fn get_sampling_simplified_markets(
2182 &self,
2183 next_cursor: Option<&str>,
2184 ) -> Result<crate::types::SimplifiedMarketsResponse> {
2185 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
2188 .http_client
2189 .get(format!("{}/sampling-simplified-markets", self.base_url))
2190 .query(&[("next_cursor", next_cursor)])
2191 .send()
2192 .await
2193 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2194
2195 response
2196 .json::<crate::types::SimplifiedMarketsResponse>()
2197 .await
2198 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2199 }
2200
2201 pub async fn get_markets(
2203 &self,
2204 next_cursor: Option<&str>,
2205 ) -> Result<crate::types::MarketsResponse> {
2206 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
2209 .http_client
2210 .get(format!("{}/markets", self.base_url))
2211 .query(&[("next_cursor", next_cursor)])
2212 .send()
2213 .await
2214 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2215
2216 response
2217 .json::<crate::types::MarketsResponse>()
2218 .await
2219 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2220 }
2221
2222 pub async fn get_simplified_markets(
2224 &self,
2225 next_cursor: Option<&str>,
2226 ) -> Result<crate::types::SimplifiedMarketsResponse> {
2227 let next_cursor = next_cursor.unwrap_or("MA=="); let response = self
2230 .http_client
2231 .get(format!("{}/simplified-markets", self.base_url))
2232 .query(&[("next_cursor", next_cursor)])
2233 .send()
2234 .await
2235 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2236
2237 response
2238 .json::<crate::types::SimplifiedMarketsResponse>()
2239 .await
2240 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2241 }
2242
2243 pub async fn get_market(&self, condition_id: &str) -> Result<crate::types::Market> {
2245 let response = self
2246 .http_client
2247 .get(format!("{}/markets/{}", self.base_url, condition_id))
2248 .send()
2249 .await
2250 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2251
2252 response
2253 .json::<crate::types::Market>()
2254 .await
2255 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2256 }
2257
2258 pub async fn get_market_trades_events(&self, condition_id: &str) -> Result<Value> {
2260 let response = self
2261 .http_client
2262 .get(format!(
2263 "{}/live-activity/events/{}",
2264 self.base_url, condition_id
2265 ))
2266 .send()
2267 .await
2268 .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
2269
2270 response
2271 .json::<Value>()
2272 .await
2273 .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
2274 }
2275}
2276
2277pub use crate::types::{
2279 ExtraOrderArgs, Market, MarketOrderArgs, MarketsResponse, MidpointResponse, NegRiskResponse,
2280 OrderBookSummary, OrderSummary, PriceResponse, PricesHistoryInterval, PricesHistoryResponse,
2281 Rewards, SpreadResponse, TickSizeResponse, Token,
2282};
2283
2284#[derive(Debug, Default)]
2286pub struct CreateOrderOptions {
2287 pub tick_size: Option<Decimal>,
2288 pub neg_risk: Option<bool>,
2289}
2290
2291pub type PolyfillClient = ClobClient;
2293
2294#[cfg(test)]
2295mod tests {
2296 use super::{ClobClient, OrderArgs as ClientOrderArgs};
2297 use crate::types::{
2298 PricesHistoryInterval, RfqCreateQuote, RfqCreateRequest, RfqOrderExecutionRequest,
2299 RfqQuotesParams, RfqRequestsParams, Side,
2300 };
2301 use crate::{ApiCredentials, PolyfillError};
2302 use mockito::{Matcher, Server};
2303 use rust_decimal::Decimal;
2304 use serde_json::json;
2305 use std::str::FromStr;
2306 use tokio;
2307
2308 fn create_test_client(base_url: &str) -> ClobClient {
2309 ClobClient::new(base_url)
2310 }
2311
2312 fn create_test_client_with_auth(base_url: &str) -> ClobClient {
2313 ClobClient::with_l1_headers(
2314 base_url,
2315 "0x1234567890123456789012345678901234567890123456789012345678901234",
2316 137,
2317 )
2318 }
2319
2320 fn create_test_client_with_l2_auth(base_url: &str) -> ClobClient {
2321 let api_creds = ApiCredentials {
2322 api_key: "test_key".to_string(),
2323 secret: "dGVzdF9zZWNyZXRfa2V5XzEyMzQ1".to_string(),
2325 passphrase: "test_passphrase".to_string(),
2326 };
2327
2328 ClobClient::with_l2_headers(
2329 base_url,
2330 "0x1234567890123456789012345678901234567890123456789012345678901234",
2331 137,
2332 api_creds,
2333 )
2334 }
2335
2336 #[tokio::test(flavor = "multi_thread")]
2337 async fn test_client_creation() {
2338 let client = create_test_client("https://test.example.com");
2339 assert_eq!(client.base_url, "https://test.example.com");
2340 assert!(client.signer.is_none());
2341 assert!(client.api_creds.is_none());
2342 }
2343
2344 #[tokio::test(flavor = "multi_thread")]
2345 async fn test_client_with_l1_headers() {
2346 let client = create_test_client_with_auth("https://test.example.com");
2347 assert_eq!(client.base_url, "https://test.example.com");
2348 assert!(client.signer.is_some());
2349 assert_eq!(client.chain_id, 137);
2350 }
2351
2352 #[tokio::test(flavor = "multi_thread")]
2353 async fn test_client_with_l2_headers() {
2354 let api_creds = ApiCredentials {
2355 api_key: "test_key".to_string(),
2356 secret: "test_secret".to_string(),
2357 passphrase: "test_passphrase".to_string(),
2358 };
2359
2360 let client = ClobClient::with_l2_headers(
2361 "https://test.example.com",
2362 "0x1234567890123456789012345678901234567890123456789012345678901234",
2363 137,
2364 api_creds.clone(),
2365 );
2366
2367 assert_eq!(client.base_url, "https://test.example.com");
2368 assert!(client.signer.is_some());
2369 assert!(client.api_creds.is_some());
2370 assert_eq!(client.chain_id, 137);
2371 }
2372
2373 #[tokio::test(flavor = "multi_thread")]
2374 async fn test_set_api_creds() {
2375 let mut client = create_test_client("https://test.example.com");
2376 assert!(client.api_creds.is_none());
2377
2378 let api_creds = ApiCredentials {
2379 api_key: "test_key".to_string(),
2380 secret: "test_secret".to_string(),
2381 passphrase: "test_passphrase".to_string(),
2382 };
2383
2384 client.set_api_creds(api_creds.clone());
2385 assert!(client.api_creds.is_some());
2386 assert_eq!(client.api_creds.unwrap().api_key, "test_key");
2387 }
2388
2389 #[tokio::test(flavor = "multi_thread")]
2390 async fn test_get_sampling_markets_success() {
2391 let mut server = Server::new_async().await;
2392 let mock_response = r#"{
2393 "limit": 10,
2394 "count": 2,
2395 "next_cursor": null,
2396 "data": [
2397 {
2398 "condition_id": "0x123",
2399 "tokens": [
2400 {"token_id": "0x456", "outcome": "Yes", "price": 0.5, "winner": false},
2401 {"token_id": "0x789", "outcome": "No", "price": 0.5, "winner": false}
2402 ],
2403 "rewards": {
2404 "rates": null,
2405 "min_size": 1.0,
2406 "max_spread": 0.1,
2407 "event_start_date": null,
2408 "event_end_date": null,
2409 "in_game_multiplier": null,
2410 "reward_epoch": null
2411 },
2412 "min_incentive_size": null,
2413 "max_incentive_spread": null,
2414 "active": true,
2415 "closed": false,
2416 "question_id": "0x123",
2417 "minimum_order_size": 1.0,
2418 "minimum_tick_size": 0.01,
2419 "description": "Test market",
2420 "category": "test",
2421 "end_date_iso": null,
2422 "game_start_time": null,
2423 "question": "Will this test pass?",
2424 "market_slug": "test-market",
2425 "seconds_delay": 0,
2426 "icon": "",
2427 "fpmm": ""
2428 }
2429 ]
2430 }"#;
2431
2432 let mock = server
2433 .mock("GET", "/sampling-markets")
2434 .match_query(Matcher::UrlEncoded("next_cursor".into(), "MA==".into()))
2435 .with_status(200)
2436 .with_header("content-type", "application/json")
2437 .with_body(mock_response)
2438 .create_async()
2439 .await;
2440
2441 let client = create_test_client(&server.url());
2442 let result = client.get_sampling_markets(None).await;
2443
2444 mock.assert_async().await;
2445 assert!(result.is_ok());
2446 let markets = result.unwrap();
2447 assert_eq!(markets.data.len(), 1);
2448 assert_eq!(markets.data[0].question, "Will this test pass?");
2449 }
2450
2451 #[tokio::test(flavor = "multi_thread")]
2452 async fn test_get_sampling_markets_with_cursor() {
2453 let mut server = Server::new_async().await;
2454 let mock_response = r#"{
2455 "limit": 5,
2456 "count": 0,
2457 "next_cursor": null,
2458 "data": []
2459 }"#;
2460
2461 let mock = server
2462 .mock("GET", "/sampling-markets")
2463 .match_query(Matcher::AllOf(vec![Matcher::UrlEncoded(
2464 "next_cursor".into(),
2465 "test_cursor".into(),
2466 )]))
2467 .with_status(200)
2468 .with_header("content-type", "application/json")
2469 .with_body(mock_response)
2470 .create_async()
2471 .await;
2472
2473 let client = create_test_client(&server.url());
2474 let result = client.get_sampling_markets(Some("test_cursor")).await;
2475
2476 mock.assert_async().await;
2477 assert!(result.is_ok());
2478 let markets = result.unwrap();
2479 assert_eq!(markets.data.len(), 0);
2480 }
2481
2482 #[tokio::test(flavor = "multi_thread")]
2483 async fn test_get_order_book_success() {
2484 let mut server = Server::new_async().await;
2485 let mock_response = r#"{
2486 "market": "0x123",
2487 "asset_id": "0x123",
2488 "hash": "0xabc123",
2489 "timestamp": "1234567890",
2490 "bids": [
2491 {"price": "0.75", "size": "100.0"}
2492 ],
2493 "asks": [
2494 {"price": "0.76", "size": "50.0"}
2495 ],
2496 "min_order_size": "1",
2497 "neg_risk": false,
2498 "tick_size": "0.01",
2499 "last_trade_price": "0.755"
2500 }"#;
2501
2502 let mock = server
2503 .mock("GET", "/book")
2504 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2505 .with_status(200)
2506 .with_header("content-type", "application/json")
2507 .with_body(mock_response)
2508 .create_async()
2509 .await;
2510
2511 let client = create_test_client(&server.url());
2512 let result = client.get_order_book("0x123").await;
2513
2514 mock.assert_async().await;
2515 assert!(result.is_ok());
2516 let book = result.unwrap();
2517 assert_eq!(book.market, "0x123");
2518 assert_eq!(book.bids.len(), 1);
2519 assert_eq!(book.asks.len(), 1);
2520 assert_eq!(book.min_order_size, Decimal::from_str("1").unwrap());
2521 assert!(!book.neg_risk);
2522 assert_eq!(book.tick_size, Decimal::from_str("0.01").unwrap());
2523 assert_eq!(
2524 book.last_trade_price,
2525 Some(Decimal::from_str("0.755").unwrap())
2526 );
2527 }
2528
2529 #[tokio::test(flavor = "multi_thread")]
2530 async fn test_get_midpoint_success() {
2531 let mut server = Server::new_async().await;
2532 let mock_response = r#"{
2533 "mid": "0.755"
2534 }"#;
2535
2536 let mock = server
2537 .mock("GET", "/midpoint")
2538 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2539 .with_status(200)
2540 .with_header("content-type", "application/json")
2541 .with_body(mock_response)
2542 .create_async()
2543 .await;
2544
2545 let client = create_test_client(&server.url());
2546 let result = client.get_midpoint("0x123").await;
2547
2548 mock.assert_async().await;
2549 assert!(result.is_ok());
2550 let response = result.unwrap();
2551 assert_eq!(response.mid, Decimal::from_str("0.755").unwrap());
2552 }
2553
2554 #[tokio::test(flavor = "multi_thread")]
2555 async fn test_get_spread_success() {
2556 let mut server = Server::new_async().await;
2557 let mock_response = r#"{
2558 "spread": "0.01"
2559 }"#;
2560
2561 let mock = server
2562 .mock("GET", "/spread")
2563 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2564 .with_status(200)
2565 .with_header("content-type", "application/json")
2566 .with_body(mock_response)
2567 .create_async()
2568 .await;
2569
2570 let client = create_test_client(&server.url());
2571 let result = client.get_spread("0x123").await;
2572
2573 mock.assert_async().await;
2574 assert!(result.is_ok());
2575 let response = result.unwrap();
2576 assert_eq!(response.spread, Decimal::from_str("0.01").unwrap());
2577 }
2578
2579 #[tokio::test(flavor = "multi_thread")]
2580 async fn test_get_price_success() {
2581 let mut server = Server::new_async().await;
2582 let mock_response = r#"{
2583 "price": "0.76"
2584 }"#;
2585
2586 let mock = server
2587 .mock("GET", "/price")
2588 .match_query(Matcher::AllOf(vec![
2589 Matcher::UrlEncoded("token_id".into(), "0x123".into()),
2590 Matcher::UrlEncoded("side".into(), "BUY".into()),
2591 ]))
2592 .with_status(200)
2593 .with_header("content-type", "application/json")
2594 .with_body(mock_response)
2595 .create_async()
2596 .await;
2597
2598 let client = create_test_client(&server.url());
2599 let result = client.get_price("0x123", Side::BUY).await;
2600
2601 mock.assert_async().await;
2602 assert!(result.is_ok());
2603 let response = result.unwrap();
2604 assert_eq!(response.price, Decimal::from_str("0.76").unwrap());
2605 }
2606
2607 #[tokio::test(flavor = "multi_thread")]
2608 async fn test_get_prices_history_interval_rejects_hex_condition_id() {
2609 let client = create_test_client("https://test.example.com");
2610 let result = client
2611 .get_prices_history_interval("0xdeadbeef", PricesHistoryInterval::OneDay, None)
2612 .await;
2613 assert!(matches!(result, Err(PolyfillError::Validation { .. })));
2614 }
2615
2616 #[tokio::test(flavor = "multi_thread")]
2617 async fn test_get_prices_history_interval_success() {
2618 let mut server = Server::new_async().await;
2619 let mock_response = r#"{"history":[{"t":1}]}"#;
2620
2621 let mock = server
2622 .mock("GET", "/prices-history")
2623 .match_query(Matcher::AllOf(vec![
2624 Matcher::UrlEncoded("market".into(), "12345".into()),
2625 Matcher::UrlEncoded("interval".into(), "1d".into()),
2626 Matcher::UrlEncoded("fidelity".into(), "5".into()),
2627 ]))
2628 .with_status(200)
2629 .with_header("content-type", "application/json")
2630 .with_body(mock_response)
2631 .create_async()
2632 .await;
2633
2634 let client = create_test_client(&server.url());
2635 let response = client
2636 .get_prices_history_interval("12345", PricesHistoryInterval::OneDay, Some(5))
2637 .await
2638 .unwrap();
2639
2640 mock.assert_async().await;
2641 assert_eq!(response.history.len(), 1);
2642 }
2643
2644 #[tokio::test(flavor = "multi_thread")]
2645 async fn test_get_tick_size_success() {
2646 let mut server = Server::new_async().await;
2647 let mock_response = r#"{
2648 "minimum_tick_size": "0.01"
2649 }"#;
2650
2651 let mock = server
2652 .mock("GET", "/tick-size")
2653 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2654 .with_status(200)
2655 .with_header("content-type", "application/json")
2656 .with_body(mock_response)
2657 .create_async()
2658 .await;
2659
2660 let client = create_test_client(&server.url());
2661 let result = client.get_tick_size("0x123").await;
2662
2663 mock.assert_async().await;
2664 assert!(result.is_ok());
2665 let tick_size = result.unwrap();
2666 assert_eq!(tick_size, Decimal::from_str("0.01").unwrap());
2667 }
2668
2669 #[tokio::test(flavor = "multi_thread")]
2670 async fn test_get_neg_risk_success() {
2671 let mut server = Server::new_async().await;
2672 let mock_response = r#"{
2673 "neg_risk": false
2674 }"#;
2675
2676 let mock = server
2677 .mock("GET", "/neg-risk")
2678 .match_query(Matcher::UrlEncoded("token_id".into(), "0x123".into()))
2679 .with_status(200)
2680 .with_header("content-type", "application/json")
2681 .with_body(mock_response)
2682 .create_async()
2683 .await;
2684
2685 let client = create_test_client(&server.url());
2686 let result = client.get_neg_risk("0x123").await;
2687
2688 mock.assert_async().await;
2689 assert!(result.is_ok());
2690 let neg_risk = result.unwrap();
2691 assert!(!neg_risk);
2692 }
2693
2694 #[tokio::test(flavor = "multi_thread")]
2695 async fn test_api_error_handling() {
2696 let mut server = Server::new_async().await;
2697
2698 let mock = server
2699 .mock("GET", "/book")
2700 .match_query(Matcher::UrlEncoded(
2701 "token_id".into(),
2702 "invalid_token".into(),
2703 ))
2704 .with_status(404)
2705 .with_header("content-type", "application/json")
2706 .with_body(r#"{"error": "Market not found"}"#)
2707 .create_async()
2708 .await;
2709
2710 let client = create_test_client(&server.url());
2711 let result = client.get_order_book("invalid_token").await;
2712
2713 mock.assert_async().await;
2714 assert!(result.is_err());
2715
2716 let error = result.unwrap_err();
2717 assert!(
2719 matches!(error, PolyfillError::Network { .. })
2720 || matches!(error, PolyfillError::Api { .. })
2721 );
2722 }
2723
2724 #[tokio::test(flavor = "multi_thread")]
2725 async fn test_network_error_handling() {
2726 let client = create_test_client("http://invalid-host-that-does-not-exist.com");
2728 let result = client.get_order_book("0x123").await;
2729
2730 assert!(result.is_err());
2731 let error = result.unwrap_err();
2732 assert!(matches!(error, PolyfillError::Network { .. }));
2733 }
2734
2735 #[test]
2736 fn test_client_url_validation() {
2737 let client = create_test_client("https://test.example.com");
2738 assert_eq!(client.base_url, "https://test.example.com");
2739
2740 let client2 = create_test_client("http://localhost:8080");
2741 assert_eq!(client2.base_url, "http://localhost:8080");
2742 }
2743
2744 #[tokio::test(flavor = "multi_thread")]
2745 async fn test_get_midpoints_batch() {
2746 let mut server = Server::new_async().await;
2747 let mock_response = r#"{
2748 "0x123": "0.755",
2749 "0x456": "0.623"
2750 }"#;
2751
2752 let mock = server
2753 .mock("POST", "/midpoints")
2754 .with_header("content-type", "application/json")
2755 .with_status(200)
2756 .with_header("content-type", "application/json")
2757 .with_body(mock_response)
2758 .create_async()
2759 .await;
2760
2761 let client = create_test_client(&server.url());
2762 let token_ids = vec!["0x123".to_string(), "0x456".to_string()];
2763 let result = client.get_midpoints(&token_ids).await;
2764
2765 mock.assert_async().await;
2766 assert!(result.is_ok());
2767 let midpoints = result.unwrap();
2768 assert_eq!(midpoints.len(), 2);
2769 assert_eq!(
2770 midpoints.get("0x123").unwrap(),
2771 &Decimal::from_str("0.755").unwrap()
2772 );
2773 assert_eq!(
2774 midpoints.get("0x456").unwrap(),
2775 &Decimal::from_str("0.623").unwrap()
2776 );
2777 }
2778
2779 #[test]
2780 fn test_client_configuration() {
2781 let client = create_test_client("https://test.example.com");
2782
2783 assert!(client.signer.is_none());
2785 assert!(client.api_creds.is_none());
2786
2787 let auth_client = create_test_client_with_auth("https://test.example.com");
2789 assert!(auth_client.signer.is_some());
2790 assert_eq!(auth_client.chain_id, 137);
2791 }
2792
2793 #[tokio::test(flavor = "multi_thread")]
2794 async fn test_get_ok() {
2795 let mut server = Server::new_async().await;
2796 let mock_response = r#"{"status": "ok"}"#;
2797
2798 let mock = server
2799 .mock("GET", "/ok")
2800 .with_header("content-type", "application/json")
2801 .with_status(200)
2802 .with_body(mock_response)
2803 .create_async()
2804 .await;
2805
2806 let client = create_test_client(&server.url());
2807 let result = client.get_ok().await;
2808
2809 mock.assert_async().await;
2810 assert!(result);
2811 }
2812
2813 #[tokio::test(flavor = "multi_thread")]
2814 async fn test_get_prices_batch() {
2815 let mut server = Server::new_async().await;
2816 let mock_response = r#"{
2817 "0x123": {
2818 "BUY": "0.755",
2819 "SELL": "0.745"
2820 },
2821 "0x456": {
2822 "BUY": "0.623",
2823 "SELL": "0.613"
2824 }
2825 }"#;
2826
2827 let mock = server
2828 .mock("POST", "/prices")
2829 .with_header("content-type", "application/json")
2830 .with_status(200)
2831 .with_body(mock_response)
2832 .create_async()
2833 .await;
2834
2835 let client = create_test_client(&server.url());
2836 let book_params = vec![
2837 crate::types::BookParams {
2838 token_id: "0x123".to_string(),
2839 side: Side::BUY,
2840 },
2841 crate::types::BookParams {
2842 token_id: "0x456".to_string(),
2843 side: Side::SELL,
2844 },
2845 ];
2846 let result = client.get_prices(&book_params).await;
2847
2848 mock.assert_async().await;
2849 assert!(result.is_ok());
2850 let prices = result.unwrap();
2851 assert_eq!(prices.len(), 2);
2852 assert!(prices.contains_key("0x123"));
2853 assert!(prices.contains_key("0x456"));
2854 }
2855
2856 #[tokio::test(flavor = "multi_thread")]
2857 async fn test_get_server_time() {
2858 let mut server = Server::new_async().await;
2859 let mock_response = "1234567890"; let mock = server
2862 .mock("GET", "/time")
2863 .with_status(200)
2864 .with_body(mock_response)
2865 .create_async()
2866 .await;
2867
2868 let client = create_test_client(&server.url());
2869 let result = client.get_server_time().await;
2870
2871 mock.assert_async().await;
2872 assert!(result.is_ok());
2873 let timestamp = result.unwrap();
2874 assert_eq!(timestamp, 1234567890);
2875 }
2876
2877 #[tokio::test(flavor = "multi_thread")]
2878 async fn test_create_or_derive_api_key() {
2879 let mut server = Server::new_async().await;
2880 let mock_response = r#"{
2881 "apiKey": "test-api-key-123",
2882 "secret": "test-secret-456",
2883 "passphrase": "test-passphrase"
2884 }"#;
2885
2886 let create_mock = server
2888 .mock("POST", "/auth/api-key")
2889 .with_header("content-type", "application/json")
2890 .with_status(200)
2891 .with_body(mock_response)
2892 .create_async()
2893 .await;
2894
2895 let client = create_test_client_with_auth(&server.url());
2896 let result = client.create_or_derive_api_key(None).await;
2897
2898 create_mock.assert_async().await;
2899 assert!(result.is_ok());
2900 let api_creds = result.unwrap();
2901 assert_eq!(api_creds.api_key, "test-api-key-123");
2902 }
2903
2904 #[tokio::test(flavor = "multi_thread")]
2905 async fn test_create_or_derive_api_key_falls_back_on_api_error() {
2906 let mut server = Server::new_async().await;
2907
2908 let create_mock = server
2910 .mock("POST", "/auth/api-key")
2911 .with_status(400)
2912 .with_header("content-type", "application/json")
2913 .with_body(r#"{"error":"key exists"}"#)
2914 .create_async()
2915 .await;
2916
2917 let derive_mock = server
2918 .mock("GET", "/auth/derive-api-key")
2919 .with_status(200)
2920 .with_header("content-type", "application/json")
2921 .with_body(
2922 r#"{"apiKey":"derived-api-key","secret":"derived-secret","passphrase":"derived-pass"}"#,
2923 )
2924 .create_async()
2925 .await;
2926
2927 let client = create_test_client_with_auth(&server.url());
2928 let result = client.create_or_derive_api_key(None).await;
2929
2930 create_mock.assert_async().await;
2931 derive_mock.assert_async().await;
2932 assert!(result.is_ok());
2933 assert_eq!(result.unwrap().api_key, "derived-api-key");
2934 }
2935
2936 #[tokio::test(flavor = "multi_thread")]
2937 async fn test_create_or_derive_api_key_does_not_fallback_on_non_api_error() {
2938 let mut server = Server::new_async().await;
2939
2940 let create_mock = server
2942 .mock("POST", "/auth/api-key")
2943 .with_status(200)
2944 .with_header("content-type", "application/json")
2945 .with_body("not-json")
2946 .create_async()
2947 .await;
2948
2949 let derive_mock = server
2951 .mock("GET", "/auth/derive-api-key")
2952 .with_status(200)
2953 .with_header("content-type", "application/json")
2954 .with_body(
2955 r#"{"apiKey":"derived-api-key","secret":"derived-secret","passphrase":"derived-pass"}"#,
2956 )
2957 .expect(0)
2958 .create_async()
2959 .await;
2960
2961 let client = create_test_client_with_auth(&server.url());
2962 let result = client.create_or_derive_api_key(None).await;
2963
2964 create_mock.assert_async().await;
2965 derive_mock.assert_async().await;
2966 assert!(result.is_err());
2967 }
2968 #[tokio::test(flavor = "multi_thread")]
2969 async fn test_get_order_books_batch() {
2970 let mut server = Server::new_async().await;
2971 let mock_response = r#"[
2972 {
2973 "market": "0x123",
2974 "asset_id": "0x123",
2975 "hash": "test-hash",
2976 "timestamp": "1234567890",
2977 "bids": [{"price": "0.75", "size": "100.0"}],
2978 "asks": [{"price": "0.76", "size": "50.0"}],
2979 "min_order_size": "1",
2980 "neg_risk": false,
2981 "tick_size": "0.01",
2982 "last_trade_price": null
2983 }
2984 ]"#;
2985
2986 let mock = server
2987 .mock("POST", "/books")
2988 .with_header("content-type", "application/json")
2989 .with_status(200)
2990 .with_body(mock_response)
2991 .create_async()
2992 .await;
2993
2994 let client = create_test_client(&server.url());
2995 let token_ids = vec!["0x123".to_string()];
2996 let result = client.get_order_books(&token_ids).await;
2997
2998 mock.assert_async().await;
2999 if let Err(e) = &result {
3000 println!("Error: {:?}", e);
3001 }
3002 assert!(result.is_ok());
3003 let books = result.unwrap();
3004 assert_eq!(books.len(), 1);
3005 }
3006
3007 #[tokio::test(flavor = "multi_thread")]
3008 async fn test_order_args_creation() {
3009 let order_args = ClientOrderArgs::new(
3011 "0x123",
3012 Decimal::from_str("0.75").unwrap(),
3013 Decimal::from_str("100.0").unwrap(),
3014 Side::BUY,
3015 );
3016
3017 assert_eq!(order_args.token_id, "0x123");
3018 assert_eq!(order_args.price, Decimal::from_str("0.75").unwrap());
3019 assert_eq!(order_args.size, Decimal::from_str("100.0").unwrap());
3020 assert_eq!(order_args.side, Side::BUY);
3021
3022 let default_args = ClientOrderArgs::default();
3024 assert_eq!(default_args.token_id, "");
3025 assert_eq!(default_args.price, Decimal::ZERO);
3026 assert_eq!(default_args.size, Decimal::ZERO);
3027 assert_eq!(default_args.side, Side::BUY);
3028 }
3029
3030 #[tokio::test(flavor = "multi_thread")]
3031 async fn test_get_fee_rate_bps_success() {
3032 let mut server = Server::new_async().await;
3033 let mock = server
3034 .mock("GET", "/fee-rate")
3035 .match_query(Matcher::UrlEncoded("token_id".into(), "123".into()))
3036 .with_status(200)
3037 .with_header("content-type", "application/json")
3038 .with_body(r#"{"fee_rate_bps":1000}"#)
3039 .create_async()
3040 .await;
3041
3042 let client = create_test_client(&server.url());
3043 let rate = client.get_fee_rate_bps("123").await.unwrap();
3044
3045 mock.assert_async().await;
3046 assert_eq!(rate, 1000);
3047 }
3048
3049 #[tokio::test(flavor = "multi_thread")]
3050 async fn test_rfq_endpoints_happy_path() {
3051 let mut server = Server::new_async().await;
3052
3053 let create_request = RfqCreateRequest {
3055 asset_in: "some_asset_in".to_string(),
3056 asset_out: "some_asset_out".to_string(),
3057 amount_in: "100".to_string(),
3058 amount_out: "200".to_string(),
3059 user_type: 0,
3060 };
3061 let create_request_mock = server
3062 .mock("POST", "/rfq/request")
3063 .match_body(Matcher::JsonString(
3064 json!({
3065 "assetIn": "some_asset_in",
3066 "assetOut": "some_asset_out",
3067 "amountIn": "100",
3068 "amountOut": "200",
3069 "userType": 0
3070 })
3071 .to_string(),
3072 ))
3073 .with_status(200)
3074 .with_header("content-type", "application/json")
3075 .with_body(r#"{"requestId":"req123","expiry":1744936318}"#)
3076 .create_async()
3077 .await;
3078
3079 let cancel_request_mock = server
3081 .mock("DELETE", "/rfq/request")
3082 .match_body(Matcher::JsonString(r#"{"requestId":"req123"}"#.to_string()))
3083 .with_status(200)
3084 .with_body("OK")
3085 .create_async()
3086 .await;
3087
3088 let rfq_requests_mock = server
3090 .mock("GET", "/rfq/data/requests")
3091 .match_query(Matcher::AllOf(vec![
3092 Matcher::UrlEncoded("offset".into(), "MA==".into()),
3093 Matcher::UrlEncoded("limit".into(), "10".into()),
3094 Matcher::UrlEncoded("state".into(), "active".into()),
3095 Matcher::UrlEncoded("requestIds[]".into(), "req123".into()),
3096 Matcher::UrlEncoded("markets[]".into(), "some_market".into()),
3097 ]))
3098 .with_status(200)
3099 .with_header("content-type", "application/json")
3100 .with_body(
3101 r#"{
3102 "data": [{
3103 "requestId": "req123",
3104 "userAddress": "0xabc",
3105 "proxyAddress": "0xdef",
3106 "condition": "some_condition_id",
3107 "token": "some_token_id",
3108 "complement": "some_complement",
3109 "side": "BUY",
3110 "sizeIn": 100,
3111 "sizeOut": 200,
3112 "price": 0.5,
3113 "state": "active",
3114 "expiry": 1744936318
3115 }],
3116 "next_cursor": "MA==",
3117 "limit": 10,
3118 "count": 1
3119 }"#,
3120 )
3121 .create_async()
3122 .await;
3123
3124 let create_quote = RfqCreateQuote {
3126 request_id: "req123".to_string(),
3127 asset_in: "some_asset_in".to_string(),
3128 asset_out: "some_asset_out".to_string(),
3129 amount_in: "100".to_string(),
3130 amount_out: "200".to_string(),
3131 user_type: 0,
3132 };
3133 let create_quote_mock = server
3134 .mock("POST", "/rfq/quote")
3135 .match_body(Matcher::JsonString(
3136 json!({
3137 "requestId": "req123",
3138 "assetIn": "some_asset_in",
3139 "assetOut": "some_asset_out",
3140 "amountIn": "100",
3141 "amountOut": "200",
3142 "userType": 0
3143 })
3144 .to_string(),
3145 ))
3146 .with_status(200)
3147 .with_header("content-type", "application/json")
3148 .with_body(r#"{"quoteId":"q123"}"#)
3149 .create_async()
3150 .await;
3151
3152 let cancel_quote_mock = server
3154 .mock("DELETE", "/rfq/quote")
3155 .match_body(Matcher::JsonString(r#"{"quoteId":"q123"}"#.to_string()))
3156 .with_status(200)
3157 .with_body("OK")
3158 .create_async()
3159 .await;
3160
3161 let requester_quotes_mock = server
3163 .mock("GET", "/rfq/data/requester/quotes")
3164 .match_query(Matcher::AllOf(vec![
3165 Matcher::UrlEncoded("offset".into(), "MA==".into()),
3166 Matcher::UrlEncoded("limit".into(), "10".into()),
3167 Matcher::UrlEncoded("state".into(), "active".into()),
3168 Matcher::UrlEncoded("quoteIds[]".into(), "q123".into()),
3169 Matcher::UrlEncoded("requestIds[]".into(), "req123".into()),
3170 ]))
3171 .with_status(200)
3172 .with_header("content-type", "application/json")
3173 .with_body(
3174 r#"{
3175 "data": [{
3176 "quoteId": "q123",
3177 "requestId": "req123",
3178 "userAddress": "0xabc",
3179 "proxyAddress": "0xdef",
3180 "condition": "some_condition_id",
3181 "token": "some_token_id",
3182 "complement": "some_complement",
3183 "side": "BUY",
3184 "sizeIn": 100,
3185 "sizeOut": 200,
3186 "price": 0.5,
3187 "matchType": "matched",
3188 "state": "active"
3189 }],
3190 "next_cursor": "MA==",
3191 "limit": 10,
3192 "count": 1
3193 }"#,
3194 )
3195 .create_async()
3196 .await;
3197
3198 let quoter_quotes_mock = server
3200 .mock("GET", "/rfq/data/quoter/quotes")
3201 .with_status(200)
3202 .with_header("content-type", "application/json")
3203 .with_body(
3204 r#"{
3205 "data": [],
3206 "next_cursor": "MA==",
3207 "limit": 10,
3208 "count": 0
3209 }"#,
3210 )
3211 .create_async()
3212 .await;
3213
3214 let best_quote_mock = server
3216 .mock("GET", "/rfq/data/best-quote")
3217 .match_query(Matcher::UrlEncoded("requestId".into(), "req123".into()))
3218 .with_status(200)
3219 .with_header("content-type", "application/json")
3220 .with_body(
3221 r#"{
3222 "quoteId": "q123",
3223 "requestId": "req123",
3224 "userAddress": "0xabc",
3225 "proxyAddress": "0xdef",
3226 "condition": "some_condition_id",
3227 "token": "some_token_id",
3228 "complement": "some_complement",
3229 "side": "BUY",
3230 "sizeIn": 100,
3231 "sizeOut": 200,
3232 "price": 0.5,
3233 "matchType": "matched",
3234 "state": "active"
3235 }"#,
3236 )
3237 .create_async()
3238 .await;
3239
3240 let exec = RfqOrderExecutionRequest {
3242 request_id: "req123".to_string(),
3243 quote_id: "q123".to_string(),
3244 maker: "0xmaker".to_string(),
3245 signer: "0xsigner".to_string(),
3246 taker: "0xtaker".to_string(),
3247 expiration: 1_740_000_000,
3248 nonce: "123".to_string(),
3249 fee_rate_bps: "1000".to_string(),
3250 side: "BUY".to_string(),
3251 token_id: "123".to_string(),
3252 maker_amount: "100".to_string(),
3253 taker_amount: "200".to_string(),
3254 signature_type: 2,
3255 signature: "0xsig".to_string(),
3256 salt: 42,
3257 owner: "owner".to_string(),
3258 };
3259
3260 let accept_mock = server
3261 .mock("POST", "/rfq/request/accept")
3262 .match_body(Matcher::JsonString(
3263 json!({
3264 "requestId": "req123",
3265 "quoteId": "q123",
3266 "maker": "0xmaker",
3267 "signer": "0xsigner",
3268 "taker": "0xtaker",
3269 "expiration": 1740000000,
3270 "nonce": "123",
3271 "feeRateBps": "1000",
3272 "side": "BUY",
3273 "tokenId": "123",
3274 "makerAmount": "100",
3275 "takerAmount": "200",
3276 "signatureType": 2,
3277 "signature": "0xsig",
3278 "salt": 42,
3279 "owner": "owner"
3280 })
3281 .to_string(),
3282 ))
3283 .with_status(200)
3284 .with_body("OK")
3285 .create_async()
3286 .await;
3287
3288 let approve_mock = server
3290 .mock("POST", "/rfq/quote/approve")
3291 .match_body(Matcher::JsonString(
3292 json!({
3293 "requestId": "req123",
3294 "quoteId": "q123",
3295 "maker": "0xmaker",
3296 "signer": "0xsigner",
3297 "taker": "0xtaker",
3298 "expiration": 1740000000,
3299 "nonce": "123",
3300 "feeRateBps": "1000",
3301 "side": "BUY",
3302 "tokenId": "123",
3303 "makerAmount": "100",
3304 "takerAmount": "200",
3305 "signatureType": 2,
3306 "signature": "0xsig",
3307 "salt": 42,
3308 "owner": "owner"
3309 })
3310 .to_string(),
3311 ))
3312 .with_status(200)
3313 .with_header("content-type", "application/json")
3314 .with_body(r#"{"tradeIds":["t1","t2"]}"#)
3315 .create_async()
3316 .await;
3317
3318 let client = create_test_client_with_l2_auth(&server.url());
3319
3320 let created = client.create_rfq_request(&create_request).await.unwrap();
3321 assert_eq!(created.request_id, "req123");
3322 assert_eq!(created.expiry, 1_744_936_318);
3323 create_request_mock.assert_async().await;
3324
3325 client.cancel_rfq_request("req123").await.unwrap();
3326 cancel_request_mock.assert_async().await;
3327
3328 let params = RfqRequestsParams {
3329 offset: Some("MA==".to_string()),
3330 limit: Some(10),
3331 state: Some("active".to_string()),
3332 request_ids: vec!["req123".to_string()],
3333 markets: vec!["some_market".to_string()],
3334 ..Default::default()
3335 };
3336 let requests = client.get_rfq_requests(Some(¶ms)).await.unwrap();
3337 assert_eq!(requests.data.len(), 1);
3338 assert_eq!(requests.data[0].request_id, "req123");
3339 rfq_requests_mock.assert_async().await;
3340
3341 let quote = client.create_rfq_quote(&create_quote).await.unwrap();
3342 assert_eq!(quote.quote_id, "q123");
3343 create_quote_mock.assert_async().await;
3344
3345 client.cancel_rfq_quote("q123").await.unwrap();
3346 cancel_quote_mock.assert_async().await;
3347
3348 let quote_params = RfqQuotesParams {
3349 offset: Some("MA==".to_string()),
3350 limit: Some(10),
3351 state: Some("active".to_string()),
3352 quote_ids: vec!["q123".to_string()],
3353 request_ids: vec!["req123".to_string()],
3354 ..Default::default()
3355 };
3356
3357 let requester_quotes = client
3358 .get_rfq_requester_quotes(Some("e_params))
3359 .await
3360 .unwrap();
3361 assert_eq!(requester_quotes.data.len(), 1);
3362 requester_quotes_mock.assert_async().await;
3363
3364 let quoter_quotes = client.get_rfq_quoter_quotes(None).await.unwrap();
3365 assert_eq!(quoter_quotes.data.len(), 0);
3366 quoter_quotes_mock.assert_async().await;
3367
3368 let best = client.get_rfq_best_quote("req123").await.unwrap();
3369 assert_eq!(best.quote_id, "q123");
3370 best_quote_mock.assert_async().await;
3371
3372 client.accept_rfq_quote(&exec).await.unwrap();
3373 accept_mock.assert_async().await;
3374
3375 let approved = client.approve_rfq_order(&exec).await.unwrap();
3376 assert_eq!(approved.trade_ids, vec!["t1".to_string(), "t2".to_string()]);
3377 approve_mock.assert_async().await;
3378 }
3379}