1use std::fmt;
2
3use alloy::primitives::{Address, B256};
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)]
78#[repr(u8)]
79pub enum SignatureType {
80 #[default]
81 Eoa = 0,
82 PolyProxy = 1,
83 PolyGnosisSafe = 2,
84 Poly1271 = 3,
88}
89
90impl SignatureType {
91 pub fn is_proxy(&self) -> bool {
93 matches!(self, Self::PolyProxy | Self::PolyGnosisSafe)
94 }
95}
96
97impl Serialize for SignatureType {
98 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
99 where
100 S: serde::Serializer,
101 {
102 serializer.serialize_u8(*self as u8)
103 }
104}
105
106impl<'de> Deserialize<'de> for SignatureType {
107 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108 where
109 D: serde::Deserializer<'de>,
110 {
111 let v = u8::deserialize(deserializer)?;
112 match v {
113 0 => Ok(Self::Eoa),
114 1 => Ok(Self::PolyProxy),
115 2 => Ok(Self::PolyGnosisSafe),
116 3 => Ok(Self::Poly1271),
117 _ => Err(serde::de::Error::custom(format!(
118 "invalid signature type: {}",
119 v
120 ))),
121 }
122 }
123}
124
125impl fmt::Display for SignatureType {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self {
128 Self::Eoa => write!(f, "eoa"),
129 Self::PolyProxy => write!(f, "poly-proxy"),
130 Self::PolyGnosisSafe => write!(f, "poly-gnosis-safe"),
131 Self::Poly1271 => write!(f, "poly-1271"),
132 }
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq)]
138pub enum TickSize {
139 Tenth,
141 Hundredth,
143 Thousandth,
145 TenThousandth,
147}
148
149impl TickSize {
150 pub fn as_f64(&self) -> f64 {
152 match self {
153 Self::Tenth => 0.1,
154 Self::Hundredth => 0.01,
155 Self::Thousandth => 0.001,
156 Self::TenThousandth => 0.0001,
157 }
158 }
159
160 pub fn decimals(&self) -> u32 {
162 match self {
163 Self::Tenth => 1,
164 Self::Hundredth => 2,
165 Self::Thousandth => 3,
166 Self::TenThousandth => 4,
167 }
168 }
169}
170
171#[derive(Debug, Clone, Copy, Default)]
173pub struct PartialCreateOrderOptions {
174 pub tick_size: Option<TickSize>,
175 pub neg_risk: Option<bool>,
176}
177
178impl TryFrom<&str> for TickSize {
179 type Error = ParseTickSizeError;
180
181 fn try_from(s: &str) -> Result<Self, Self::Error> {
182 match s {
183 "0.1" => Ok(Self::Tenth),
184 "0.01" => Ok(Self::Hundredth),
185 "0.001" => Ok(Self::Thousandth),
186 "0.0001" => Ok(Self::TenThousandth),
187 _ => Err(ParseTickSizeError(s.to_string())),
188 }
189 }
190}
191
192impl TryFrom<f64> for TickSize {
193 type Error = ParseTickSizeError;
194
195 fn try_from(n: f64) -> Result<Self, Self::Error> {
196 const EPSILON: f64 = 1e-10;
197 if (n - 0.1).abs() < EPSILON {
198 Ok(Self::Tenth)
199 } else if (n - 0.01).abs() < EPSILON {
200 Ok(Self::Hundredth)
201 } else if (n - 0.001).abs() < EPSILON {
202 Ok(Self::Thousandth)
203 } else if (n - 0.0001).abs() < EPSILON {
204 Ok(Self::TenThousandth)
205 } else {
206 Err(ParseTickSizeError(n.to_string()))
207 }
208 }
209}
210
211impl std::str::FromStr for TickSize {
212 type Err = ParseTickSizeError;
213
214 fn from_str(s: &str) -> Result<Self, Self::Err> {
215 Self::try_from(s)
216 }
217}
218
219fn serialize_salt<S>(salt: &str, serializer: S) -> Result<S::Ok, S::Error>
220where
221 S: serde::Serializer,
222{
223 let val = salt
227 .parse::<u64>()
228 .map_err(|_| serde::ser::Error::custom("invalid salt: must fit in u64"))?;
229 serializer.serialize_u64(val)
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct Order {
242 #[serde(serialize_with = "serialize_salt")]
243 pub salt: String,
244 pub maker: Address,
245 pub signer: Address,
246 pub token_id: String,
247 pub maker_amount: String,
248 pub taker_amount: String,
249 pub side: OrderSide,
250 pub expiration: String,
252 pub signature_type: SignatureType,
253 pub timestamp: String,
255 pub metadata: B256,
256 pub builder: B256,
257 #[serde(skip)]
258 pub neg_risk: bool,
259}
260
261#[derive(Debug, Clone)]
263pub struct MarketOrderArgs {
264 pub token_id: String,
265 pub amount: f64,
268 pub side: OrderSide,
269 pub price: Option<f64>,
272 pub fee_rate_bps: Option<u16>,
276 pub nonce: Option<u64>,
280 pub funder: Option<Address>,
281 pub signature_type: Option<SignatureType>,
282 pub order_type: Option<OrderKind>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct SignedOrder {
289 #[serde(flatten)]
290 pub order: Order,
291 pub signature: String,
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use alloy::primitives::Address;
298 use std::str::FromStr;
299
300 #[test]
301 fn test_order_serialization() {
302 let order = Order {
303 salt: "123".to_string(),
304 maker: Address::from_str("0x0000000000000000000000000000000000000001").unwrap(),
305 signer: Address::from_str("0x0000000000000000000000000000000000000002").unwrap(),
306 token_id: "456".to_string(),
307 maker_amount: "1000".to_string(),
308 taker_amount: "2000".to_string(),
309 side: OrderSide::Buy,
310 expiration: "0".to_string(),
311 signature_type: SignatureType::Eoa,
312 timestamp: "1700000000000".to_string(),
313 metadata: B256::ZERO,
314 builder: B256::ZERO,
315 neg_risk: false,
316 };
317
318 let signed_order = SignedOrder {
319 order,
320 signature: "0xabc".to_string(),
321 };
322
323 let json = serde_json::to_value(&signed_order).unwrap();
324
325 assert!(json.get("makerAmount").is_some());
327 assert!(json.get("takerAmount").is_some());
328 assert!(json.get("tokenId").is_some());
329 assert!(json.get("signatureType").is_some());
330
331 assert!(json.get("signature").is_some());
333 assert!(json.get("salt").is_some());
334
335 assert_eq!(json["makerAmount"], "1000");
337 assert_eq!(json["side"], "BUY");
338 assert_eq!(json["signatureType"], 0);
339 assert_eq!(json["timestamp"], "1700000000000");
340 }
341
342 #[test]
343 fn signed_order_serializes_v2_wire_shape() {
344 let order = Order {
345 salt: "479249096354".to_string(),
346 maker: alloy::primitives::Address::ZERO,
347 signer: alloy::primitives::Address::ZERO,
348 token_id: "100".to_string(),
349 maker_amount: "1000000".to_string(),
350 taker_amount: "5000000".to_string(),
351 side: OrderSide::Buy,
352 expiration: "0".to_string(),
353 signature_type: SignatureType::PolyProxy,
354 timestamp: "1700000000000".to_string(),
355 metadata: alloy::primitives::B256::ZERO,
356 builder: alloy::primitives::B256::ZERO,
357 neg_risk: false,
358 };
359 let signed = SignedOrder {
360 order,
361 signature: "0xdead".to_string(),
362 };
363 let v = serde_json::to_value(&signed).unwrap();
364 for k in [
365 "salt",
366 "maker",
367 "signer",
368 "tokenId",
369 "makerAmount",
370 "takerAmount",
371 "side",
372 "expiration",
373 "signatureType",
374 "timestamp",
375 "metadata",
376 "builder",
377 "signature",
378 ] {
379 assert!(v.get(k).is_some(), "missing wire field {k}");
380 }
381 for k in ["taker", "nonce", "feeRateBps"] {
382 assert!(v.get(k).is_none(), "V1 field {k} must be gone");
383 }
384 assert!(v["salt"].is_number());
385 assert!(v["makerAmount"].is_string());
386 assert_eq!(v["builder"].as_str().unwrap().len(), 66);
387 }
388
389 #[test]
390 fn order_side_serde_roundtrip() {
391 let buy: OrderSide = serde_json::from_str("\"BUY\"").unwrap();
392 let sell: OrderSide = serde_json::from_str("\"SELL\"").unwrap();
393 assert_eq!(buy, OrderSide::Buy);
394 assert_eq!(sell, OrderSide::Sell);
395
396 assert_eq!(serde_json::to_string(&OrderSide::Buy).unwrap(), "\"BUY\"");
397 assert_eq!(serde_json::to_string(&OrderSide::Sell).unwrap(), "\"SELL\"");
398 }
399
400 #[test]
401 fn order_side_display_is_numeric() {
402 assert_eq!(OrderSide::Buy.to_string(), "0");
404 assert_eq!(OrderSide::Sell.to_string(), "1");
405 }
406
407 #[test]
408 fn order_side_rejects_lowercase() {
409 let result = serde_json::from_str::<OrderSide>("\"buy\"");
410 assert!(result.is_err(), "Should reject lowercase order side");
411 }
412
413 #[test]
414 fn order_kind_serde_roundtrip() {
415 for (variant, expected) in [
416 (OrderKind::Gtc, "GTC"),
417 (OrderKind::Fok, "FOK"),
418 (OrderKind::Gtd, "GTD"),
419 (OrderKind::Fak, "FAK"),
420 ] {
421 let serialized = serde_json::to_string(&variant).unwrap();
422 assert_eq!(serialized, format!("\"{}\"", expected));
423
424 let deserialized: OrderKind = serde_json::from_str(&serialized).unwrap();
425 assert_eq!(deserialized, variant);
426 }
427 }
428
429 #[test]
430 fn order_kind_display() {
431 assert_eq!(OrderKind::Gtc.to_string(), "GTC");
432 assert_eq!(OrderKind::Fok.to_string(), "FOK");
433 assert_eq!(OrderKind::Gtd.to_string(), "GTD");
434 assert_eq!(OrderKind::Fak.to_string(), "FAK");
435 }
436
437 #[test]
438 fn signature_type_serde_as_u8() {
439 assert_eq!(serde_json::to_string(&SignatureType::Eoa).unwrap(), "0");
440 assert_eq!(
441 serde_json::to_string(&SignatureType::PolyProxy).unwrap(),
442 "1"
443 );
444 assert_eq!(
445 serde_json::to_string(&SignatureType::PolyGnosisSafe).unwrap(),
446 "2"
447 );
448
449 let eoa: SignatureType = serde_json::from_str("0").unwrap();
450 assert_eq!(eoa, SignatureType::Eoa);
451 let proxy: SignatureType = serde_json::from_str("1").unwrap();
452 assert_eq!(proxy, SignatureType::PolyProxy);
453 let gnosis: SignatureType = serde_json::from_str("2").unwrap();
454 assert_eq!(gnosis, SignatureType::PolyGnosisSafe);
455
456 assert_eq!(
457 serde_json::to_string(&SignatureType::Poly1271).unwrap(),
458 "3"
459 );
460 let poly1271: SignatureType = serde_json::from_str("3").unwrap();
461 assert_eq!(poly1271, SignatureType::Poly1271);
462 }
463
464 #[test]
465 fn signature_type_rejects_invalid_u8() {
466 let result = serde_json::from_str::<SignatureType>("4");
467 assert!(result.is_err(), "Should reject invalid signature type 4");
468
469 let result = serde_json::from_str::<SignatureType>("255");
470 assert!(result.is_err(), "Should reject invalid signature type 255");
471 }
472
473 #[test]
474 fn signature_type_display() {
475 assert_eq!(SignatureType::Eoa.to_string(), "eoa");
476 assert_eq!(SignatureType::PolyProxy.to_string(), "poly-proxy");
477 assert_eq!(
478 SignatureType::PolyGnosisSafe.to_string(),
479 "poly-gnosis-safe"
480 );
481 }
482
483 #[test]
484 fn signature_type_default_is_eoa() {
485 assert_eq!(SignatureType::default(), SignatureType::Eoa);
486 }
487
488 #[test]
489 fn signature_type_is_proxy() {
490 assert!(!SignatureType::Eoa.is_proxy());
491 assert!(SignatureType::PolyProxy.is_proxy());
492 assert!(SignatureType::PolyGnosisSafe.is_proxy());
493 assert!(!SignatureType::Poly1271.is_proxy());
495 }
496
497 #[test]
498 fn tick_size_from_str() {
499 assert_eq!(TickSize::try_from("0.1").unwrap(), TickSize::Tenth);
500 assert_eq!(TickSize::try_from("0.01").unwrap(), TickSize::Hundredth);
501 assert_eq!(TickSize::try_from("0.001").unwrap(), TickSize::Thousandth);
502 assert_eq!(
503 TickSize::try_from("0.0001").unwrap(),
504 TickSize::TenThousandth
505 );
506 }
507
508 #[test]
509 fn tick_size_from_str_rejects_invalid() {
510 assert!(TickSize::try_from("0.5").is_err());
511 assert!(TickSize::try_from("1.0").is_err());
512 assert!(TickSize::try_from("abc").is_err());
513 assert!(TickSize::try_from("0.00001").is_err());
514 }
515
516 #[test]
517 fn tick_size_from_f64() {
518 assert_eq!(TickSize::try_from(0.1).unwrap(), TickSize::Tenth);
519 assert_eq!(TickSize::try_from(0.01).unwrap(), TickSize::Hundredth);
520 assert_eq!(TickSize::try_from(0.001).unwrap(), TickSize::Thousandth);
521 assert_eq!(TickSize::try_from(0.0001).unwrap(), TickSize::TenThousandth);
522 }
523
524 #[test]
525 fn tick_size_from_f64_rejects_invalid() {
526 assert!(TickSize::try_from(0.5).is_err());
527 assert!(TickSize::try_from(0.0).is_err());
528 assert!(TickSize::try_from(1.0).is_err());
529 }
530
531 #[test]
532 fn tick_size_as_f64() {
533 assert!((TickSize::Tenth.as_f64() - 0.1).abs() < f64::EPSILON);
534 assert!((TickSize::Hundredth.as_f64() - 0.01).abs() < f64::EPSILON);
535 assert!((TickSize::Thousandth.as_f64() - 0.001).abs() < f64::EPSILON);
536 assert!((TickSize::TenThousandth.as_f64() - 0.0001).abs() < f64::EPSILON);
537 }
538
539 #[test]
540 fn tick_size_decimals() {
541 assert_eq!(TickSize::Tenth.decimals(), 1);
542 assert_eq!(TickSize::Hundredth.decimals(), 2);
543 assert_eq!(TickSize::Thousandth.decimals(), 3);
544 assert_eq!(TickSize::TenThousandth.decimals(), 4);
545 }
546
547 #[test]
548 fn tick_size_from_str_trait() {
549 let ts: TickSize = "0.01".parse().unwrap();
550 assert_eq!(ts, TickSize::Hundredth);
551 }
552
553 #[test]
554 fn parse_tick_size_error_display() {
555 let err = TickSize::try_from("bad").unwrap_err();
556 let msg = err.to_string();
557 assert!(
558 msg.contains("bad"),
559 "Error should contain invalid value: {}",
560 msg
561 );
562 assert!(
563 msg.contains("0.1"),
564 "Error should list valid values: {}",
565 msg
566 );
567 }
568
569 #[test]
570 fn order_neg_risk_skipped_in_serialization() {
571 let order = Order {
572 salt: "1".to_string(),
573 maker: Address::ZERO,
574 signer: Address::ZERO,
575 token_id: "1".to_string(),
576 maker_amount: "1".to_string(),
577 taker_amount: "1".to_string(),
578 side: OrderSide::Buy,
579 expiration: "0".to_string(),
580 signature_type: SignatureType::Eoa,
581 timestamp: "0".to_string(),
582 metadata: B256::ZERO,
583 builder: B256::ZERO,
584 neg_risk: true,
585 };
586 let json = serde_json::to_value(&order).unwrap();
587 assert!(
588 json.get("neg_risk").is_none() && json.get("negRisk").is_none(),
589 "neg_risk should be skipped in serialization: {}",
590 json
591 );
592 }
593
594 #[test]
595 fn salt_serialized_as_number() {
596 let order = Order {
597 salt: "18446744073709551615".to_string(), maker: Address::ZERO,
599 signer: Address::ZERO,
600 token_id: "1".to_string(),
601 maker_amount: "1".to_string(),
602 taker_amount: "1".to_string(),
603 side: OrderSide::Buy,
604 expiration: "0".to_string(),
605 signature_type: SignatureType::Eoa,
606 timestamp: "0".to_string(),
607 metadata: B256::ZERO,
608 builder: B256::ZERO,
609 neg_risk: false,
610 };
611 let json = serde_json::to_value(&order).unwrap();
612 assert!(
613 json["salt"].is_number(),
614 "Salt should be a number: {:?}",
615 json["salt"]
616 );
617 assert_eq!(json["salt"].as_u64().unwrap(), u64::MAX);
618 }
619
620 #[test]
621 fn salt_rejects_u128() {
622 let order = Order {
623 salt: "340282366920938463463374607431768211455".to_string(), maker: Address::ZERO,
625 signer: Address::ZERO,
626 token_id: "1".to_string(),
627 maker_amount: "1".to_string(),
628 taker_amount: "1".to_string(),
629 side: OrderSide::Buy,
630 expiration: "0".to_string(),
631 signature_type: SignatureType::Eoa,
632 timestamp: "0".to_string(),
633 metadata: B256::ZERO,
634 builder: B256::ZERO,
635 neg_risk: false,
636 };
637 assert!(
638 serde_json::to_value(&order).is_err(),
639 "u128 salt should be rejected"
640 );
641 }
642}