1use crate::client::SignerClient;
2use crate::error::Result;
3use crate::models::{
4 ApiResponse, CancelOrderRequest, CreateOrderRequest, Order, OrderFilter, OrderType, Pagination,
5 Side, TimeInForce, Trade,
6};
7use crate::signers::{sign_cancel_payload, sign_order_payload};
8
9#[derive(Debug)]
10pub struct OrderApi {
11 client: SignerClient,
12}
13
14impl OrderApi {
15 pub fn new(client: SignerClient) -> Self {
16 Self { client }
17 }
18
19 pub async fn create_order(
20 &self,
21 symbol: &str,
22 side: Side,
23 order_type: OrderType,
24 quantity: &str,
25 price: Option<&str>,
26 client_order_id: Option<&str>,
27 time_in_force: Option<TimeInForce>,
28 post_only: Option<bool>,
29 reduce_only: Option<bool>,
30 ) -> Result<Order> {
31 let nonce = self.client.generate_nonce()?;
32
33 let side_str = match side {
34 Side::Buy => "BUY",
35 Side::Sell => "SELL",
36 };
37
38 let signature = sign_order_payload(
39 self.client.signer().as_ref(),
40 symbol,
41 side_str,
42 quantity,
43 price,
44 nonce,
45 )?;
46
47 let request = CreateOrderRequest {
48 symbol: symbol.to_string(),
49 side,
50 order_type,
51 quantity: quantity.to_string(),
52 price: price.map(String::from),
53 stop_price: None,
54 client_order_id: client_order_id.map(String::from),
55 time_in_force: time_in_force.unwrap_or(TimeInForce::Gtc),
56 post_only,
57 reduce_only,
58 signature,
59 nonce,
60 };
61
62 let response: ApiResponse<Order> = self
63 .client
64 .api_client()
65 .post("/orders", Some(request))
66 .await?;
67
68 match response.data {
69 Some(order) => Ok(order),
70 None => Err(crate::error::LighterError::OrderValidation(
71 response
72 .error
73 .unwrap_or_else(|| "Failed to create order".to_string()),
74 )),
75 }
76 }
77
78 pub async fn cancel_order(
79 &self,
80 order_id: Option<&str>,
81 client_order_id: Option<&str>,
82 symbol: Option<&str>,
83 ) -> Result<()> {
84 if order_id.is_none() && client_order_id.is_none() {
85 return Err(crate::error::LighterError::OrderValidation(
86 "Either order_id or client_order_id must be provided".to_string(),
87 ));
88 }
89
90 let nonce = self.client.generate_nonce()?;
91
92 let signature = sign_cancel_payload(
93 self.client.signer().as_ref(),
94 order_id,
95 client_order_id,
96 symbol,
97 nonce,
98 )?;
99
100 let request = CancelOrderRequest {
101 order_id: order_id.map(String::from),
102 client_order_id: client_order_id.map(String::from),
103 symbol: symbol.map(String::from),
104 signature,
105 nonce,
106 };
107
108 let response: ApiResponse<()> = self
109 .client
110 .api_client()
111 .post("/orders/cancel", Some(request))
112 .await?;
113
114 if !response.success {
115 return Err(crate::error::LighterError::Api {
116 status: 400,
117 message: response
118 .error
119 .unwrap_or_else(|| "Failed to cancel order".to_string()),
120 });
121 }
122
123 Ok(())
124 }
125
126 pub async fn cancel_all_orders(&self, symbol: Option<&str>) -> Result<u32> {
127 let nonce = self.client.generate_nonce()?;
128
129 let signature =
130 sign_cancel_payload(self.client.signer().as_ref(), None, None, symbol, nonce)?;
131
132 let request = CancelOrderRequest {
133 order_id: None,
134 client_order_id: None,
135 symbol: symbol.map(String::from),
136 signature,
137 nonce,
138 };
139
140 let response: ApiResponse<serde_json::Value> = self
141 .client
142 .api_client()
143 .post("/orders/cancel-all", Some(request))
144 .await?;
145
146 match response.data {
147 Some(data) => {
148 let cancelled_count = data
149 .get("cancelled_count")
150 .and_then(|v| v.as_u64())
151 .unwrap_or(0) as u32;
152 Ok(cancelled_count)
153 }
154 None => Err(crate::error::LighterError::Api {
155 status: 400,
156 message: response
157 .error
158 .unwrap_or_else(|| "Failed to cancel all orders".to_string()),
159 }),
160 }
161 }
162
163 pub async fn get_order(&self, order_id: &str) -> Result<Order> {
164 let response: ApiResponse<Order> = self
165 .client
166 .api_client()
167 .get(&format!("/orders/{}", order_id))
168 .await?;
169
170 match response.data {
171 Some(order) => Ok(order),
172 None => Err(crate::error::LighterError::Api {
173 status: 404,
174 message: response
175 .error
176 .unwrap_or_else(|| "Order not found".to_string()),
177 }),
178 }
179 }
180
181 pub async fn get_orders(
182 &self,
183 filter: Option<OrderFilter>,
184 ) -> Result<(Vec<Order>, Option<Pagination>)> {
185 let mut query_params = Vec::new();
186
187 if let Some(filter) = filter {
188 if let Some(symbol) = filter.symbol {
189 query_params.push(format!("symbol={}", symbol));
190 }
191 if let Some(status) = filter.status {
192 query_params.push(format!("status={:?}", status));
193 }
194 if let Some(side) = filter.side {
195 query_params.push(format!("side={:?}", side));
196 }
197 if let Some(order_type) = filter.order_type {
198 query_params.push(format!("order_type={:?}", order_type));
199 }
200 if let Some(page) = filter.page {
201 query_params.push(format!("page={}", page));
202 }
203 if let Some(limit) = filter.limit {
204 query_params.push(format!("limit={}", limit));
205 }
206 }
207
208 let endpoint = if query_params.is_empty() {
209 "/orders".to_string()
210 } else {
211 format!("/orders?{}", query_params.join("&"))
212 };
213
214 let response: ApiResponse<serde_json::Value> =
215 self.client.api_client().get(&endpoint).await?;
216
217 match response.data {
218 Some(data) => {
219 let orders: Vec<Order> = serde_json::from_value(
220 data.get("orders")
221 .cloned()
222 .unwrap_or(serde_json::Value::Array(vec![])),
223 )
224 .unwrap_or_default();
225
226 let pagination: Option<Pagination> = data
227 .get("pagination")
228 .and_then(|p| serde_json::from_value(p.clone()).ok());
229
230 Ok((orders, pagination))
231 }
232 None => Err(crate::error::LighterError::Api {
233 status: 500,
234 message: response
235 .error
236 .unwrap_or_else(|| "Failed to fetch orders".to_string()),
237 }),
238 }
239 }
240
241 pub async fn get_trades(&self, symbol: Option<&str>) -> Result<Vec<Trade>> {
242 let endpoint = match symbol {
243 Some(sym) => format!("/trades?symbol={}", sym),
244 None => "/trades".to_string(),
245 };
246
247 let response: ApiResponse<Vec<Trade>> = self.client.api_client().get(&endpoint).await?;
248
249 match response.data {
250 Some(trades) => Ok(trades),
251 None => Err(crate::error::LighterError::Api {
252 status: 500,
253 message: response
254 .error
255 .unwrap_or_else(|| "Failed to fetch trades".to_string()),
256 }),
257 }
258 }
259}