1use crate::{
2 constants::{self, Addresses, ZERO_ADDRESS},
3 errors::{Error, Result},
4 internal::{amounts, signing},
5 types::*,
6};
7use alloy::primitives::Address;
8use alloy::signers::local::PrivateKeySigner;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11#[derive(Debug, Clone)]
13pub struct OrderBuilderOptions {
14 pub addresses: Option<Addresses>,
16
17 pub generate_salt: Option<fn() -> String>,
19}
20
21impl Default for OrderBuilderOptions {
22 fn default() -> Self {
23 Self {
24 addresses: None,
25 generate_salt: Some(generate_order_salt),
26 }
27 }
28}
29
30pub fn generate_order_salt() -> String {
32 use rand::Rng;
33 let mut rng = rand::rng();
34 let salt: u64 = rng.random_range(0..constants::MAX_SALT);
35 salt.to_string()
36}
37
38pub struct OrderBuilder {
42 chain_id: ChainId,
43 signer: Option<PrivateKeySigner>,
44 addresses: Addresses,
45 generate_salt: fn() -> String,
46 predict_account: Option<Address>,
49}
50
51impl OrderBuilder {
52 pub fn new(
64 chain_id: ChainId,
65 signer: Option<PrivateKeySigner>,
66 options: Option<OrderBuilderOptions>,
67 ) -> Result<Self> {
68 let opts = options.unwrap_or_default();
69 let addresses = opts.addresses.unwrap_or_else(|| Addresses::for_chain(chain_id));
70 let generate_salt = opts.generate_salt.unwrap_or(generate_order_salt);
71
72 Ok(Self {
73 chain_id,
74 signer,
75 addresses,
76 generate_salt,
77 predict_account: None,
78 })
79 }
80
81 pub fn with_predict_account(
98 chain_id: ChainId,
99 signer: PrivateKeySigner,
100 predict_account: &str,
101 options: Option<OrderBuilderOptions>,
102 ) -> Result<Self> {
103 let opts = options.unwrap_or_default();
104 let addresses = opts.addresses.unwrap_or_else(|| Addresses::for_chain(chain_id));
105 let generate_salt = opts.generate_salt.unwrap_or(generate_order_salt);
106
107 let predict_account_addr = predict_account.parse::<Address>()
108 .map_err(|e| Error::Other(format!("Invalid predict account address: {}", e)))?;
109
110 Ok(Self {
111 chain_id,
112 signer: Some(signer),
113 addresses,
114 generate_salt,
115 predict_account: Some(predict_account_addr),
116 })
117 }
118
119 pub fn uses_predict_account(&self) -> bool {
121 self.predict_account.is_some()
122 }
123
124 pub fn predict_account(&self) -> Option<Address> {
126 self.predict_account
127 }
128
129 pub fn signer_address(&self) -> Result<Address> {
135 self.signer
136 .as_ref()
137 .map(|s| s.address())
138 .ok_or_else(|| Error::Other("No signer configured".to_string()))
139 }
140
141 pub fn signer(&self) -> Option<PrivateKeySigner> {
147 self.signer.clone()
148 }
149
150 pub fn get_limit_order_amounts(&self, data: LimitOrderData) -> Result<LimitOrderAmounts> {
164 amounts::get_limit_order_amounts(data)
165 }
166
167 pub fn build_order(&self, strategy: OrderStrategy, input: BuildOrderInput) -> Result<Order> {
182 let signer_address = self.signer_address()
184 .unwrap_or_else(|_| {
185 input.signer.as_ref()
186 .and_then(|s| s.parse::<Address>().ok())
187 .unwrap_or(ZERO_ADDRESS.parse().unwrap())
188 });
189
190 let signer_str = format!("{}", signer_address);
191
192 let (maker_str, order_signer_str) = if let Some(predict_account) = self.predict_account {
196 let pa = format!("{}", predict_account);
197 (pa.clone(), pa)
198 } else {
199 (signer_str.clone(), signer_str.clone())
200 };
201
202 let expiration = if let Some(expires_at) = input.expires_at {
204 let timestamp = expires_at.timestamp();
205 let now = SystemTime::now()
206 .duration_since(UNIX_EPOCH)
207 .unwrap()
208 .as_secs() as i64;
209
210 if timestamp <= now {
211 return Err(Error::InvalidOrderData("Expiration must be in the future".to_string()));
212 }
213 timestamp.to_string()
214 } else {
215 let now = SystemTime::now()
217 .duration_since(UNIX_EPOCH)
218 .unwrap()
219 .as_secs();
220
221 let expiration_secs = match strategy {
222 OrderStrategy::Market => now + constants::FIVE_MINUTES_SECONDS,
223 OrderStrategy::Limit => now + (365 * 24 * 60 * 60), };
225
226 expiration_secs.to_string()
227 };
228
229 Ok(Order {
230 salt: input.salt.unwrap_or_else(|| (self.generate_salt)()),
231 maker: input.maker.unwrap_or_else(|| maker_str.clone()),
232 signer: input.signer.unwrap_or_else(|| order_signer_str.clone()),
233 taker: input.taker.unwrap_or_else(|| ZERO_ADDRESS.to_string()),
234 token_id: input.token_id,
235 maker_amount: input.maker_amount,
236 taker_amount: input.taker_amount,
237 expiration,
238 nonce: input.nonce.unwrap_or_else(|| "0".to_string()),
239 fee_rate_bps: input.fee_rate_bps.to_string(),
240 side: input.side,
241 signature_type: input.signature_type.unwrap_or(SignatureType::Eoa),
242 })
243 }
244
245 pub fn get_verifying_contract(&self, is_neg_risk: bool, is_yield_bearing: bool) -> Address {
257 let address_str = self.addresses.get_ctf_exchange(is_yield_bearing, is_neg_risk);
258 address_str.parse().unwrap()
259 }
260
261 pub fn build_typed_data_hash(
277 &self,
278 order: &Order,
279 is_neg_risk: bool,
280 is_yield_bearing: bool,
281 ) -> Result<String> {
282 let verifying_contract = self.get_verifying_contract(is_neg_risk, is_yield_bearing);
283 let domain = signing::get_domain(self.chain_id, verifying_contract);
284 let hash = signing::build_typed_data_hash(order, &domain)?;
285 Ok(format!("0x{}", hex::encode(hash.as_slice())))
286 }
287
288 pub async fn sign_typed_data_order(
308 &self,
309 order: Order,
310 is_neg_risk: bool,
311 is_yield_bearing: bool,
312 ) -> Result<SignedOrder> {
313 let signer = self.signer.as_ref()
314 .ok_or_else(|| Error::Other("No signer configured".to_string()))?;
315
316 let verifying_contract = self.get_verifying_contract(is_neg_risk, is_yield_bearing);
317 let hash = self.build_typed_data_hash(&order, is_neg_risk, is_yield_bearing)?;
318
319 let signature = if let Some(predict_account) = self.predict_account {
320 let ecdsa_validator = self.addresses.ecdsa_validator.parse::<Address>()
324 .map_err(|e| Error::Other(format!("Invalid ECDSA validator address: {}", e)))?;
325
326 signing::sign_order_for_predict_account(
327 &order,
328 self.chain_id,
329 verifying_contract,
330 predict_account,
331 ecdsa_validator,
332 signer,
333 ).await?
334 } else {
335 signing::sign_order(&order, self.chain_id, verifying_contract, signer).await?
337 };
338
339 Ok(SignedOrder {
340 order,
341 hash: Some(hash),
342 signature,
343 })
344 }
345
346 pub async fn sign_typed_data_order_eoa(
352 &self,
353 order: Order,
354 is_neg_risk: bool,
355 is_yield_bearing: bool,
356 ) -> Result<SignedOrder> {
357 let signer = self.signer.as_ref()
358 .ok_or_else(|| Error::Other("No signer configured".to_string()))?;
359
360 let verifying_contract = self.get_verifying_contract(is_neg_risk, is_yield_bearing);
361 let hash = self.build_typed_data_hash(&order, is_neg_risk, is_yield_bearing)?;
362
363 let signature = signing::sign_order(&order, self.chain_id, verifying_contract, signer).await?;
365
366 Ok(SignedOrder {
367 order,
368 hash: Some(hash),
369 signature,
370 })
371 }
372
373 pub fn signer_address_string(&self) -> Result<String> {
375 self.signer_address().map(|addr| format!("{}", addr))
376 }
377
378 pub fn chain_id(&self) -> ChainId {
380 self.chain_id
381 }
382
383 pub fn addresses(&self) -> &Addresses {
385 &self.addresses
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use rust_decimal_macros::dec;
393
394 #[test]
395 fn test_new_order_builder() {
396 let builder = OrderBuilder::new(ChainId::BnbTestnet, None, None).unwrap();
397 assert_eq!(builder.chain_id(), ChainId::BnbTestnet);
398 }
399
400 #[test]
401 fn test_get_limit_order_amounts() {
402 let builder = OrderBuilder::new(ChainId::BnbTestnet, None, None).unwrap();
403
404 let data = LimitOrderData {
405 side: Side::Buy,
406 price_per_share_wei: dec!(500000000000000000),
407 quantity_wei: dec!(10000000000000000000),
408 };
409
410 let amounts = builder.get_limit_order_amounts(data).unwrap();
411 assert!(amounts.maker_amount > rust_decimal::Decimal::ZERO);
412 }
413
414 #[test]
415 fn test_build_order() {
416 let signer = PrivateKeySigner::random();
417 let builder = OrderBuilder::new(ChainId::BnbTestnet, Some(signer), None).unwrap();
418
419 let input = BuildOrderInput {
420 side: Side::Buy,
421 token_id: "12345".to_string(),
422 maker_amount: "1000000000000000000".to_string(),
423 taker_amount: "2000000000000000000".to_string(),
424 fee_rate_bps: 100,
425 signer: None,
426 nonce: None,
427 salt: None,
428 maker: None,
429 taker: None,
430 signature_type: None,
431 expires_at: None,
432 };
433
434 let order = builder.build_order(OrderStrategy::Limit, input).unwrap();
435 assert_eq!(order.side, Side::Buy);
436 assert_eq!(order.token_id, "12345");
437 }
438
439 #[tokio::test]
440 async fn test_sign_order() {
441 let signer = PrivateKeySigner::random();
442 let builder = OrderBuilder::new(ChainId::BnbTestnet, Some(signer), None).unwrap();
443
444 let input = BuildOrderInput {
445 side: Side::Buy,
446 token_id: "12345".to_string(),
447 maker_amount: "1000000000000000000".to_string(),
448 taker_amount: "2000000000000000000".to_string(),
449 fee_rate_bps: 100,
450 signer: None,
451 nonce: None,
452 salt: None,
453 maker: None,
454 taker: None,
455 signature_type: None,
456 expires_at: None,
457 };
458
459 let order = builder.build_order(OrderStrategy::Limit, input).unwrap();
460 let signed_order = builder.sign_typed_data_order(order, false, false).await.unwrap();
461
462 assert!(signed_order.signature.starts_with("0x"));
463 assert!(signed_order.hash.is_some());
464 }
465}