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