1use crate::errors::ClobError;
2use crate::exchange_order_builder::ExchangeOrderBuilder;
3use crate::signing::Eip712Signer;
4use crate::types::{OrderData, Side, SignatureType, SignedOrder};
5use crate::utilities::{decimal_places, round_down, round_normal, round_up};
6use rust_decimal::Decimal;
7use rust_decimal::prelude::*;
8
9#[derive(Debug, Clone)]
10pub struct RoundConfig {
11 pub price: u32,
12 pub size: u32,
13 pub amount: u32,
14}
15
16use std::collections::HashMap;
17
18pub fn rounding_config() -> HashMap<&'static str, RoundConfig> {
19 let mut m = HashMap::new();
20 m.insert(
21 "0.1",
22 RoundConfig {
23 price: 1,
24 size: 2,
25 amount: 3,
26 },
27 );
28 m.insert(
29 "0.01",
30 RoundConfig {
31 price: 2,
32 size: 2,
33 amount: 4,
34 },
35 );
36 m.insert(
37 "0.001",
38 RoundConfig {
39 price: 3,
40 size: 2,
41 amount: 5,
42 },
43 );
44 m.insert(
45 "0.0001",
46 RoundConfig {
47 price: 4,
48 size: 2,
49 amount: 6,
50 },
51 );
52 m
53}
54
55pub struct RawAmounts {
56 pub side: Side,
57 pub raw_maker_amt: Decimal,
58 pub raw_taker_amt: Decimal,
59}
60
61pub fn get_order_raw_amounts(
62 side: Side,
63 size: Decimal,
64 price: Decimal,
65 round_config: &RoundConfig,
66) -> RawAmounts {
67 let raw_price = round_normal(price, round_config.price);
68
69 if let Side::BUY = side {
70 let raw_taker_amt = round_down(size, round_config.size);
71 let mut raw_maker_amt = raw_taker_amt * raw_price;
72 if decimal_places(raw_maker_amt) > round_config.amount {
73 raw_maker_amt = round_up(raw_maker_amt, round_config.amount + 4);
74 if decimal_places(raw_maker_amt) > round_config.amount {
75 raw_maker_amt = round_down(raw_maker_amt, round_config.amount);
76 }
77 }
78 RawAmounts {
79 side: Side::BUY,
80 raw_maker_amt,
81 raw_taker_amt,
82 }
83 } else {
84 let raw_maker_amt = round_down(size, round_config.size);
85 let mut raw_taker_amt = raw_maker_amt * raw_price;
86 if decimal_places(raw_taker_amt) > round_config.amount {
87 raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4);
88 if decimal_places(raw_taker_amt) > round_config.amount {
89 raw_taker_amt = round_down(raw_taker_amt, round_config.amount);
90 }
91 }
92 RawAmounts {
93 side: Side::SELL,
94 raw_maker_amt,
95 raw_taker_amt,
96 }
97 }
98}
99
100pub(crate) fn parse_units(value: &str, decimals: u32) -> Result<String, ClobError> {
101 let d = value
102 .parse::<Decimal>()
103 .map_err(|e| ClobError::Other(format!("parse decimal error: {}", e)))?;
104 let factor = Decimal::new(10i64.pow(decimals), 0);
105 let scaled = d * factor;
106 let scaled_i = scaled.trunc();
107 match scaled_i.to_i128() {
108 Some(i) => Ok(i.to_string()),
109 None => Err(ClobError::Other("scaled value out of range".to_string())),
110 }
111}
112
113pub async fn build_order_creation_args(
114 signer: &str,
115 maker: &str,
116 signature_type: SignatureType,
117 user_order: &crate::types::UserOrder,
118 round_config: &RoundConfig,
119) -> Result<OrderData, ClobError> {
120 let ra = get_order_raw_amounts(
121 user_order.side,
122 user_order.size,
123 user_order.price,
124 round_config,
125 );
126
127 let maker_amount = parse_units(&ra.raw_maker_amt.to_string(), 6)?;
128 let taker_amount = parse_units(&ra.raw_taker_amt.to_string(), 6)?;
129
130 let taker = if let Some(t) = &user_order.taker {
131 if !t.is_empty() {
132 t.clone()
133 } else {
134 "0x0000000000000000000000000000000000000000".to_string()
135 }
136 } else {
137 "0x0000000000000000000000000000000000000000".to_string()
138 };
139
140 let fee_rate_bps = user_order.fee_rate_bps.to_string();
141 let nonce = user_order
142 .nonce
143 .map(|v| v.to_string())
144 .unwrap_or_else(|| "0".to_string());
145
146 Ok(OrderData {
147 maker: maker.to_string(),
148 taker,
149 token_id: user_order.token_id.clone(),
150 maker_amount,
151 taker_amount,
152 side: ra.side,
153 fee_rate_bps,
154 nonce,
155 signer: signer.to_string(),
156 expiration: user_order
157 .expiration
158 .map(|v| v.to_string())
159 .unwrap_or_else(|| "0".to_string()),
160 signature_type,
161 })
162}
163
164pub async fn build_order(
165 signer: &impl crate::signing::Eip712Signer,
166 exchange_address: &str,
167 chain_id: i32,
168 order_data: OrderData,
169) -> Result<SignedOrder, ClobError> {
170 let builder = ExchangeOrderBuilder::new(exchange_address, chain_id as i64, signer);
171 builder.build_signed_order(order_data).await
172}
173
174pub fn get_market_order_raw_amounts(
175 side: Side,
176 amount: Decimal,
177 price: Decimal,
178 round_config: &RoundConfig,
179) -> RawAmounts {
180 let raw_price = round_down(price, round_config.price);
181
182 if let Side::BUY = side {
183 let raw_maker_amt = round_down(amount, round_config.size);
184 let mut raw_taker_amt = raw_maker_amt / raw_price;
185 if decimal_places(raw_taker_amt) > round_config.amount {
186 raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4);
187 if decimal_places(raw_taker_amt) > round_config.amount {
188 raw_taker_amt = round_down(raw_taker_amt, round_config.amount);
189 }
190 }
191 RawAmounts {
192 side: Side::BUY,
193 raw_maker_amt,
194 raw_taker_amt,
195 }
196 } else {
197 let raw_maker_amt = round_down(amount, round_config.size);
198 let mut raw_taker_amt = raw_maker_amt * raw_price;
199 if decimal_places(raw_taker_amt) > round_config.amount {
200 raw_taker_amt = round_up(raw_taker_amt, round_config.amount + 4);
201 if decimal_places(raw_taker_amt) > round_config.amount {
202 raw_taker_amt = round_down(raw_taker_amt, round_config.amount);
203 }
204 }
205 RawAmounts {
206 side: Side::SELL,
207 raw_maker_amt,
208 raw_taker_amt,
209 }
210 }
211}
212
213pub struct OrderBuilder<'a, S: Eip712Signer> {
214 signer: &'a S,
215 chain_id: i64,
216 signature_type: SignatureType,
217 funder_address: Option<String>,
218}
219
220#[derive(Debug, Clone)]
221pub struct BuilderConfig {
222 pub tick_size: Option<String>,
223 pub neg_risk: Option<bool>,
224 pub signature_type: SignatureType,
225 pub funder_address: Option<String>,
226}
227
228impl Default for BuilderConfig {
229 fn default() -> Self {
230 Self {
231 tick_size: None,
232 neg_risk: None,
233 signature_type: SignatureType::EOA,
234 funder_address: None,
235 }
236 }
237}
238
239impl<'a, S: Eip712Signer> OrderBuilder<'a, S> {
240 pub fn new(
241 signer: &'a S,
242 chain_id: i64,
243 signature_type: Option<SignatureType>,
244 funder_address: Option<String>,
245 ) -> Self {
246 Self {
247 signer,
248 chain_id,
249 signature_type: signature_type.unwrap_or(SignatureType::EOA),
250 funder_address,
251 }
252 }
253
254 pub fn with_config(signer: &'a S, chain_id: i64, cfg: &BuilderConfig) -> Self {
255 Self {
256 signer,
257 chain_id,
258 signature_type: cfg.signature_type,
259 funder_address: cfg.funder_address.clone(),
260 }
261 }
262
263 pub async fn build_order(
264 &self,
265 exchange_address: &str,
266 user_order: &crate::types::UserOrder,
267 options_tick: &str,
268 ) -> Result<crate::types::SignedOrder, ClobError> {
269 let rc_map = rounding_config();
270 let round_config = rc_map
271 .get(options_tick)
272 .ok_or(ClobError::Other("invalid tick size".to_string()))?;
273 let eoa_addr = self.signer.get_address().await?;
274 let maker = self.funder_address.clone().unwrap_or(eoa_addr.clone());
275
276 let order_data = build_order_creation_args(
277 &eoa_addr,
278 &maker,
279 self.signature_type,
280 user_order,
281 round_config,
282 )
283 .await?;
284
285 let builder = ExchangeOrderBuilder::new(exchange_address, self.chain_id, self.signer);
286 builder.build_signed_order(order_data).await
287 }
288
289 pub async fn build_order_with_salt(
291 &self,
292 exchange_address: &str,
293 user_order: &crate::types::UserOrder,
294 options_tick: &str,
295 forced_salt: &str,
296 ) -> Result<crate::types::SignedOrder, ClobError> {
297 let rc_map = rounding_config();
298 let round_config = rc_map
299 .get(options_tick)
300 .ok_or(ClobError::Other("invalid tick size".to_string()))?;
301 let eoa_addr = self.signer.get_address().await?;
302 let maker = self.funder_address.clone().unwrap_or(eoa_addr.clone());
303
304 let order_data = build_order_creation_args(
305 &eoa_addr,
306 &maker,
307 self.signature_type,
308 user_order,
309 round_config,
310 )
311 .await?;
312
313 let builder = ExchangeOrderBuilder::new(exchange_address, self.chain_id, self.signer);
314 builder
315 .build_signed_order_with_salt(order_data, forced_salt)
316 .await
317 }
318
319 pub async fn build_market_order(
320 &self,
321 exchange_address: &str,
322 user_market_order: &crate::types::UserMarketOrder,
323 options_tick: &str,
324 ) -> Result<crate::types::SignedOrder, ClobError> {
325 let rc_map = rounding_config();
326 let round_config = rc_map
327 .get(options_tick)
328 .ok_or(ClobError::Other("invalid tick size".to_string()))?;
329 let eoa_addr = self.signer.get_address().await?;
330 let maker = self.funder_address.clone().unwrap_or(eoa_addr.clone());
331
332 let order_data = build_market_order_creation_args(
333 &eoa_addr,
334 &maker,
335 self.signature_type,
336 user_market_order,
337 round_config,
338 )
339 .await?;
340
341 let builder = ExchangeOrderBuilder::new(exchange_address, self.chain_id, self.signer);
342 builder.build_signed_order(order_data).await
343 }
344}
345
346pub async fn build_market_order_creation_args(
347 signer: &str,
348 maker: &str,
349 signature_type: SignatureType,
350 user_market_order: &crate::types::UserMarketOrder,
351 round_config: &RoundConfig,
352) -> Result<OrderData, ClobError> {
353 let price = user_market_order.price;
354 let ra = get_market_order_raw_amounts(
355 user_market_order.side,
356 user_market_order.amount,
357 price,
358 round_config,
359 );
360
361 let maker_amount = parse_units(&ra.raw_maker_amt.to_string(), 6)?;
362 let taker_amount = parse_units(&ra.raw_taker_amt.to_string(), 6)?;
363
364 let taker = if let Some(t) = &user_market_order.taker {
365 if !t.is_empty() {
366 t.clone()
367 } else {
368 "0x0000000000000000000000000000000000000000".to_string()
369 }
370 } else {
371 "0x0000000000000000000000000000000000000000".to_string()
372 };
373
374 let fee_rate_bps = user_market_order.fee_rate_bps.to_string();
375 let nonce = user_market_order
376 .nonce
377 .map(|v| v.to_string())
378 .unwrap_or_else(|| "0".to_string());
379
380 Ok(OrderData {
381 maker: maker.to_string(),
382 taker,
383 token_id: user_market_order.token_id.clone(),
384 maker_amount,
385 taker_amount,
386 side: ra.side,
387 fee_rate_bps,
388 nonce,
389 signer: signer.to_string(),
390 expiration: "0".to_string(),
391 signature_type,
392 })
393}
394
395pub fn calculate_buy_market_price(
396 positions: &[crate::types::OrderSummary],
397 amount_to_match: Decimal,
398 order_type: crate::types::OrderType,
399) -> Result<Decimal, ClobError> {
400 if positions.is_empty() {
401 return Err(ClobError::Other("no match".to_string()));
402 }
403 let mut sum = Decimal::ZERO;
404 for i in (0..positions.len()).rev() {
405 let p = &positions[i];
406 let price = p.price.parse::<Decimal>().unwrap_or(Decimal::ZERO);
407 let size = p.size.parse::<Decimal>().unwrap_or(Decimal::ZERO);
408 sum += size * price;
409 if sum >= amount_to_match {
410 return Ok(price);
411 }
412 }
413 if let crate::types::OrderType::FOK = order_type {
414 return Err(ClobError::Other("no match".to_string()));
415 }
416 Ok(positions[0]
417 .price
418 .parse::<Decimal>()
419 .unwrap_or(Decimal::ZERO))
420}
421
422pub fn calculate_sell_market_price(
423 positions: &[crate::types::OrderSummary],
424 amount_to_match: Decimal,
425 order_type: crate::types::OrderType,
426) -> Result<Decimal, ClobError> {
427 if positions.is_empty() {
428 return Err(ClobError::Other("no match".to_string()));
429 }
430 let mut sum = Decimal::ZERO;
431 for i in (0..positions.len()).rev() {
432 let p = &positions[i];
433 let size = p.size.parse::<Decimal>().unwrap_or(Decimal::ZERO);
434 sum += size;
435 if sum >= amount_to_match {
436 let price = p.price.parse::<Decimal>().unwrap_or(Decimal::ZERO);
437 return Ok(price);
438 }
439 }
440 if let crate::types::OrderType::FOK = order_type {
441 return Err(ClobError::Other("no match".to_string()));
442 }
443 Ok(positions[0]
444 .price
445 .parse::<Decimal>()
446 .unwrap_or(Decimal::ZERO))
447}
448
449pub fn compute_market_price_from_book(
451 book: &crate::types::OrderBookSummary,
452 side: crate::types::Side,
453 amount: Decimal,
454 order_type: crate::types::OrderType,
455) -> Result<Decimal, ClobError> {
456 match side {
457 crate::types::Side::BUY => {
458 if book.asks.is_empty() {
459 return Err(ClobError::Other("no match".to_string()));
460 }
461 calculate_buy_market_price(&book.asks, amount, order_type)
462 }
463 crate::types::Side::SELL => {
464 if book.bids.is_empty() {
465 return Err(ClobError::Other("no match".to_string()));
466 }
467 calculate_sell_market_price(&book.bids, amount, order_type)
468 }
469 }
470}