feat(client): use typed HTTP response models instead of serde_json::Value

This commit is contained in:
Andrey Kuznetsov
2026-02-12 23:49:50 +00:00
parent 6190a24e0c
commit 84c3d269ea
6 changed files with 339 additions and 82 deletions
+74 -57
View File
@@ -14,7 +14,6 @@ use alloy_signer_local::PrivateKeySigner;
use reqwest::header::HeaderName;
use reqwest::Client;
use reqwest::{Method, RequestBuilder};
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;
use serde_json::Value;
use std::str::FromStr;
@@ -623,18 +622,8 @@ impl ClobClient {
));
}
let tick_size_response: Value = response.json().await?;
let tick_size = tick_size_response["minimum_tick_size"]
.as_str()
.and_then(|s| Decimal::from_str(s).ok())
.or_else(|| {
tick_size_response["minimum_tick_size"]
.as_f64()
.map(|f| Decimal::from_f64(f).unwrap_or(Decimal::ZERO))
})
.ok_or_else(|| PolyfillError::parse("Invalid tick size format", None))?;
Ok(tick_size)
let tick_size_response: TickSizeResponse = response.json().await?;
Ok(tick_size_response.minimum_tick_size)
}
/// Get maker fee rate (in bps) for a token
@@ -658,7 +647,7 @@ impl ClobClient {
.json()
.await
.map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
Ok(fee_rate.fee_rate_bps)
Ok(fee_rate.base_fee)
}
/// Create a new API key
@@ -820,12 +809,8 @@ impl ClobClient {
));
}
let neg_risk_response: Value = response.json().await?;
let neg_risk = neg_risk_response["neg_risk"]
.as_bool()
.ok_or_else(|| PolyfillError::parse("Invalid neg risk format", None))?;
Ok(neg_risk)
let neg_risk_response: NegRiskResponse = response.json().await?;
Ok(neg_risk_response.neg_risk)
}
/// Resolve tick size for an order
@@ -953,7 +938,7 @@ impl ClobClient {
.collect(),
};
order_builder.calculate_market_price(&levels, amount)
order_builder.calculate_market_price(side, &levels, amount)
}
/// Create a market order
@@ -974,7 +959,7 @@ impl ClobClient {
let extras = extras.unwrap_or_default();
let price = self
.calculate_market_price(&order_args.token_id, Side::BUY, order_args.amount)
.calculate_market_price(&order_args.token_id, order_args.side, order_args.amount)
.await?;
if !self.is_price_in_range(
@@ -1000,7 +985,7 @@ impl ClobClient {
&self,
order: SignedOrderRequest,
order_type: OrderType,
) -> Result<Value> {
) -> Result<crate::types::PostOrderResponse> {
let signer = self
.signer
.as_ref()
@@ -1029,7 +1014,7 @@ impl ClobClient {
return Err(PolyfillError::api(status, message));
}
Ok(response.json::<Value>().await?)
Ok(response.json::<crate::types::PostOrderResponse>().await?)
}
/// Post multiple orders to the exchange in a single request
@@ -1037,7 +1022,7 @@ impl ClobClient {
&self,
orders: Vec<SignedOrderRequest>,
order_type: OrderType,
) -> Result<Value> {
) -> Result<Vec<crate::types::PostOrderResponse>> {
if orders.is_empty() {
return Err(PolyfillError::validation("orders cannot be empty"));
}
@@ -1076,7 +1061,9 @@ impl ClobClient {
return Err(PolyfillError::api(status, message));
}
Ok(response.json::<Value>().await?)
Ok(response
.json::<Vec<crate::types::PostOrderResponse>>()
.await?)
}
/// Create and post an order in one call with an explicit order type
@@ -1084,13 +1071,16 @@ impl ClobClient {
&self,
order_args: &OrderArgs,
order_type: OrderType,
) -> Result<Value> {
) -> Result<crate::types::PostOrderResponse> {
let order = self.create_order(order_args, None, None, None).await?;
self.post_order(order, order_type).await
}
/// Create and post an order in one call (defaults to GTC)
pub async fn create_and_post_order(&self, order_args: &OrderArgs) -> Result<Value> {
pub async fn create_and_post_order(
&self,
order_args: &OrderArgs,
) -> Result<crate::types::PostOrderResponse> {
self.create_and_post_order_with_type(order_args, OrderType::GTC)
.await
}
@@ -1100,7 +1090,7 @@ impl ClobClient {
&self,
order_args: &[OrderArgs],
order_type: OrderType,
) -> Result<Value> {
) -> Result<Vec<crate::types::PostOrderResponse>> {
if order_args.is_empty() {
return Err(PolyfillError::validation("order_args cannot be empty"));
}
@@ -1114,13 +1104,16 @@ impl ClobClient {
}
/// Create and post multiple orders in one call (defaults to GTC)
pub async fn create_and_post_orders(&self, order_args: &[OrderArgs]) -> Result<Value> {
pub async fn create_and_post_orders(
&self,
order_args: &[OrderArgs],
) -> Result<Vec<crate::types::PostOrderResponse>> {
self.create_and_post_orders_with_type(order_args, OrderType::GTC)
.await
}
/// Cancel an order
pub async fn cancel(&self, order_id: &str) -> Result<Value> {
pub async fn cancel(&self, order_id: &str) -> Result<crate::types::CancelOrdersResponse> {
let signer = self
.signer
.as_ref()
@@ -1143,11 +1136,16 @@ impl ClobClient {
));
}
Ok(response.json::<Value>().await?)
Ok(response
.json::<crate::types::CancelOrdersResponse>()
.await?)
}
/// Cancel multiple orders
pub async fn cancel_orders(&self, order_ids: &[String]) -> Result<Value> {
pub async fn cancel_orders(
&self,
order_ids: &[String],
) -> Result<crate::types::CancelOrdersResponse> {
let signer = self
.signer
.as_ref()
@@ -1168,11 +1166,13 @@ impl ClobClient {
));
}
Ok(response.json::<Value>().await?)
Ok(response
.json::<crate::types::CancelOrdersResponse>()
.await?)
}
/// Cancel all orders
pub async fn cancel_all(&self) -> Result<Value> {
pub async fn cancel_all(&self) -> Result<crate::types::CancelOrdersResponse> {
let signer = self
.signer
.as_ref()
@@ -1182,7 +1182,7 @@ impl ClobClient {
.as_ref()
.ok_or_else(|| PolyfillError::auth("API credentials not set"))?;
let headers = create_l2_headers::<Value>(signer, api_creds, "DELETE", "/cancel-all", None)?;
let headers = create_l2_headers::<()>(signer, api_creds, "DELETE", "/cancel-all", None)?;
let req =
self.create_request_with_headers(Method::DELETE, "/cancel-all", headers.into_iter());
@@ -1194,7 +1194,9 @@ impl ClobClient {
));
}
Ok(response.json::<Value>().await?)
Ok(response
.json::<crate::types::CancelOrdersResponse>()
.await?)
}
/// Get open orders with optional filtering
@@ -1292,7 +1294,7 @@ impl ClobClient {
&self,
trade_params: Option<&crate::types::TradeParams>,
next_cursor: Option<&str>,
) -> Result<Vec<Value>> {
) -> Result<Vec<crate::types::TradeResponse>> {
let signer = self
.signer
.as_ref()
@@ -1348,7 +1350,14 @@ impl ClobClient {
next_cursor = new_cursor;
let results = resp["data"].clone();
output.push(results);
let trades = serde_json::from_value::<Vec<crate::types::TradeResponse>>(results)
.map_err(|e| {
PolyfillError::parse(
format!("Failed to parse data from trades response: {}", e),
None,
)
})?;
output.extend(trades);
}
Ok(output)
@@ -1646,7 +1655,7 @@ impl ClobClient {
&self,
market: Option<&str>,
asset_id: Option<&str>,
) -> Result<Value> {
) -> Result<crate::types::CancelOrdersResponse> {
let signer = self
.signer
.as_ref()
@@ -1680,7 +1689,7 @@ impl ClobClient {
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
response
.json::<Value>()
.json::<crate::types::CancelOrdersResponse>()
.await
.map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))
}
@@ -1802,12 +1811,12 @@ impl ClobClient {
.await
.map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?;
let result: Value = response
let result: crate::types::OrderScoringResponse = response
.json()
.await
.map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None))?;
Ok(result["scoring"].as_bool().unwrap_or(false))
Ok(result.scoring)
}
/// Check if multiple orders are scoring
@@ -2357,9 +2366,11 @@ impl ClobClient {
// Re-export types from the canonical location in types.rs
pub use crate::types::{
ExtraOrderArgs, Market, MarketOrderArgs, MarketsResponse, MidpointResponse, NegRiskResponse,
OrderBookSummary, OrderSummary, PriceResponse, PricesHistoryInterval, PricesHistoryResponse,
Rewards, SpreadResponse, TickSizeResponse, Token,
CancelOrdersResponse, ExtraOrderArgs, MakerOrder, Market, MarketOrderArgs, MarketsResponse,
MidpointResponse, NegRiskResponse, OrderBookSummary, OrderScoringResponse, OrderSummary,
PostOrderResponse, PriceHistoryPoint, PriceResponse, PricesHistoryInterval,
PricesHistoryResponse, Rewards, SpreadResponse, TickSizeResponse, Token, TradeResponse,
TraderSide,
};
// Compatibility types that need to stay in client.rs
@@ -2715,7 +2726,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread")]
async fn test_get_prices_history_interval_success() {
let mut server = Server::new_async().await;
let mock_response = r#"{"history":[{"t":1}]}"#;
let mock_response = r#"{"history":[{"t":1,"p":"0.52"}]}"#;
let mock = server
.mock("GET", "/prices-history")
@@ -2738,6 +2749,8 @@ mod tests {
mock.assert_async().await;
assert_eq!(response.history.len(), 1);
assert_eq!(response.history[0].t, 1);
assert_eq!(response.history[0].p, Decimal::from_str("0.52").unwrap());
}
#[tokio::test(flavor = "multi_thread")]
@@ -2980,7 +2993,7 @@ mod tests {
.mock("POST", "/orders")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"success":true,"orderIDs":["a","b"]}"#)
.with_body(r#"[{"success":true,"orderID":"a"},{"success":true,"orderID":"b"}]"#)
.create_async()
.await;
@@ -2996,24 +3009,28 @@ mod tests {
mock.assert_async().await;
assert!(result.is_ok());
assert_eq!(result.unwrap()["success"], true);
let orders = result.unwrap();
assert_eq!(orders.len(), 2);
assert!(orders.iter().all(|o| o.success));
}
#[tokio::test(flavor = "multi_thread")]
async fn test_post_orders_batch_empty_validation() {
let client = create_test_client_with_l2_auth("https://test.example.com");
let result = client.post_orders(Vec::new(), crate::types::OrderType::GTC).await;
let result = client
.post_orders(Vec::new(), crate::types::OrderType::GTC)
.await;
assert!(matches!(result, Err(PolyfillError::Validation { .. })));
}
#[tokio::test(flavor = "multi_thread")]
async fn test_post_orders_batch_too_many_validation() {
let client = create_test_client_with_l2_auth("https://test.example.com");
let orders = (0..16)
.map(|_| sample_signed_order())
.collect::<Vec<_>>();
let orders = (0..16).map(|_| sample_signed_order()).collect::<Vec<_>>();
let result = client.post_orders(orders, crate::types::OrderType::GTC).await;
let result = client
.post_orders(orders, crate::types::OrderType::GTC)
.await;
assert!(matches!(result, Err(PolyfillError::Validation { .. })));
}
@@ -3036,7 +3053,7 @@ mod tests {
mock.assert_async().await;
assert!(result.is_ok());
assert_eq!(result.unwrap()["success"], true);
assert!(result.unwrap().success);
}
#[tokio::test(flavor = "multi_thread")]
@@ -3058,7 +3075,7 @@ mod tests {
mock.assert_async().await;
assert!(result.is_ok());
assert_eq!(result.unwrap()[0]["success"], true);
assert!(result.unwrap()[0].success);
}
#[tokio::test(flavor = "multi_thread")]
@@ -3222,7 +3239,7 @@ mod tests {
.match_query(Matcher::UrlEncoded("token_id".into(), "123".into()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(r#"{"fee_rate_bps":1000}"#)
.with_body(r#"{"base_fee":1000}"#)
.create_async()
.await;
+7
View File
@@ -97,10 +97,12 @@ pub use crate::types::{
BatchPriceRequest,
BatchPriceResponse,
BookParams,
CancelOrdersResponse,
ClientConfig,
ClientResult,
FeeRateResponse,
FillEvent,
MakerOrder,
Market,
MarketSnapshot,
MarketsResponse,
@@ -114,9 +116,12 @@ pub use crate::types::{
OrderBookSummary,
OrderDelta,
OrderRequest,
OrderScoringResponse,
OrderStatus,
OrderSummary,
OrderType,
PostOrderResponse,
PriceHistoryPoint,
PriceResponse,
PricesHistoryInterval,
PricesHistoryResponse,
@@ -143,6 +148,8 @@ pub use crate::types::{
Token,
TokenPrice,
TradeParams,
TradeResponse,
TraderSide,
WssAuth,
WssChannelType,
WssSubscription,
+94 -13
View File
@@ -193,32 +193,48 @@ impl OrderBuilder {
/// Get order amounts for a market order
fn get_market_order_amounts(
&self,
side: Side,
amount: Decimal,
price: Decimal,
round_config: &RoundConfig,
) -> (u32, u32) {
let raw_maker_amt = amount.round_dp_with_strategy(round_config.size, ToZero);
let raw_price = price.round_dp_with_strategy(round_config.price, MidpointTowardZero);
let raw_taker_amt = raw_maker_amt / raw_price;
let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);
(
decimal_to_token_u32(raw_maker_amt),
decimal_to_token_u32(raw_taker_amt),
)
match side {
Side::BUY => {
let raw_maker_amt = amount.round_dp_with_strategy(round_config.size, ToZero);
let raw_taker_amt = raw_maker_amt / raw_price;
let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);
(
decimal_to_token_u32(raw_maker_amt),
decimal_to_token_u32(raw_taker_amt),
)
},
Side::SELL => {
let raw_maker_amt = amount.round_dp_with_strategy(round_config.size, ToZero);
let raw_taker_amt = raw_maker_amt * raw_price;
let raw_taker_amt = self.fix_amount_rounding(raw_taker_amt, round_config);
(
decimal_to_token_u32(raw_maker_amt),
decimal_to_token_u32(raw_taker_amt),
)
},
}
}
/// Calculate market price from order book levels
pub fn calculate_market_price(
&self,
side: Side,
positions: &[crate::types::BookLevel],
amount_to_match: Decimal,
) -> Result<Decimal> {
let mut sum = Decimal::ZERO;
for level in positions {
sum += level.size * level.price;
sum += match side {
Side::BUY => level.size * level.price,
Side::SELL => level.size,
};
if sum >= amount_to_match {
return Ok(level.price);
}
@@ -246,8 +262,12 @@ impl OrderBuilder {
.tick_size
.ok_or_else(|| PolyfillError::validation("Cannot create order without tick size"))?;
let (maker_amount, taker_amount) =
self.get_market_order_amounts(order_args.amount, price, &ROUNDING_CONFIG[&tick_size]);
let (maker_amount, taker_amount) = self.get_market_order_amounts(
order_args.side,
order_args.amount,
price,
&ROUNDING_CONFIG[&tick_size],
);
let neg_risk = options
.neg_risk
@@ -262,7 +282,7 @@ impl OrderBuilder {
self.build_signed_order(
order_args.token_id.clone(),
Side::BUY,
order_args.side,
chain_id,
exchange_address,
maker_amount,
@@ -437,4 +457,65 @@ mod tests {
assert!(seed < u64::MAX);
}
}
#[test]
fn test_calculate_market_price_respects_side_amount_semantics() {
let signer: PrivateKeySigner =
"0x1234567890123456789012345678901234567890123456789012345678901234"
.parse()
.unwrap();
let builder = OrderBuilder::new(signer, None, None);
let levels = vec![
crate::types::BookLevel {
price: Decimal::from_str("0.50").unwrap(),
size: Decimal::from_str("10").unwrap(),
},
crate::types::BookLevel {
price: Decimal::from_str("0.55").unwrap(),
size: Decimal::from_str("10").unwrap(),
},
];
// BUY amounts are quote-denominated: need 6 USDC -> first level (10 * 0.50 = 5) is not enough.
let buy_price = builder
.calculate_market_price(Side::BUY, &levels, Decimal::from_str("6").unwrap())
.unwrap();
assert_eq!(buy_price, Decimal::from_str("0.55").unwrap());
// SELL amounts are base-denominated: need 6 tokens -> first level (size 10) is enough.
let sell_price = builder
.calculate_market_price(Side::SELL, &levels, Decimal::from_str("6").unwrap())
.unwrap();
assert_eq!(sell_price, Decimal::from_str("0.50").unwrap());
}
#[test]
fn test_create_market_order_uses_input_side() {
let signer: PrivateKeySigner =
"0x1234567890123456789012345678901234567890123456789012345678901234"
.parse()
.unwrap();
let builder = OrderBuilder::new(signer, None, None);
let order = builder
.create_market_order(
137,
&MarketOrderArgs {
token_id: "123".to_string(),
side: Side::SELL,
amount: Decimal::from_str("5").unwrap(),
},
Decimal::from_str("0.40").unwrap(),
&ExtraOrderArgs::default(),
&OrderOptions {
tick_size: Some(Decimal::from_str("0.01").unwrap()),
neg_risk: Some(false),
fee_rate_bps: None,
},
)
.unwrap();
assert_eq!(order.side, "SELL");
}
}
+159 -7
View File
@@ -191,6 +191,21 @@ pub enum Side {
SELL = 1,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TraderSide {
Taker,
Maker,
#[serde(untagged)]
Unknown(String),
}
impl Default for TraderSide {
fn default() -> Self {
Self::Unknown("UNKNOWN".to_string())
}
}
impl Side {
pub fn as_str(&self) -> &'static str {
match self {
@@ -526,6 +541,8 @@ impl Default for ExtraOrderArgs {
#[derive(Debug, Clone)]
pub struct MarketOrderArgs {
pub token_id: String,
pub side: Side,
/// Quote amount for buys, base token amount for sells.
pub amount: Decimal,
}
@@ -1108,6 +1125,131 @@ pub struct OpenOrder {
pub created_at: u64,
}
/// Response from posting a single order.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostOrderResponse {
#[serde(default)]
pub error_msg: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
)]
pub making_amount: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string_default_on_error"
)]
pub taking_amount: Option<Decimal>,
#[serde(rename = "orderID")]
pub order_id: String,
#[serde(default)]
pub status: Option<String>,
pub success: bool,
#[serde(default, alias = "transactionsHashes")]
pub transaction_hashes: Vec<String>,
#[serde(default)]
pub trade_ids: Vec<String>,
}
/// Response from cancel endpoints.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelOrdersResponse {
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub canceled: Vec<String>,
#[serde(default, alias = "not_canceled")]
pub not_canceled: std::collections::HashMap<String, String>,
}
/// Trade item returned by `/data/trades`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct TradeResponse {
pub id: String,
#[serde(default)]
pub taker_order_id: Option<String>,
pub market: String,
pub asset_id: String,
pub side: Side,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub size: Decimal,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub fee_rate_bps: Option<Decimal>,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub price: Decimal,
#[serde(default)]
pub status: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub match_time: Option<u64>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub last_update: Option<u64>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_number_from_string"
)]
pub bucket_index: Option<u64>,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub maker_address: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::vec_from_null"
)]
pub maker_orders: Vec<MakerOrder>,
#[serde(default)]
pub transaction_hash: Option<String>,
#[serde(default)]
pub trader_side: TraderSide,
#[serde(default, alias = "err_msg")]
pub error_msg: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MakerOrder {
pub order_id: String,
#[serde(default)]
pub owner: Option<String>,
#[serde(default)]
pub maker_address: Option<String>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub matched_amount: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub price: Option<Decimal>,
#[serde(
default,
deserialize_with = "crate::decode::deserializers::optional_decimal_from_string"
)]
pub fee_rate_bps: Option<Decimal>,
#[serde(default)]
pub asset_id: Option<String>,
#[serde(default)]
pub outcome: Option<String>,
#[serde(default)]
pub side: Option<Side>,
}
/// Balance allowance information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BalanceAllowance {
@@ -1256,24 +1398,29 @@ impl PricesHistoryInterval {
}
}
/// Raw response from `/prices-history`.
/// A single price-history datapoint from `/prices-history`.
///
/// We intentionally keep `history` entries as `serde_json::Value` because the upstream API has
/// no stable public schema here and currently may return empty history for many markets.
/// Mirrors the official Polymarket SDK shape (`t`, `p`).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceHistoryPoint {
pub t: i64,
#[serde(deserialize_with = "crate::decode::deserializers::decimal_from_string")]
pub p: Decimal,
}
/// Response from `/prices-history`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PricesHistoryResponse {
pub history: Vec<serde_json::Value>,
pub history: Vec<PriceHistoryPoint>,
}
#[derive(Debug, Deserialize)]
pub struct SpreadResponse {
#[serde(with = "rust_decimal::serde::str")]
pub spread: Decimal,
}
#[derive(Debug, Deserialize)]
pub struct TickSizeResponse {
#[serde(with = "rust_decimal::serde::str")]
pub minimum_tick_size: Decimal,
}
@@ -1282,6 +1429,11 @@ pub struct NegRiskResponse {
pub neg_risk: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderScoringResponse {
pub scoring: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BookParams {
pub token_id: String,
@@ -1376,7 +1528,7 @@ pub struct Rewards {
/// Fee rate in basis points for a given token.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeeRateResponse {
pub fee_rate_bps: u32,
pub base_fee: u32,
}
/// Create RFQ request (Requester).
+3 -3
View File
@@ -112,9 +112,9 @@ async fn test_real_api_authenticated_order_flow() {
println!("PASS: Order posted successfully!");
// Step 5: Cancel the order
if let Some(order_id) = response.get("orderID").and_then(|v| v.as_str()) {
println!("Step 5: Canceling order {}...", order_id);
let cancel_result = client.cancel(order_id).await;
if !response.order_id.is_empty() {
println!("Step 5: Canceling order {}...", response.order_id);
let cancel_result = client.cancel(&response.order_id).await;
assert!(
cancel_result.is_ok(),
"Failed to cancel order: {:?}",
+2 -2
View File
@@ -41,9 +41,9 @@ async fn test_post_order_authentication() {
println!(" Response: {:?}", response);
// Try to cancel it if we got an order ID
if let Some(order_id) = response.get("orderID").and_then(|v| v.as_str()) {
if !response.order_id.is_empty() {
println!("\nStep 3: Canceling order...");
match client.cancel(order_id).await {
match client.cancel(&response.order_id).await {
Ok(_) => println!("Order canceled successfully"),
Err(e) => println!("Cancel failed (order might have expired): {:?}", e),
}