1use std::fmt;
2
3use alloy::primitives::Address;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7#[derive(Error, Debug, Clone, PartialEq)]
9#[error("invalid tick size: {0}. Valid values are 0.1, 0.01, 0.001, or 0.0001")]
10pub struct ParseTickSizeError(String);
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
14#[serde(rename_all = "UPPERCASE")]
15pub enum OrderSide {
16 Buy,
17 Sell,
18}
19
20impl OrderSide {
21 pub fn as_str(&self) -> &'static str {
23 match self {
24 Self::Buy => "BUY",
25 Self::Sell => "SELL",
26 }
27 }
28}
29
30impl Serialize for OrderSide {
31 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32 where
33 S: serde::Serializer,
34 {
35 match self {
36 Self::Buy => serializer.serialize_str("BUY"),
37 Self::Sell => serializer.serialize_str("SELL"),
38 }
39 }
40}
41
42impl fmt::Display for OrderSide {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Self::Buy => write!(f, "0"),
46 Self::Sell => write!(f, "1"),
47 }
48 }
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "UPPERCASE")]
54pub enum OrderKind {
55 Gtc,
57 Fok,
59 Gtd,
61 Fak,
63}
64
65impl fmt::Display for OrderKind {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 Self::Gtc => write!(f, "GTC"),
69 Self::Fok => write!(f, "FOK"),
70 Self::Gtd => write!(f, "GTD"),
71 Self::Fak => write!(f, "FAK"),
72 }
73 }
74}
75
76#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
78pub enum SignatureType {
79 #[default]
80 Eoa = 0,
81 PolyProxy = 1,
82 PolyGnosisSafe = 2,
83}
84
85impl SignatureType {
86 pub fn is_proxy(&self) -> bool {
88 matches!(self, Self::PolyProxy | Self::PolyGnosisSafe)
89 }
90}
91
92impl Serialize for SignatureType {
93 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94 where
95 S: serde::Serializer,
96 {
97 serializer.serialize_u8(*self as u8)
98 }
99}
100
101impl<'de> Deserialize<'de> for SignatureType {
102 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
103 where
104 D: serde::Deserializer<'de>,
105 {
106 let v = u8::deserialize(deserializer)?;
107 match v {
108 0 => Ok(Self::Eoa),
109 1 => Ok(Self::PolyProxy),
110 2 => Ok(Self::PolyGnosisSafe),
111 _ => Err(serde::de::Error::custom(format!(
112 "invalid signature type: {}",
113 v
114 ))),
115 }
116 }
117}
118
119impl fmt::Display for SignatureType {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 Self::Eoa => write!(f, "eoa"),
123 Self::PolyProxy => write!(f, "poly-proxy"),
124 Self::PolyGnosisSafe => write!(f, "poly-gnosis-safe"),
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq)]
131pub enum TickSize {
132 Tenth,
134 Hundredth,
136 Thousandth,
138 TenThousandth,
140}
141
142impl TickSize {
143 pub fn as_f64(&self) -> f64 {
145 match self {
146 Self::Tenth => 0.1,
147 Self::Hundredth => 0.01,
148 Self::Thousandth => 0.001,
149 Self::TenThousandth => 0.0001,
150 }
151 }
152
153 pub fn decimals(&self) -> u32 {
155 match self {
156 Self::Tenth => 1,
157 Self::Hundredth => 2,
158 Self::Thousandth => 3,
159 Self::TenThousandth => 4,
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, Default)]
166pub struct PartialCreateOrderOptions {
167 pub tick_size: Option<TickSize>,
168 pub neg_risk: Option<bool>,
169}
170
171impl TryFrom<&str> for TickSize {
172 type Error = ParseTickSizeError;
173
174 fn try_from(s: &str) -> Result<Self, Self::Error> {
175 match s {
176 "0.1" => Ok(Self::Tenth),
177 "0.01" => Ok(Self::Hundredth),
178 "0.001" => Ok(Self::Thousandth),
179 "0.0001" => Ok(Self::TenThousandth),
180 _ => Err(ParseTickSizeError(s.to_string())),
181 }
182 }
183}
184
185impl TryFrom<f64> for TickSize {
186 type Error = ParseTickSizeError;
187
188 fn try_from(n: f64) -> Result<Self, Self::Error> {
189 const EPSILON: f64 = 1e-10;
190 if (n - 0.1).abs() < EPSILON {
191 Ok(Self::Tenth)
192 } else if (n - 0.01).abs() < EPSILON {
193 Ok(Self::Hundredth)
194 } else if (n - 0.001).abs() < EPSILON {
195 Ok(Self::Thousandth)
196 } else if (n - 0.0001).abs() < EPSILON {
197 Ok(Self::TenThousandth)
198 } else {
199 Err(ParseTickSizeError(n.to_string()))
200 }
201 }
202}
203
204impl std::str::FromStr for TickSize {
205 type Err = ParseTickSizeError;
206
207 fn from_str(s: &str) -> Result<Self, Self::Err> {
208 Self::try_from(s)
209 }
210}
211
212fn serialize_salt<S>(salt: &str, serializer: S) -> Result<S::Ok, S::Error>
213where
214 S: serde::Serializer,
215{
216 let val = salt
220 .parse::<u128>()
221 .map_err(|_| serde::ser::Error::custom("invalid salt"))?;
222 serializer.serialize_u128(val)
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227#[serde(rename_all = "camelCase")]
228pub struct Order {
229 #[serde(serialize_with = "serialize_salt")]
230 pub salt: String,
231 pub maker: Address,
232 pub signer: Address,
233 pub taker: Address,
234 pub token_id: String,
235 pub maker_amount: String,
236 pub taker_amount: String,
237 pub expiration: String,
238 pub nonce: String,
239 pub fee_rate_bps: String,
240 pub side: OrderSide,
241 pub signature_type: SignatureType,
242 #[serde(skip)]
243 pub neg_risk: bool,
244}
245
246#[derive(Debug, Clone)]
248pub struct MarketOrderArgs {
249 pub token_id: String,
250 pub amount: f64,
253 pub side: OrderSide,
254 pub price: Option<f64>,
257 pub fee_rate_bps: Option<u16>,
258 pub nonce: Option<u64>,
259 pub funder: Option<Address>,
260 pub signature_type: Option<SignatureType>,
261 pub order_type: Option<OrderKind>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct SignedOrder {
268 #[serde(flatten)]
269 pub order: Order,
270 pub signature: String,
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use alloy::primitives::Address;
277 use std::str::FromStr;
278
279 #[test]
280 fn test_order_serialization() {
281 let order = Order {
282 salt: "123".to_string(),
283 maker: Address::from_str("0x0000000000000000000000000000000000000001").unwrap(),
284 signer: Address::from_str("0x0000000000000000000000000000000000000002").unwrap(),
285 taker: Address::ZERO,
286 token_id: "456".to_string(),
287 maker_amount: "1000".to_string(),
288 taker_amount: "2000".to_string(),
289 expiration: "0".to_string(),
290 nonce: "789".to_string(),
291 fee_rate_bps: "0".to_string(),
292 side: OrderSide::Buy,
293 signature_type: SignatureType::Eoa,
294 neg_risk: false,
295 };
296
297 let signed_order = SignedOrder {
298 order,
299 signature: "0xabc".to_string(),
300 };
301
302 let json = serde_json::to_value(&signed_order).unwrap();
303
304 assert!(json.get("makerAmount").is_some());
306 assert!(json.get("takerAmount").is_some());
307 assert!(json.get("tokenId").is_some());
308 assert!(json.get("feeRateBps").is_some());
309 assert!(json.get("signatureType").is_some());
310
311 assert!(json.get("signature").is_some());
313 assert!(json.get("salt").is_some());
314
315 assert_eq!(json["makerAmount"], "1000");
317 assert_eq!(json["side"], "BUY");
318 assert_eq!(json["signatureType"], 0);
319 assert_eq!(json["nonce"], "789");
320 }
321
322 #[test]
323 fn order_side_serde_roundtrip() {
324 let buy: OrderSide = serde_json::from_str("\"BUY\"").unwrap();
325 let sell: OrderSide = serde_json::from_str("\"SELL\"").unwrap();
326 assert_eq!(buy, OrderSide::Buy);
327 assert_eq!(sell, OrderSide::Sell);
328
329 assert_eq!(serde_json::to_string(&OrderSide::Buy).unwrap(), "\"BUY\"");
330 assert_eq!(serde_json::to_string(&OrderSide::Sell).unwrap(), "\"SELL\"");
331 }
332
333 #[test]
334 fn order_side_display_is_numeric() {
335 assert_eq!(OrderSide::Buy.to_string(), "0");
337 assert_eq!(OrderSide::Sell.to_string(), "1");
338 }
339
340 #[test]
341 fn order_side_rejects_lowercase() {
342 let result = serde_json::from_str::<OrderSide>("\"buy\"");
343 assert!(result.is_err(), "Should reject lowercase order side");
344 }
345
346 #[test]
347 fn order_kind_serde_roundtrip() {
348 for (variant, expected) in [
349 (OrderKind::Gtc, "GTC"),
350 (OrderKind::Fok, "FOK"),
351 (OrderKind::Gtd, "GTD"),
352 (OrderKind::Fak, "FAK"),
353 ] {
354 let serialized = serde_json::to_string(&variant).unwrap();
355 assert_eq!(serialized, format!("\"{}\"", expected));
356
357 let deserialized: OrderKind = serde_json::from_str(&serialized).unwrap();
358 assert_eq!(deserialized, variant);
359 }
360 }
361
362 #[test]
363 fn order_kind_display() {
364 assert_eq!(OrderKind::Gtc.to_string(), "GTC");
365 assert_eq!(OrderKind::Fok.to_string(), "FOK");
366 assert_eq!(OrderKind::Gtd.to_string(), "GTD");
367 assert_eq!(OrderKind::Fak.to_string(), "FAK");
368 }
369
370 #[test]
371 fn signature_type_serde_as_u8() {
372 assert_eq!(serde_json::to_string(&SignatureType::Eoa).unwrap(), "0");
373 assert_eq!(
374 serde_json::to_string(&SignatureType::PolyProxy).unwrap(),
375 "1"
376 );
377 assert_eq!(
378 serde_json::to_string(&SignatureType::PolyGnosisSafe).unwrap(),
379 "2"
380 );
381
382 let eoa: SignatureType = serde_json::from_str("0").unwrap();
383 assert_eq!(eoa, SignatureType::Eoa);
384 let proxy: SignatureType = serde_json::from_str("1").unwrap();
385 assert_eq!(proxy, SignatureType::PolyProxy);
386 let gnosis: SignatureType = serde_json::from_str("2").unwrap();
387 assert_eq!(gnosis, SignatureType::PolyGnosisSafe);
388 }
389
390 #[test]
391 fn signature_type_rejects_invalid_u8() {
392 let result = serde_json::from_str::<SignatureType>("3");
393 assert!(result.is_err(), "Should reject invalid signature type 3");
394
395 let result = serde_json::from_str::<SignatureType>("255");
396 assert!(result.is_err(), "Should reject invalid signature type 255");
397 }
398
399 #[test]
400 fn signature_type_display() {
401 assert_eq!(SignatureType::Eoa.to_string(), "eoa");
402 assert_eq!(SignatureType::PolyProxy.to_string(), "poly-proxy");
403 assert_eq!(
404 SignatureType::PolyGnosisSafe.to_string(),
405 "poly-gnosis-safe"
406 );
407 }
408
409 #[test]
410 fn signature_type_default_is_eoa() {
411 assert_eq!(SignatureType::default(), SignatureType::Eoa);
412 }
413
414 #[test]
415 fn signature_type_is_proxy() {
416 assert!(!SignatureType::Eoa.is_proxy());
417 assert!(SignatureType::PolyProxy.is_proxy());
418 assert!(SignatureType::PolyGnosisSafe.is_proxy());
419 }
420
421 #[test]
422 fn tick_size_from_str() {
423 assert_eq!(TickSize::try_from("0.1").unwrap(), TickSize::Tenth);
424 assert_eq!(TickSize::try_from("0.01").unwrap(), TickSize::Hundredth);
425 assert_eq!(TickSize::try_from("0.001").unwrap(), TickSize::Thousandth);
426 assert_eq!(
427 TickSize::try_from("0.0001").unwrap(),
428 TickSize::TenThousandth
429 );
430 }
431
432 #[test]
433 fn tick_size_from_str_rejects_invalid() {
434 assert!(TickSize::try_from("0.5").is_err());
435 assert!(TickSize::try_from("1.0").is_err());
436 assert!(TickSize::try_from("abc").is_err());
437 assert!(TickSize::try_from("0.00001").is_err());
438 }
439
440 #[test]
441 fn tick_size_from_f64() {
442 assert_eq!(TickSize::try_from(0.1).unwrap(), TickSize::Tenth);
443 assert_eq!(TickSize::try_from(0.01).unwrap(), TickSize::Hundredth);
444 assert_eq!(TickSize::try_from(0.001).unwrap(), TickSize::Thousandth);
445 assert_eq!(TickSize::try_from(0.0001).unwrap(), TickSize::TenThousandth);
446 }
447
448 #[test]
449 fn tick_size_from_f64_rejects_invalid() {
450 assert!(TickSize::try_from(0.5).is_err());
451 assert!(TickSize::try_from(0.0).is_err());
452 assert!(TickSize::try_from(1.0).is_err());
453 }
454
455 #[test]
456 fn tick_size_as_f64() {
457 assert!((TickSize::Tenth.as_f64() - 0.1).abs() < f64::EPSILON);
458 assert!((TickSize::Hundredth.as_f64() - 0.01).abs() < f64::EPSILON);
459 assert!((TickSize::Thousandth.as_f64() - 0.001).abs() < f64::EPSILON);
460 assert!((TickSize::TenThousandth.as_f64() - 0.0001).abs() < f64::EPSILON);
461 }
462
463 #[test]
464 fn tick_size_decimals() {
465 assert_eq!(TickSize::Tenth.decimals(), 1);
466 assert_eq!(TickSize::Hundredth.decimals(), 2);
467 assert_eq!(TickSize::Thousandth.decimals(), 3);
468 assert_eq!(TickSize::TenThousandth.decimals(), 4);
469 }
470
471 #[test]
472 fn tick_size_from_str_trait() {
473 let ts: TickSize = "0.01".parse().unwrap();
474 assert_eq!(ts, TickSize::Hundredth);
475 }
476
477 #[test]
478 fn parse_tick_size_error_display() {
479 let err = TickSize::try_from("bad").unwrap_err();
480 let msg = err.to_string();
481 assert!(
482 msg.contains("bad"),
483 "Error should contain invalid value: {}",
484 msg
485 );
486 assert!(
487 msg.contains("0.1"),
488 "Error should list valid values: {}",
489 msg
490 );
491 }
492
493 #[test]
494 fn order_neg_risk_skipped_in_serialization() {
495 let order = Order {
496 salt: "1".to_string(),
497 maker: Address::ZERO,
498 signer: Address::ZERO,
499 taker: Address::ZERO,
500 token_id: "1".to_string(),
501 maker_amount: "1".to_string(),
502 taker_amount: "1".to_string(),
503 expiration: "0".to_string(),
504 nonce: "0".to_string(),
505 fee_rate_bps: "0".to_string(),
506 side: OrderSide::Buy,
507 signature_type: SignatureType::Eoa,
508 neg_risk: true,
509 };
510 let json = serde_json::to_value(&order).unwrap();
511 assert!(
512 json.get("neg_risk").is_none() && json.get("negRisk").is_none(),
513 "neg_risk should be skipped in serialization: {}",
514 json
515 );
516 }
517
518 #[test]
519 fn salt_serialized_as_number() {
520 let order = Order {
521 salt: "340282366920938463463374607431768211455".to_string(), maker: Address::ZERO,
523 signer: Address::ZERO,
524 taker: Address::ZERO,
525 token_id: "1".to_string(),
526 maker_amount: "1".to_string(),
527 taker_amount: "1".to_string(),
528 expiration: "0".to_string(),
529 nonce: "0".to_string(),
530 fee_rate_bps: "0".to_string(),
531 side: OrderSide::Buy,
532 signature_type: SignatureType::Eoa,
533 neg_risk: false,
534 };
535 let json = serde_json::to_value(&order).unwrap();
536 assert!(
537 json["salt"].is_number(),
538 "Salt should be a number: {:?}",
539 json["salt"]
540 );
541 }
542}