use crate::client::ClobClient;
use crate::constants::{END_CURSOR, INITIAL_CURSOR};
use crate::endpoints::endpoints;
use crate::errors::{ClobError, ClobResult};
use crate::headers::create_l2_headers;
use crate::order_builder::{calculate_buy_market_price, calculate_sell_market_price};
use crate::types::*;
use rs_order_utils::v2::SignedOrder;
use std::collections::HashMap;
impl ClobClient {
pub async fn create_limit_order(
&self,
user_limit_order: &UserLimitOrder,
options: Option<CreateOrderOptions>,
) -> ClobResult<serde_json::Value> {
self.can_l1_auth()?;
let token_id = &user_limit_order.token_id;
let tick_size = if let Some(opts) = &options {
opts.tick_size
} else {
self.get_tick_size(token_id).await?
};
let neg_risk = if let Some(opts) = &options {
opts.neg_risk.unwrap_or(false)
} else {
self.get_neg_risk(token_id).await?
};
let create_options = CreateOrderOptions {
tick_size,
neg_risk: Some(neg_risk),
};
let order_builder = self
.order_builder
.as_ref()
.ok_or(ClobError::L1AuthUnavailable)?;
let signed_order = order_builder
.build_limit_order(user_limit_order, &create_options)
.await?;
self.signed_order_to_json(signed_order)
}
pub async fn create_market_order(
&self,
user_market_order: &UserMarketOrder,
options: Option<CreateOrderOptions>,
) -> ClobResult<serde_json::Value> {
self.can_l1_auth()?;
let token_id = &user_market_order.token_id;
let tick_size = if let Some(opts) = &options {
opts.tick_size
} else {
self.get_tick_size(token_id).await?
};
let neg_risk = if let Some(opts) = &options {
opts.neg_risk.unwrap_or(false)
} else {
self.get_neg_risk(token_id).await?
};
let create_options = CreateOrderOptions {
tick_size,
neg_risk: Some(neg_risk),
};
let mut order = user_market_order.clone();
if order.price.is_none() {
let price = self
.calculate_market_price(
token_id,
order.side,
order.amount,
order.order_type.unwrap_or(OrderType::Fok),
)
.await?;
order.price = Some(price);
}
let order_builder = self
.order_builder
.as_ref()
.ok_or(ClobError::L1AuthUnavailable)?;
let signed_order = order_builder
.build_market_order(&order, &create_options)
.await?;
self.signed_order_to_json(signed_order)
}
pub async fn create_and_post_limit_order(
&self,
user_limit_order: &UserLimitOrder,
options: Option<CreateOrderOptions>,
order_type: OrderType,
) -> ClobResult<serde_json::Value> {
let order = self.create_limit_order(user_limit_order, options).await?;
self.post_order(order, order_type).await
}
pub async fn create_and_post_market_order(
&self,
user_market_order: &UserMarketOrder,
options: Option<CreateOrderOptions>,
order_type: OrderType,
) -> ClobResult<serde_json::Value> {
let order = self.create_market_order(user_market_order, options).await?;
self.post_order(order, order_type).await
}
pub async fn get_trades(&self, params: Option<TradeParams>) -> ClobResult<Vec<Trade>> {
self.can_l2_auth()?;
let mut results = Vec::new();
let mut next_cursor = INITIAL_CURSOR.to_string();
while next_cursor != END_CURSOR {
let response = self
.get_trades_paginated(params.clone(), Some(next_cursor.clone()))
.await?;
next_cursor = response.next_cursor;
results.extend(response.data);
}
Ok(results)
}
pub async fn get_trades_paginated(
&self,
params: Option<TradeParams>,
cursor: Option<String>,
) -> ClobResult<TradesPaginatedResponse> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let endpoint_path = endpoints::GET_TRADES;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(wallet, creds, "GET", endpoint_path, None, timestamp)
.await?
.to_headers();
let mut query_params = HashMap::new();
query_params.insert(
"next_cursor".to_string(),
cursor.unwrap_or_else(|| INITIAL_CURSOR.to_string()),
);
if let Some(p) = params {
if let Some(id) = p.id {
query_params.insert("id".to_string(), id);
}
if let Some(market) = p.market {
query_params.insert("market".to_string(), market);
}
if let Some(asset_id) = p.asset_id {
query_params.insert("asset_id".to_string(), asset_id);
}
if let Some(maker) = p.maker_address {
query_params.insert("maker_address".to_string(), maker);
}
if let Some(before) = p.before {
query_params.insert("before".to_string(), before.to_string());
}
if let Some(after) = p.after {
query_params.insert("after".to_string(), after.to_string());
}
}
self.http_client
.get(endpoint_path, Some(headers), Some(query_params))
.await
}
pub async fn get_open_order(&self, order_id: &str) -> ClobResult<OpenOrder> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let endpoint_path = format!("{}{}", endpoints::GET_ORDER, order_id);
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(wallet, creds, "GET", &endpoint_path, None, timestamp)
.await?
.to_headers();
self.http_client
.get(&endpoint_path, Some(headers), None)
.await
}
pub async fn get_open_orders(
&self,
params: Option<OpenOrderParams>,
) -> ClobResult<OpenOrdersResponse> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let endpoint_path = endpoints::GET_OPEN_ORDERS;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(wallet, creds, "GET", endpoint_path, None, timestamp)
.await?
.to_headers();
let mut query_params = HashMap::new();
if let Some(p) = params {
if let Some(id) = p.id {
query_params.insert("id".to_string(), id);
}
if let Some(market) = p.market {
query_params.insert("market".to_string(), market);
}
if let Some(asset_id) = p.asset_id {
query_params.insert("asset_id".to_string(), asset_id);
}
}
self.http_client
.get(
endpoint_path,
Some(headers),
(!query_params.is_empty()).then_some(query_params),
)
.await
}
pub async fn post_order(
&self,
order: serde_json::Value,
order_type: OrderType,
) -> ClobResult<serde_json::Value> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let order_payload = self.order_to_json(order, order_type)?;
let body = serde_json::to_string(&order_payload)?;
let endpoint_path = endpoints::POST_ORDER;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers =
create_l2_headers(wallet, creds, "POST", endpoint_path, Some(&body), timestamp).await?;
let final_headers = if self.can_builder_auth() {
match self
._generate_builder_headers(headers.clone(), "POST", endpoint_path, Some(&body))
.await?
{
Some(builder_headers) => builder_headers.to_headers(),
None => headers.to_headers(),
}
} else {
headers.to_headers()
};
self.http_client
.post(
endpoint_path,
Some(final_headers),
Some(order_payload),
None,
)
.await
}
pub async fn post_orders(&self, orders: Vec<PostOrdersArgs>) -> ClobResult<serde_json::Value> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let owner = &creds.key;
let payloads: Vec<_> = orders
.iter()
.map(|arg| {
serde_json::json!({
"order": arg.order,
"owner": owner,
"orderType": arg.order_type,
"deferExec": false
})
})
.collect();
let body = serde_json::to_string(&payloads)?;
let endpoint_path = endpoints::POST_ORDERS;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers =
create_l2_headers(wallet, creds, "POST", endpoint_path, Some(&body), timestamp).await?;
let final_headers = if self.can_builder_auth() {
match self
._generate_builder_headers(headers.clone(), "POST", endpoint_path, Some(&body))
.await?
{
Some(builder_headers) => builder_headers.to_headers(),
None => headers.to_headers(),
}
} else {
headers.to_headers()
};
self.http_client
.post(endpoint_path, Some(final_headers), Some(payloads), None)
.await
}
pub async fn cancel_order(&self, order_id: &str) -> ClobResult<serde_json::Value> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let payload = OrderPayload {
order_id: order_id.to_string(),
};
let body = serde_json::to_string(&payload)?;
let endpoint_path = endpoints::CANCEL_ORDER;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(
wallet,
creds,
"DELETE",
endpoint_path,
Some(&body),
timestamp,
)
.await?
.to_headers();
self.http_client
.delete(endpoint_path, Some(headers), Some(payload), None)
.await
}
pub async fn cancel_orders(&self, order_ids: Vec<String>) -> ClobResult<serde_json::Value> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
#[derive(serde::Serialize)]
struct CancelOrdersPayload {
order_ids: Vec<String>,
}
let payload = CancelOrdersPayload { order_ids };
let body = serde_json::to_string(&payload)?;
let endpoint_path = endpoints::CANCEL_ORDERS;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(
wallet,
creds,
"DELETE",
endpoint_path,
Some(&body),
timestamp,
)
.await?
.to_headers();
self.http_client
.delete(endpoint_path, Some(headers), Some(payload), None)
.await
}
pub async fn cancel_all(&self) -> ClobResult<serde_json::Value> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let endpoint_path = endpoints::CANCEL_ALL;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(wallet, creds, "DELETE", endpoint_path, None, timestamp)
.await?
.to_headers();
self.http_client
.delete(endpoint_path, Some(headers), None::<()>, None)
.await
}
pub async fn cancel_market_orders(
&self,
params: OrderMarketCancelParams,
) -> ClobResult<serde_json::Value> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let body = serde_json::to_string(¶ms)?;
let endpoint_path = endpoints::CANCEL_MARKET_ORDERS;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(
wallet,
creds,
"DELETE",
endpoint_path,
Some(&body),
timestamp,
)
.await?
.to_headers();
self.http_client
.delete(endpoint_path, Some(headers), Some(params), None)
.await
}
pub async fn get_builder_trades(
&self,
params: Option<TradeParams>,
cursor: Option<String>,
) -> ClobResult<BuilderTradesResponse> {
self.must_builder_auth()?;
let endpoint_path = endpoints::GET_BUILDER_TRADES;
let headers = self
._get_builder_headers("GET", endpoint_path, None)
.await?;
let mut query_params = HashMap::new();
query_params.insert(
"next_cursor".to_string(),
cursor.unwrap_or_else(|| INITIAL_CURSOR.to_string()),
);
if let Some(p) = params {
if let Some(id) = p.id {
query_params.insert("id".to_string(), id);
}
if let Some(market) = p.market {
query_params.insert("market".to_string(), market);
}
if let Some(asset_id) = p.asset_id {
query_params.insert("asset_id".to_string(), asset_id);
}
}
self.http_client
.get(endpoint_path, Some(headers), Some(query_params))
.await
}
pub async fn calculate_market_price(
&self,
token_id: &str,
side: Side,
amount: f64,
order_type: OrderType,
) -> ClobResult<f64> {
let orderbook = self.get_order_book(token_id).await?;
match side {
Side::Buy => {
if orderbook.asks.is_empty() {
return Err(ClobError::NoMatch);
}
calculate_buy_market_price(&orderbook.asks, amount, order_type)
}
Side::Sell => {
if orderbook.bids.is_empty() {
return Err(ClobError::NoMatch);
}
calculate_sell_market_price(&orderbook.bids, amount, order_type)
}
}
}
fn order_to_json(
&self,
order: serde_json::Value,
order_type: OrderType,
) -> ClobResult<serde_json::Value> {
let owner = self
.creds
.as_ref()
.ok_or(ClobError::L2AuthNotAvailable)?
.key
.clone();
Ok(serde_json::json!({
"order": order,
"owner": owner,
"orderType": order_type,
}))
}
fn signed_order_to_json(&self, signed_order: SignedOrder) -> ClobResult<serde_json::Value> {
serde_json::to_value(&signed_order).map_err(ClobError::JsonError)
}
pub async fn get_pre_migration_orders(&self) -> ClobResult<Vec<OpenOrder>> {
self.can_l2_auth()?;
let mut results = Vec::new();
let mut next_cursor = INITIAL_CURSOR.to_string();
while next_cursor != END_CURSOR {
let page = self
.get_pre_migration_orders_paginated(Some(next_cursor))
.await?;
results.extend(page.data);
next_cursor = page.next_cursor;
}
Ok(results)
}
pub async fn get_pre_migration_orders_paginated(
&self,
cursor: Option<String>,
) -> ClobResult<PreMigrationOrdersPaginatedResponse> {
self.can_l2_auth()?;
let wallet = self.wallet.as_ref().ok_or(ClobError::L1AuthUnavailable)?;
let creds = self.creds.as_ref().ok_or(ClobError::L2AuthNotAvailable)?;
let endpoint_path = endpoints::GET_PRE_MIGRATION_ORDERS;
let timestamp = if self.use_server_time {
Some(self.get_server_time().await?)
} else {
None
};
let headers = create_l2_headers(wallet, creds, "GET", endpoint_path, None, timestamp)
.await?
.to_headers();
let mut query_params = HashMap::new();
query_params.insert(
"next_cursor".to_string(),
cursor.unwrap_or_else(|| INITIAL_CURSOR.to_string()),
);
self.http_client
.get(endpoint_path, Some(headers), Some(query_params))
.await
}
}