lighter_rust/api/
order.rs

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}