Skip to main content

polyoxide_clob/
types.rs

1use std::fmt;
2
3use alloy::primitives::Address;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7/// Error when parsing a tick size from an invalid value
8#[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 Serialize for OrderSide {
20    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21    where
22        S: serde::Serializer,
23    {
24        match self {
25            Self::Buy => serializer.serialize_str("BUY"),
26            Self::Sell => serializer.serialize_str("SELL"),
27        }
28    }
29}
30
31impl fmt::Display for OrderSide {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Self::Buy => write!(f, "0"),
35            Self::Sell => write!(f, "1"),
36        }
37    }
38}
39
40/// Order type/kind
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "UPPERCASE")]
43pub enum OrderKind {
44    /// Good-till-Cancelled
45    Gtc,
46    /// Fill-or-Kill
47    Fok,
48    /// Good-till-Date
49    Gtd,
50    /// Fill-and-Kill
51    Fak,
52}
53
54impl fmt::Display for OrderKind {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::Gtc => write!(f, "GTC"),
58            Self::Fok => write!(f, "FOK"),
59            Self::Gtd => write!(f, "GTD"),
60            Self::Fak => write!(f, "FAK"),
61        }
62    }
63}
64
65/// Signature type
66/// Signature type
67#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
68pub enum SignatureType {
69    #[default]
70    Eoa = 0,
71    PolyProxy = 1,
72    PolyGnosisSafe = 2,
73}
74
75impl SignatureType {
76    /// Returns true if the signature type indicates a proxy wallet
77    pub fn is_proxy(&self) -> bool {
78        matches!(self, Self::PolyProxy | Self::PolyGnosisSafe)
79    }
80}
81
82impl Serialize for SignatureType {
83    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84    where
85        S: serde::Serializer,
86    {
87        serializer.serialize_u8(*self as u8)
88    }
89}
90
91impl<'de> Deserialize<'de> for SignatureType {
92    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
93    where
94        D: serde::Deserializer<'de>,
95    {
96        let v = u8::deserialize(deserializer)?;
97        match v {
98            0 => Ok(Self::Eoa),
99            1 => Ok(Self::PolyProxy),
100            2 => Ok(Self::PolyGnosisSafe),
101            _ => Err(serde::de::Error::custom(format!(
102                "invalid signature type: {}",
103                v
104            ))),
105        }
106    }
107}
108
109impl fmt::Display for SignatureType {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        match self {
112            Self::Eoa => write!(f, "eoa"),
113            Self::PolyProxy => write!(f, "poly-proxy"),
114            Self::PolyGnosisSafe => write!(f, "poly-gnosis-safe"),
115        }
116    }
117}
118
119/// Tick size (minimum price increment)
120#[derive(Debug, Clone, Copy, PartialEq)]
121pub enum TickSize {
122    /// 0.1
123    Tenth,
124    /// 0.01
125    Hundredth,
126    /// 0.001
127    Thousandth,
128    /// 0.0001
129    TenThousandth,
130}
131
132impl TickSize {
133    pub fn as_f64(&self) -> f64 {
134        match self {
135            Self::Tenth => 0.1,
136            Self::Hundredth => 0.01,
137            Self::Thousandth => 0.001,
138            Self::TenThousandth => 0.0001,
139        }
140    }
141
142    pub fn decimals(&self) -> u32 {
143        match self {
144            Self::Tenth => 1,
145            Self::Hundredth => 2,
146            Self::Thousandth => 3,
147            Self::TenThousandth => 4,
148        }
149    }
150}
151
152/// Options for creating an order
153#[derive(Debug, Clone, Copy, Default)]
154pub struct PartialCreateOrderOptions {
155    pub tick_size: Option<TickSize>,
156    pub neg_risk: Option<bool>,
157}
158
159impl TryFrom<&str> for TickSize {
160    type Error = ParseTickSizeError;
161
162    fn try_from(s: &str) -> Result<Self, Self::Error> {
163        match s {
164            "0.1" => Ok(Self::Tenth),
165            "0.01" => Ok(Self::Hundredth),
166            "0.001" => Ok(Self::Thousandth),
167            "0.0001" => Ok(Self::TenThousandth),
168            _ => Err(ParseTickSizeError(s.to_string())),
169        }
170    }
171}
172
173impl TryFrom<f64> for TickSize {
174    type Error = ParseTickSizeError;
175
176    fn try_from(n: f64) -> Result<Self, Self::Error> {
177        const EPSILON: f64 = 1e-10;
178        if (n - 0.1).abs() < EPSILON {
179            Ok(Self::Tenth)
180        } else if (n - 0.01).abs() < EPSILON {
181            Ok(Self::Hundredth)
182        } else if (n - 0.001).abs() < EPSILON {
183            Ok(Self::Thousandth)
184        } else if (n - 0.0001).abs() < EPSILON {
185            Ok(Self::TenThousandth)
186        } else {
187            Err(ParseTickSizeError(n.to_string()))
188        }
189    }
190}
191
192impl std::str::FromStr for TickSize {
193    type Err = ParseTickSizeError;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        Self::try_from(s)
197    }
198}
199
200fn serialize_salt<S>(salt: &str, serializer: S) -> Result<S::Ok, S::Error>
201where
202    S: serde::Serializer,
203{
204    // Parse the string as u128 and serialize it as a number
205    let val = salt
206        .parse::<u128>()
207        .map_err(|_| serde::ser::Error::custom("invalid salt"))?;
208    serializer.serialize_u128(val)
209}
210
211/// Unsigned order
212#[derive(Debug, Clone, Serialize, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct Order {
215    #[serde(serialize_with = "serialize_salt")]
216    pub salt: String,
217    pub maker: Address,
218    pub signer: Address,
219    pub taker: Address,
220    pub token_id: String,
221    pub maker_amount: String,
222    pub taker_amount: String,
223    pub expiration: String,
224    pub nonce: String,
225    pub fee_rate_bps: String,
226    pub side: OrderSide,
227    pub signature_type: SignatureType,
228    #[serde(skip)]
229    pub neg_risk: bool,
230}
231
232/// Arguments for creating a market order
233#[derive(Debug, Clone)]
234pub struct MarketOrderArgs {
235    pub token_id: String,
236    /// For BUY: Amount in USDC to spend
237    /// For SELL: Amount of token to sell
238    pub amount: f64,
239    pub side: OrderSide,
240    /// Worst acceptable price to fill at.
241    /// If None, it will be calculated from the orderbook.
242    pub price: Option<f64>,
243    pub fee_rate_bps: Option<u16>,
244    pub nonce: Option<u64>,
245    pub funder: Option<Address>,
246    pub signature_type: Option<SignatureType>,
247    pub order_type: Option<OrderKind>,
248}
249
250/// Signed order
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct SignedOrder {
254    #[serde(flatten)]
255    pub order: Order,
256    pub signature: String,
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262    use alloy::primitives::Address;
263    use std::str::FromStr;
264
265    #[test]
266    fn test_order_serialization() {
267        let order = Order {
268            salt: "123".to_string(),
269            maker: Address::from_str("0x0000000000000000000000000000000000000001").unwrap(),
270            signer: Address::from_str("0x0000000000000000000000000000000000000002").unwrap(),
271            taker: Address::ZERO,
272            token_id: "456".to_string(),
273            maker_amount: "1000".to_string(),
274            taker_amount: "2000".to_string(),
275            expiration: "0".to_string(),
276            nonce: "789".to_string(),
277            fee_rate_bps: "0".to_string(),
278            side: OrderSide::Buy,
279            signature_type: SignatureType::Eoa,
280            neg_risk: false,
281        };
282
283        let signed_order = SignedOrder {
284            order,
285            signature: "0xabc".to_string(),
286        };
287
288        let json = serde_json::to_value(&signed_order).unwrap();
289
290        // Check camelCase
291        assert!(json.get("makerAmount").is_some());
292        assert!(json.get("takerAmount").is_some());
293        assert!(json.get("tokenId").is_some());
294        assert!(json.get("feeRateBps").is_some());
295        assert!(json.get("signatureType").is_some());
296
297        // Check flattened fields
298        assert!(json.get("signature").is_some());
299        assert!(json.get("salt").is_some());
300
301        // Check values
302        assert_eq!(json["makerAmount"], "1000");
303        assert_eq!(json["side"], "BUY");
304        assert_eq!(json["signatureType"], 0);
305        assert_eq!(json["nonce"], "789");
306    }
307}