Skip to main content

lightcone_sdk/program/
builder.rs

1//! Fluent builder for creating and signing orders.
2
3use solana_sdk::{pubkey::Pubkey, signature::Keypair};
4
5use crate::program::orders::FullOrder;
6use crate::program::types::OrderSide;
7use crate::shared::SubmitOrderRequest;
8
9/// Builder for creating orders with a fluent API.
10///
11/// Provides a convenient way to construct, sign, and convert orders
12/// for API submission in a single chain of method calls.
13///
14/// # Example
15///
16/// ```rust,ignore
17/// use lightcone_sdk::prelude::*;
18///
19/// let request = OrderBuilder::new()
20///     .maker(maker_pubkey)
21///     .market(market_pubkey)
22///     .base_mint(yes_token)
23///     .quote_mint(usdc)
24///     .bid()
25///     .nonce(5)
26///     .maker_amount(1_000_000)
27///     .taker_amount(500_000)
28///     .build_and_sign(&keypair)
29///     .to_submit_request("orderbook_id");
30/// ```
31#[derive(Debug, Clone, Default)]
32pub struct OrderBuilder {
33    nonce: Option<u64>,
34    maker: Option<Pubkey>,
35    market: Option<Pubkey>,
36    base_mint: Option<Pubkey>,
37    quote_mint: Option<Pubkey>,
38    side: Option<OrderSide>,
39    maker_amount: Option<u64>,
40    taker_amount: Option<u64>,
41    expiration: i64,
42}
43
44impl OrderBuilder {
45    /// Create a new order builder.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Set the nonce (required).
51    ///
52    /// The nonce must be >= the user's on-chain nonce for the order to be valid.
53    pub fn nonce(mut self, nonce: u64) -> Self {
54        self.nonce = Some(nonce);
55        self
56    }
57
58    /// Set the maker pubkey (required).
59    ///
60    /// This is the public key of the order creator.
61    pub fn maker(mut self, maker: Pubkey) -> Self {
62        self.maker = Some(maker);
63        self
64    }
65
66    /// Set the market pubkey (required).
67    pub fn market(mut self, market: Pubkey) -> Self {
68        self.market = Some(market);
69        self
70    }
71
72    /// Set the base mint / token being bought or sold (required).
73    pub fn base_mint(mut self, base_mint: Pubkey) -> Self {
74        self.base_mint = Some(base_mint);
75        self
76    }
77
78    /// Set the quote mint / payment token (required).
79    pub fn quote_mint(mut self, quote_mint: Pubkey) -> Self {
80        self.quote_mint = Some(quote_mint);
81        self
82    }
83
84    /// Set as a bid order (buy base with quote).
85    pub fn bid(mut self) -> Self {
86        self.side = Some(OrderSide::Bid);
87        self
88    }
89
90    /// Set as an ask order (sell base for quote).
91    pub fn ask(mut self) -> Self {
92        self.side = Some(OrderSide::Ask);
93        self
94    }
95
96    /// Set the side directly.
97    pub fn side(mut self, side: OrderSide) -> Self {
98        self.side = Some(side);
99        self
100    }
101
102    /// Set the amount the maker gives.
103    pub fn maker_amount(mut self, amount: u64) -> Self {
104        self.maker_amount = Some(amount);
105        self
106    }
107
108    /// Set the amount the maker wants to receive.
109    pub fn taker_amount(mut self, amount: u64) -> Self {
110        self.taker_amount = Some(amount);
111        self
112    }
113
114    /// Set expiration timestamp (0 = no expiration).
115    pub fn expiration(mut self, expiration: i64) -> Self {
116        self.expiration = expiration;
117        self
118    }
119
120    /// Build an unsigned FullOrder.
121    ///
122    /// The returned order has an all-zero signature and must be signed
123    /// before submission.
124    ///
125    /// # Panics
126    ///
127    /// Panics if required fields are missing.
128    pub fn build(self) -> FullOrder {
129        FullOrder {
130            nonce: self.nonce.expect("nonce is required"),
131            maker: self.maker.expect("maker is required"),
132            market: self.market.expect("market is required"),
133            base_mint: self.base_mint.expect("base_mint is required"),
134            quote_mint: self.quote_mint.expect("quote_mint is required"),
135            side: self.side.expect("side is required (call .bid() or .ask())"),
136            maker_amount: self.maker_amount.expect("maker_amount is required"),
137            taker_amount: self.taker_amount.expect("taker_amount is required"),
138            expiration: self.expiration,
139            signature: [0u8; 64],
140        }
141    }
142
143    /// Build and sign the order with the given keypair.
144    ///
145    /// Returns a signed FullOrder ready for API submission.
146    ///
147    /// # Panics
148    ///
149    /// Panics if required fields are missing.
150    pub fn build_and_sign(self, keypair: &Keypair) -> FullOrder {
151        let mut order = self.build();
152        order.sign(keypair);
153        order
154    }
155
156    /// Build, sign, and convert directly to a SubmitOrderRequest.
157    ///
158    /// # Arguments
159    ///
160    /// * `keypair` - Keypair to sign the order with
161    /// * `orderbook_id` - Target orderbook ID
162    ///
163    /// # Panics
164    ///
165    /// Panics if required fields are missing.
166    pub fn to_submit_request(
167        self,
168        keypair: &Keypair,
169        orderbook_id: impl Into<String>,
170    ) -> SubmitOrderRequest {
171        self.build_and_sign(keypair).to_submit_request(orderbook_id)
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use solana_sdk::signer::Signer;
179
180    #[test]
181    fn test_order_builder_basic() {
182        let keypair = Keypair::new();
183        let maker = keypair.pubkey();
184        let market = Pubkey::new_unique();
185        let base_mint = Pubkey::new_unique();
186        let quote_mint = Pubkey::new_unique();
187
188        let order = OrderBuilder::new()
189            .nonce(1)
190            .maker(maker)
191            .market(market)
192            .base_mint(base_mint)
193            .quote_mint(quote_mint)
194            .bid()
195            .maker_amount(1_000_000)
196            .taker_amount(500_000)
197            .build_and_sign(&keypair);
198
199        assert_eq!(order.nonce, 1);
200        assert_eq!(order.maker, maker);
201        assert_eq!(order.market, market);
202        assert_eq!(order.base_mint, base_mint);
203        assert_eq!(order.quote_mint, quote_mint);
204        assert_eq!(order.side, OrderSide::Bid);
205        assert_eq!(order.maker_amount, 1_000_000);
206        assert_eq!(order.taker_amount, 500_000);
207        assert!(order.is_signed());
208    }
209
210    #[test]
211    fn test_order_builder_to_submit_request() {
212        let keypair = Keypair::new();
213        let maker = keypair.pubkey();
214        let market = Pubkey::new_unique();
215        let base_mint = Pubkey::new_unique();
216        let quote_mint = Pubkey::new_unique();
217
218        let request = OrderBuilder::new()
219            .nonce(1)
220            .maker(maker)
221            .market(market)
222            .base_mint(base_mint)
223            .quote_mint(quote_mint)
224            .ask()
225            .maker_amount(500_000)
226            .taker_amount(1_000_000)
227            .to_submit_request(&keypair, "test_orderbook");
228
229        assert_eq!(request.maker, maker.to_string());
230        assert_eq!(request.nonce, 1);
231        assert_eq!(request.market_pubkey, market.to_string());
232        assert_eq!(request.base_token, base_mint.to_string());
233        assert_eq!(request.quote_token, quote_mint.to_string());
234        assert_eq!(request.side, 1); // Ask
235        assert_eq!(request.maker_amount, 500_000);
236        assert_eq!(request.taker_amount, 1_000_000);
237        assert_eq!(request.orderbook_id, "test_orderbook");
238        assert_eq!(request.signature.len(), 128); // 64 bytes = 128 hex chars
239    }
240
241    #[test]
242    fn test_order_builder_unsigned() {
243        let keypair = Keypair::new();
244        let order = OrderBuilder::new()
245            .nonce(1)
246            .maker(keypair.pubkey())
247            .market(Pubkey::new_unique())
248            .base_mint(Pubkey::new_unique())
249            .quote_mint(Pubkey::new_unique())
250            .bid()
251            .maker_amount(1_000_000)
252            .taker_amount(500_000)
253            .build();
254
255        assert!(!order.is_signed());
256    }
257
258    #[test]
259    #[should_panic(expected = "nonce is required")]
260    fn test_order_builder_missing_nonce() {
261        let keypair = Keypair::new();
262        OrderBuilder::new()
263            .maker(keypair.pubkey())
264            .market(Pubkey::new_unique())
265            .base_mint(Pubkey::new_unique())
266            .quote_mint(Pubkey::new_unique())
267            .bid()
268            .maker_amount(1_000_000)
269            .taker_amount(500_000)
270            .build_and_sign(&keypair);
271    }
272
273    #[test]
274    #[should_panic(expected = "side is required")]
275    fn test_order_builder_missing_side() {
276        let keypair = Keypair::new();
277        OrderBuilder::new()
278            .nonce(1)
279            .maker(keypair.pubkey())
280            .market(Pubkey::new_unique())
281            .base_mint(Pubkey::new_unique())
282            .quote_mint(Pubkey::new_unique())
283            .maker_amount(1_000_000)
284            .taker_amount(500_000)
285            .build_and_sign(&keypair);
286    }
287}