Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,7 @@ impl ClobClient {
&self,
orders: Vec<SignedOrderRequest>,
order_type: OrderType,
) -> Result<Vec<Value>> {
) -> Result<Vec<BatchOrderResponse>> {
let signer = self
.signer
.as_ref()
Expand Down Expand Up @@ -930,7 +930,22 @@ impl ClobClient {
));
}

Ok(response.json::<Vec<Value>>().await?)
let batch_results: Vec<BatchOrderResponse> = response.json().await?;

for (idx, result) in batch_results.iter().enumerate() {
if result.has_error() {
let error_msg = result
.error_msg
.clone()
.unwrap_or_else(|| "Unknown error".to_string());
return Err(PolyError::order(
format!("Order {} failed: {}", idx, error_msg),
crate::errors::OrderErrorKind::ExecutionFailed,
));
}
}

Ok(batch_results)
}

/// Create and post an order in one call
Expand Down Expand Up @@ -1826,10 +1841,10 @@ impl ClobClient {

// Re-export types from the canonical location in types.rs
pub use crate::types::{
DataApiPositionsParams, DataApiSortBy, DataApiSortDirection, DataPosition, DataPositionValue,
ExtraOrderArgs, GammaEvent, GammaListParams, Market, MarketOrderArgs, MarketsResponse,
MidpointResponse, NegRiskResponse, OrderBookSummary, OrderSummary, PriceResponse, Rewards,
Sport, SpreadResponse, Tag, TickSizeResponse, Token,
BatchOrderResponse, DataApiPositionsParams, DataApiSortBy, DataApiSortDirection, DataPosition,
DataPositionValue, ExtraOrderArgs, GammaEvent, GammaListParams, Market, MarketOrderArgs,
MarketsResponse, MidpointResponse, NegRiskResponse, OrderBookSummary, OrderSummary,
PriceResponse, Rewards, Sport, SpreadResponse, Tag, TickSizeResponse, Token,
};

// Compatibility types that need to stay in client.rs
Expand Down
62 changes: 62 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,24 @@ impl PostOrder {
}
}

/// Response for a single order in a batch request
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchOrderResponse {
pub success: bool,
pub error_msg: Option<String>,
pub order_id: Option<String>,
pub making_amount: Option<String>,
pub taking_amount: Option<String>,
pub status: Option<String>,
}

impl BatchOrderResponse {
pub fn has_error(&self) -> bool {
!self.success || self.error_msg.is_some()
}
}

/// Market information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Market {
Expand Down Expand Up @@ -1721,3 +1739,47 @@ pub type ClientResult<T> = anyhow::Result<T>;

/// Result type used throughout the client
pub type Result<T> = std::result::Result<T, crate::errors::PolyError>;

#[cfg(test)]
mod batch_order_response_tests {
use super::*;

#[test]
fn test_error_detection_success_false() {
let response = BatchOrderResponse {
success: false,
error_msg: Some("not enough balance / allowance".to_string()),
order_id: None,
making_amount: None,
taking_amount: None,
status: None,
};
assert!(response.has_error());
}

#[test]
fn test_error_detection_success_true_with_error_msg() {
let response = BatchOrderResponse {
success: true,
error_msg: Some("not enough balance / allowance".to_string()),
order_id: None,
making_amount: None,
taking_amount: None,
status: None,
};
assert!(response.has_error());
}

#[test]
fn test_error_detection_success_true_no_error_msg() {
let response = BatchOrderResponse {
success: true,
error_msg: None,
order_id: Some("order-123".to_string()),
making_amount: Some("100".to_string()),
taking_amount: Some("50".to_string()),
status: Some("filled".to_string()),
};
assert!(!response.has_error());
}
}