From 0ff584d6eed3b6f895fe701b0689d8ec8d02a33b Mon Sep 17 00:00:00 2001 From: floor-licker Date: Tue, 4 Nov 2025 21:59:40 -0500 Subject: [PATCH] fix: fix return type mismatch --- src/client.rs | 500 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 445 insertions(+), 55 deletions(-) diff --git a/src/client.rs b/src/client.rs index 6d7d9a3..2cb015c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -605,28 +605,54 @@ impl ClobClient { /// - Market ID (all orders for a specific market) /// /// The response includes order status, fill information, and timestamps. - pub async fn get_orders(&self, params: Option) -> Result> { + pub async fn get_orders(&self, params: Option<&crate::types::OpenOrderParams>, next_cursor: Option<&str>) -> Result> { let signer = self.signer.as_ref() .ok_or_else(|| PolyfillError::auth("Signer not set"))?; let api_creds = self.api_creds.as_ref() .ok_or_else(|| PolyfillError::auth("API credentials not set"))?; - let headers = create_l2_headers::(signer, api_creds, "GET", "/orders", None)?; - let mut req = self.create_request_with_headers(Method::GET, "/orders", headers.into_iter()); + let method = Method::GET; + let endpoint = "/data/orders"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; - // Add query parameters if provided - if let Some(params) = params { - let query_params = params.to_query_params(); - req = req.query(&query_params); + let query_params = match params { + None => Vec::new(), + Some(p) => p.to_query_params(), + }; + + let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); // INITIAL_CURSOR + let mut output = Vec::new(); + + while next_cursor != "LTE=" { // END_CURSOR + let req = self.http_client + .request(method.clone(), format!("{}{}", self.base_url, endpoint)) + .query(&query_params) + .query(&[("next_cursor", &next_cursor)]); + + let r = headers + .clone() + .into_iter() + .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v)); + + let resp = r.send().await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))? + .json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?; + + let new_cursor = resp["next_cursor"] + .as_str() + .ok_or_else(|| PolyfillError::parse("Failed to parse next cursor".to_string(), None))? + .to_owned(); + + next_cursor = new_cursor; + + let results = resp["data"].clone(); + let orders = serde_json::from_value::>(results) + .map_err(|e| PolyfillError::parse(format!("Failed to parse data from order response: {}", e), None))?; + output.extend(orders); } - - let response = req.send().await?; - if !response.status().is_success() { - return Err(PolyfillError::api(response.status().as_u16(), "Failed to get orders")); - } - - let orders: Vec = response.json().await?; - Ok(orders) + + Ok(output) } /// Get trade history with optional filtering @@ -639,28 +665,52 @@ impl ClobClient { /// - Time range (before/after timestamps) /// /// Trades are returned in reverse chronological order (newest first). - pub async fn get_trades(&self, params: Option) -> Result> { + pub async fn get_trades(&self, trade_params: Option<&crate::types::TradeParams>, next_cursor: Option<&str>) -> Result> { let signer = self.signer.as_ref() .ok_or_else(|| PolyfillError::auth("Signer not set"))?; let api_creds = self.api_creds.as_ref() .ok_or_else(|| PolyfillError::auth("API credentials not set"))?; - let headers = create_l2_headers::(signer, api_creds, "GET", "/trades", None)?; - let mut req = self.create_request_with_headers(Method::GET, "/trades", headers.into_iter()); + let method = Method::GET; + let endpoint = "/data/trades"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; - // Add query parameters if provided - if let Some(params) = params { - let query_params = params.to_query_params(); - req = req.query(&query_params); + let query_params = match trade_params { + None => Vec::new(), + Some(p) => p.to_query_params(), + }; + + let mut next_cursor = next_cursor.unwrap_or("MA==").to_string(); // INITIAL_CURSOR + let mut output = Vec::new(); + + while next_cursor != "LTE=" { // END_CURSOR + let req = self.http_client + .request(method.clone(), format!("{}{}", self.base_url, endpoint)) + .query(&query_params) + .query(&[("next_cursor", &next_cursor)]); + + let r = headers + .clone() + .into_iter() + .fold(req, |r, (k, v)| r.header(HeaderName::from_static(k), v)); + + let resp = r.send().await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))? + .json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?; + + let new_cursor = resp["next_cursor"] + .as_str() + .ok_or_else(|| PolyfillError::parse("Failed to parse next cursor".to_string(), None))? + .to_owned(); + + next_cursor = new_cursor; + + let results = resp["data"].clone(); + output.push(results); } - - let response = req.send().await?; - if !response.status().is_success() { - return Err(PolyfillError::api(response.status().as_u16(), "Failed to get trades")); - } - - let trades: Vec = response.json().await?; - Ok(trades) + + Ok(output) } /// Get balance and allowance information for all assets @@ -670,22 +720,38 @@ impl ClobClient { /// /// You need both balance and allowance to place orders - the exchange needs permission /// to move your tokens when orders are filled. - pub async fn balance_allowance(&self) -> Result> { + pub async fn get_balance_allowance(&self, params: Option) -> Result { let signer = self.signer.as_ref() .ok_or_else(|| PolyfillError::auth("Signer not set"))?; let api_creds = self.api_creds.as_ref() .ok_or_else(|| PolyfillError::auth("API credentials not set"))?; - let headers = create_l2_headers::(signer, api_creds, "GET", "/balance-allowance", None)?; - let req = self.create_request_with_headers(Method::GET, "/balance-allowance", headers.into_iter()); - - let response = req.send().await?; - if !response.status().is_success() { - return Err(PolyfillError::api(response.status().as_u16(), "Failed to get balance allowance")); + let mut params = params.unwrap_or_default(); + if params.signature_type.is_none() { + params.set_signature_type( + self.order_builder + .as_ref() + .expect("OrderBuilder not set") + .get_sig_type(), + ); } - let balances: Vec = response.json().await?; - Ok(balances) + let query_params = params.to_query_params(); + + let method = Method::GET; + let endpoint = "/balance-allowance"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .query(&query_params) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) } /// Set up notifications for order fills and other events @@ -696,21 +762,33 @@ impl ClobClient { /// - Market conditions change significantly /// /// The signature proves you own the account and want to receive notifications. - pub async fn notifications(&self, params: crate::types::NotificationParams) -> Result { + pub async fn get_notifications(&self) -> Result { let signer = self.signer.as_ref() .ok_or_else(|| PolyfillError::auth("Signer not set"))?; let api_creds = self.api_creds.as_ref() .ok_or_else(|| PolyfillError::auth("API credentials not set"))?; - let headers = create_l2_headers(signer, api_creds, "POST", "/notifications", Some(¶ms))?; - let req = self.create_request_with_headers(Method::POST, "/notifications", headers.into_iter()); + let method = Method::GET; + let endpoint = "/notifications"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; - let response = req.json(¶ms).send().await?; - if !response.status().is_success() { - return Err(PolyfillError::api(response.status().as_u16(), "Failed to set up notifications")); - } + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .query(&[( + "signature_type", + &self + .order_builder + .as_ref() + .expect("OrderBuilder not set") + .get_sig_type().to_string(), + )]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; - Ok(response.json::().await?) + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) } /// Get midpoints for multiple tokens in a single request @@ -720,12 +798,19 @@ impl ClobClient { /// /// Midpoints are returned as a HashMap where the key is the token_id and the value /// is the midpoint price (or None if there's no valid midpoint). - pub async fn get_midpoints(&self, token_ids: Vec) -> Result { - let request = crate::types::BatchMidpointRequest { token_ids }; + pub async fn get_midpoints(&self, token_ids: &[String]) -> Result> { + let request_data: Vec> = token_ids + .iter() + .map(|id| { + let mut map = std::collections::HashMap::new(); + map.insert("token_id", id.clone()); + map + }) + .collect(); let response = self.http_client .post(&format!("{}/midpoints", self.base_url)) - .json(&request) + .json(&request_data) .send() .await?; @@ -733,7 +818,7 @@ impl ClobClient { return Err(PolyfillError::api(response.status().as_u16(), "Failed to get batch midpoints")); } - let midpoints: crate::types::BatchMidpointResponse = response.json().await?; + let midpoints: std::collections::HashMap = response.json().await?; Ok(midpoints) } @@ -744,12 +829,20 @@ impl ClobClient { /// a portfolio or comparing multiple markets. /// /// Returns bid (best buy price), ask (best sell price), and mid (average) for each token. - pub async fn get_prices(&self, token_ids: Vec) -> Result { - let request = crate::types::BatchPriceRequest { token_ids }; + pub async fn get_prices(&self, book_params: &[crate::types::BookParams]) -> Result>> { + let request_data: Vec> = book_params + .iter() + .map(|params| { + let mut map = std::collections::HashMap::new(); + map.insert("token_id", params.token_id.clone()); + map.insert("side", params.side.as_str().to_string()); + map + }) + .collect(); let response = self.http_client .post(&format!("{}/prices", self.base_url)) - .json(&request) + .json(&request_data) .send() .await?; @@ -757,9 +850,306 @@ impl ClobClient { return Err(PolyfillError::api(response.status().as_u16(), "Failed to get batch prices")); } - let prices: crate::types::BatchPriceResponse = response.json().await?; + let prices: std::collections::HashMap> = response.json().await?; Ok(prices) } + + /// Get order book for multiple tokens (batch) - reference implementation compatible + pub async fn get_order_books(&self, token_ids: &[String]) -> Result> { + let request_data: Vec> = token_ids + .iter() + .map(|id| { + let mut map = std::collections::HashMap::new(); + map.insert("token_id", id.clone()); + map + }) + .collect(); + + let response = self.http_client + .post(&format!("{}/books", self.base_url)) + .json(&request_data) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::>().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get single order by ID + pub async fn get_order(&self, order_id: &str) -> Result { + let signer = self.signer.as_ref() + .ok_or_else(|| PolyfillError::config("Signer not configured"))?; + let api_creds = self.api_creds.as_ref() + .ok_or_else(|| PolyfillError::config("API credentials not configured"))?; + + let method = Method::GET; + let endpoint = &format!("/data/order/{}", order_id); + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get last trade price for a token + pub async fn get_last_trade_price(&self, token_id: &str) -> Result { + let response = self.http_client + .get(&format!("{}/last-trade-price", self.base_url)) + .query(&[("token_id", token_id)]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get last trade prices for multiple tokens + pub async fn get_last_trade_prices(&self, token_ids: &[String]) -> Result { + let request_data: Vec> = token_ids + .iter() + .map(|id| { + let mut map = std::collections::HashMap::new(); + map.insert("token_id", id.clone()); + map + }) + .collect(); + + let response = self.http_client + .post(&format!("{}/last-trades-prices", self.base_url)) + .json(&request_data) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Cancel market orders with optional filters + pub async fn cancel_market_orders(&self, market: Option<&str>, asset_id: Option<&str>) -> Result { + let signer = self.signer.as_ref() + .ok_or_else(|| PolyfillError::config("Signer not configured"))?; + let api_creds = self.api_creds.as_ref() + .ok_or_else(|| PolyfillError::config("API credentials not configured"))?; + + let method = Method::DELETE; + let endpoint = "/cancel-market-orders"; + let body = std::collections::HashMap::from([ + ("market", market.unwrap_or("")), + ("asset_id", asset_id.unwrap_or("")), + ]); + + let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(&body))?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .json(&body) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Drop (delete) notifications by IDs + pub async fn drop_notifications(&self, ids: &[String]) -> Result { + let signer = self.signer.as_ref() + .ok_or_else(|| PolyfillError::config("Signer not configured"))?; + let api_creds = self.api_creds.as_ref() + .ok_or_else(|| PolyfillError::config("API credentials not configured"))?; + + let method = Method::DELETE; + let endpoint = "/notifications"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .query(&[("ids", ids.join(","))]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Update balance allowance + pub async fn update_balance_allowance(&self, params: Option) -> Result { + let signer = self.signer.as_ref() + .ok_or_else(|| PolyfillError::config("Signer not configured"))?; + let api_creds = self.api_creds.as_ref() + .ok_or_else(|| PolyfillError::config("API credentials not configured"))?; + + let mut params = params.unwrap_or_default(); + if params.signature_type.is_none() { + params.set_signature_type( + self.order_builder + .as_ref() + .expect("OrderBuilder not set") + .get_sig_type(), + ); + } + + let query_params = params.to_query_params(); + + let method = Method::GET; + let endpoint = "/balance-allowance/update"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .query(&query_params) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Check if an order is scoring + pub async fn is_order_scoring(&self, order_id: &str) -> Result { + let signer = self.signer.as_ref() + .ok_or_else(|| PolyfillError::config("Signer not configured"))?; + let api_creds = self.api_creds.as_ref() + .ok_or_else(|| PolyfillError::config("API credentials not configured"))?; + + let method = Method::GET; + let endpoint = "/order-scoring"; + let headers = create_l2_headers::(signer, api_creds, method.as_str(), endpoint, None)?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .query(&[("order_id", order_id)]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + let result: Value = response.json().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?; + + Ok(result["scoring"].as_bool().unwrap_or(false)) + } + + /// Check if multiple orders are scoring + pub async fn are_orders_scoring(&self, order_ids: &[&str]) -> Result> { + let signer = self.signer.as_ref() + .ok_or_else(|| PolyfillError::config("Signer not configured"))?; + let api_creds = self.api_creds.as_ref() + .ok_or_else(|| PolyfillError::config("API credentials not configured"))?; + + let method = Method::POST; + let endpoint = "/orders-scoring"; + let headers = create_l2_headers(signer, api_creds, method.as_str(), endpoint, Some(order_ids))?; + + let response = self.http_client + .request(method, format!("{}{}", self.base_url, endpoint)) + .headers(headers.into_iter().map(|(k, v)| (HeaderName::from_static(k), v.parse().unwrap())).collect()) + .json(order_ids) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::>().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get sampling markets with pagination + pub async fn get_sampling_markets(&self, next_cursor: Option<&str>) -> Result { + let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR + + let response = self.http_client + .get(&format!("{}/sampling-markets", self.base_url)) + .query(&[("next_cursor", next_cursor)]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get sampling simplified markets with pagination + pub async fn get_sampling_simplified_markets(&self, next_cursor: Option<&str>) -> Result { + let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR + + let response = self.http_client + .get(&format!("{}/sampling-simplified-markets", self.base_url)) + .query(&[("next_cursor", next_cursor)]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get markets with pagination + pub async fn get_markets(&self, next_cursor: Option<&str>) -> Result { + let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR + + let response = self.http_client + .get(&format!("{}/markets", self.base_url)) + .query(&[("next_cursor", next_cursor)]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get simplified markets with pagination + pub async fn get_simplified_markets(&self, next_cursor: Option<&str>) -> Result { + let next_cursor = next_cursor.unwrap_or("MA=="); // INITIAL_CURSOR + + let response = self.http_client + .get(&format!("{}/simplified-markets", self.base_url)) + .query(&[("next_cursor", next_cursor)]) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get single market by condition ID + pub async fn get_market(&self, condition_id: &str) -> Result { + let response = self.http_client + .get(&format!("{}/markets/{}", self.base_url, condition_id)) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } + + /// Get market trades events + pub async fn get_market_trades_events(&self, condition_id: &str) -> Result { + let response = self.http_client + .get(&format!("{}/live-activity/events/{}", self.base_url, condition_id)) + .send() + .await + .map_err(|e| PolyfillError::network(format!("Request failed: {}", e)))?; + + response.json::().await + .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + } } // Response types for API calls