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