lightcone_sdk/program/
builder.rs1use rust_decimal::Decimal;
4use solana_pubkey::Pubkey;
5
6#[cfg(feature = "client")]
7use solana_keypair::Keypair;
8
9use crate::program::orders::FullOrder;
10use crate::program::types::OrderSide;
11use crate::shared::scaling::{scale_price_size, OrderbookDecimals, ScalingError};
12use crate::shared::SubmitOrderRequest;
13
14#[derive(Debug, Clone, Default)]
37pub struct OrderBuilder {
38 nonce: Option<u64>,
39 maker: Option<Pubkey>,
40 market: Option<Pubkey>,
41 base_mint: Option<Pubkey>,
42 quote_mint: Option<Pubkey>,
43 side: Option<OrderSide>,
44 maker_amount: Option<u64>,
45 taker_amount: Option<u64>,
46 expiration: i64,
47 price_raw: Option<String>,
48 size_raw: Option<String>,
49}
50
51impl OrderBuilder {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn nonce(mut self, nonce: u64) -> Self {
61 self.nonce = Some(nonce);
62 self
63 }
64
65 pub fn maker(mut self, maker: Pubkey) -> Self {
69 self.maker = Some(maker);
70 self
71 }
72
73 pub fn market(mut self, market: Pubkey) -> Self {
75 self.market = Some(market);
76 self
77 }
78
79 pub fn base_mint(mut self, base_mint: Pubkey) -> Self {
81 self.base_mint = Some(base_mint);
82 self
83 }
84
85 pub fn quote_mint(mut self, quote_mint: Pubkey) -> Self {
87 self.quote_mint = Some(quote_mint);
88 self
89 }
90
91 pub fn bid(mut self) -> Self {
93 self.side = Some(OrderSide::Bid);
94 self
95 }
96
97 pub fn ask(mut self) -> Self {
99 self.side = Some(OrderSide::Ask);
100 self
101 }
102
103 pub fn side(mut self, side: OrderSide) -> Self {
105 self.side = Some(side);
106 self
107 }
108
109 pub fn maker_amount(mut self, amount: u64) -> Self {
111 self.maker_amount = Some(amount);
112 self
113 }
114
115 pub fn taker_amount(mut self, amount: u64) -> Self {
117 self.taker_amount = Some(amount);
118 self
119 }
120
121 pub fn expiration(mut self, expiration: i64) -> Self {
123 self.expiration = expiration;
124 self
125 }
126
127 pub fn build(self) -> FullOrder {
136 FullOrder {
137 nonce: self.nonce.expect("nonce is required"),
138 maker: self.maker.expect("maker is required"),
139 market: self.market.expect("market is required"),
140 base_mint: self.base_mint.expect("base_mint is required"),
141 quote_mint: self.quote_mint.expect("quote_mint is required"),
142 side: self.side.expect("side is required (call .bid() or .ask())"),
143 maker_amount: self.maker_amount.expect("maker_amount is required"),
144 taker_amount: self.taker_amount.expect("taker_amount is required"),
145 expiration: self.expiration,
146 signature: [0u8; 64],
147 }
148 }
149
150 #[cfg(feature = "client")]
158 pub fn build_and_sign(self, keypair: &Keypair) -> FullOrder {
159 let mut order = self.build();
160 order.sign(keypair);
161 order
162 }
163
164 #[cfg(feature = "client")]
175 pub fn to_submit_request(
176 self,
177 keypair: &Keypair,
178 orderbook_id: impl Into<String>,
179 ) -> SubmitOrderRequest {
180 self.build_and_sign(keypair).to_submit_request(orderbook_id)
181 }
182
183 pub fn price(mut self, price: &str) -> Self {
189 self.price_raw = Some(price.to_string());
190 self
191 }
192
193 pub fn size(mut self, size: &str) -> Self {
195 self.size_raw = Some(size.to_string());
196 self
197 }
198
199 pub fn apply_scaling(mut self, decimals: &OrderbookDecimals) -> Result<Self, ScalingError> {
204 let price_str = self
205 .price_raw
206 .as_deref()
207 .expect("price() is required for apply_scaling");
208 let size_str = self
209 .size_raw
210 .as_deref()
211 .expect("size() is required for apply_scaling");
212
213 let price: Decimal =
214 price_str
215 .parse()
216 .map_err(|e: rust_decimal::Error| ScalingError::InvalidDecimal {
217 input: price_str.to_string(),
218 reason: e.to_string(),
219 })?;
220
221 let size: Decimal =
222 size_str
223 .parse()
224 .map_err(|e: rust_decimal::Error| ScalingError::InvalidDecimal {
225 input: size_str.to_string(),
226 reason: e.to_string(),
227 })?;
228
229 let side = self
230 .side
231 .expect("side is required (call .bid() or .ask()) for apply_scaling");
232
233 let scaled = scale_price_size(price, size, side, decimals)?;
234 self.maker_amount = Some(scaled.maker_amount);
235 self.taker_amount = Some(scaled.taker_amount);
236 Ok(self)
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 #[cfg(feature = "client")]
244 use solana_signer::Signer;
245
246 #[test]
247 #[cfg(feature = "client")]
248 fn test_order_builder_basic() {
249 let keypair = Keypair::new();
250 let maker = keypair.pubkey();
251 let market = Pubkey::new_unique();
252 let base_mint = Pubkey::new_unique();
253 let quote_mint = Pubkey::new_unique();
254
255 let order = OrderBuilder::new()
256 .nonce(1)
257 .maker(maker)
258 .market(market)
259 .base_mint(base_mint)
260 .quote_mint(quote_mint)
261 .bid()
262 .maker_amount(1_000_000)
263 .taker_amount(500_000)
264 .build_and_sign(&keypair);
265
266 assert_eq!(order.nonce, 1);
267 assert_eq!(order.maker, maker);
268 assert_eq!(order.market, market);
269 assert_eq!(order.base_mint, base_mint);
270 assert_eq!(order.quote_mint, quote_mint);
271 assert_eq!(order.side, OrderSide::Bid);
272 assert_eq!(order.maker_amount, 1_000_000);
273 assert_eq!(order.taker_amount, 500_000);
274 assert!(order.is_signed());
275 }
276
277 #[test]
278 #[cfg(feature = "client")]
279 fn test_order_builder_to_submit_request() {
280 let keypair = Keypair::new();
281 let maker = keypair.pubkey();
282 let market = Pubkey::new_unique();
283 let base_mint = Pubkey::new_unique();
284 let quote_mint = Pubkey::new_unique();
285
286 let request = OrderBuilder::new()
287 .nonce(1)
288 .maker(maker)
289 .market(market)
290 .base_mint(base_mint)
291 .quote_mint(quote_mint)
292 .ask()
293 .maker_amount(500_000)
294 .taker_amount(1_000_000)
295 .to_submit_request(&keypair, "test_orderbook");
296
297 assert_eq!(request.maker, maker.to_string());
298 assert_eq!(request.nonce, 1);
299 assert_eq!(request.market_pubkey, market.to_string());
300 assert_eq!(request.base_token, base_mint.to_string());
301 assert_eq!(request.quote_token, quote_mint.to_string());
302 assert_eq!(request.side, 1); assert_eq!(request.maker_amount, 500_000);
304 assert_eq!(request.taker_amount, 1_000_000);
305 assert_eq!(request.orderbook_id, "test_orderbook");
306 assert_eq!(request.signature.len(), 128); }
308
309 #[test]
310 fn test_order_builder_unsigned() {
311 #[cfg(feature = "client")]
312 let keypair = Keypair::new();
313 #[cfg(feature = "client")]
314 let maker = keypair.pubkey();
315 #[cfg(not(feature = "client"))]
316 let maker = Pubkey::new_unique();
317
318 let order = OrderBuilder::new()
319 .nonce(1)
320 .maker(maker)
321 .market(Pubkey::new_unique())
322 .base_mint(Pubkey::new_unique())
323 .quote_mint(Pubkey::new_unique())
324 .bid()
325 .maker_amount(1_000_000)
326 .taker_amount(500_000)
327 .build();
328
329 assert!(!order.is_signed());
330 }
331
332 #[test]
333 #[cfg(feature = "client")]
334 #[should_panic(expected = "nonce is required")]
335 fn test_order_builder_missing_nonce() {
336 let keypair = Keypair::new();
337 OrderBuilder::new()
338 .maker(keypair.pubkey())
339 .market(Pubkey::new_unique())
340 .base_mint(Pubkey::new_unique())
341 .quote_mint(Pubkey::new_unique())
342 .bid()
343 .maker_amount(1_000_000)
344 .taker_amount(500_000)
345 .build_and_sign(&keypair);
346 }
347
348 #[test]
349 #[cfg(feature = "client")]
350 #[should_panic(expected = "side is required")]
351 fn test_order_builder_missing_side() {
352 let keypair = Keypair::new();
353 OrderBuilder::new()
354 .nonce(1)
355 .maker(keypair.pubkey())
356 .market(Pubkey::new_unique())
357 .base_mint(Pubkey::new_unique())
358 .quote_mint(Pubkey::new_unique())
359 .maker_amount(1_000_000)
360 .taker_amount(500_000)
361 .build_and_sign(&keypair);
362 }
363}