Skip to main content

predict_sdk/
types.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3/// Chain ID for BNB Chain networks
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[repr(u64)]
6pub enum ChainId {
7    BnbMainnet = 56,
8    BnbTestnet = 97,
9}
10
11impl ChainId {
12    pub fn as_u64(&self) -> u64 {
13        *self as u64
14    }
15}
16
17impl TryFrom<u64> for ChainId {
18    type Error = crate::Error;
19
20    fn try_from(value: u64) -> Result<Self, Self::Error> {
21        match value {
22            56 => Ok(ChainId::BnbMainnet),
23            97 => Ok(ChainId::BnbTestnet),
24            _ => Err(crate::Error::InvalidChainId(value)),
25        }
26    }
27}
28
29/// Order side: BUY or SELL
30///
31/// Serializes as numeric value (0 = Buy, 1 = Sell) to match the Predict API format.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33#[repr(u8)]
34pub enum Side {
35    Buy = 0,
36    Sell = 1,
37}
38
39impl Serialize for Side {
40    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
41        serializer.serialize_u8(*self as u8)
42    }
43}
44
45impl<'de> Deserialize<'de> for Side {
46    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
47        struct SideVisitor;
48
49        impl<'de> serde::de::Visitor<'de> for SideVisitor {
50            type Value = Side;
51
52            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
53                formatter.write_str("0, 1, \"BUY\", or \"SELL\"")
54            }
55
56            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Side, E> {
57                match value {
58                    0 => Ok(Side::Buy),
59                    1 => Ok(Side::Sell),
60                    _ => Err(E::invalid_value(serde::de::Unexpected::Unsigned(value), &self)),
61                }
62            }
63
64            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Side, E> {
65                match value {
66                    "BUY" | "Buy" | "buy" => Ok(Side::Buy),
67                    "SELL" | "Sell" | "sell" => Ok(Side::Sell),
68                    _ => Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)),
69                }
70            }
71        }
72
73        deserializer.deserialize_any(SideVisitor)
74    }
75}
76
77/// Signature type for orders
78/// EOA also supports EIP-1271
79///
80/// Serializes as numeric value (0 = EOA, 1 = PolyProxy, 2 = PolyGnosisSafe) to match the Predict API format.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82#[repr(u8)]
83pub enum SignatureType {
84    Eoa = 0,
85    PolyProxy = 1,
86    PolyGnosisSafe = 2,
87}
88
89impl Serialize for SignatureType {
90    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
91        serializer.serialize_u8(*self as u8)
92    }
93}
94
95impl<'de> Deserialize<'de> for SignatureType {
96    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
97        struct SignatureTypeVisitor;
98
99        impl<'de> serde::de::Visitor<'de> for SignatureTypeVisitor {
100            type Value = SignatureType;
101
102            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
103                formatter.write_str("0, 1, 2, or a string like \"EOA\"")
104            }
105
106            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<SignatureType, E> {
107                match value {
108                    0 => Ok(SignatureType::Eoa),
109                    1 => Ok(SignatureType::PolyProxy),
110                    2 => Ok(SignatureType::PolyGnosisSafe),
111                    _ => Err(E::invalid_value(serde::de::Unexpected::Unsigned(value), &self)),
112                }
113            }
114
115            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<SignatureType, E> {
116                match value {
117                    "EOA" | "eoa" => Ok(SignatureType::Eoa),
118                    "POLY_PROXY" => Ok(SignatureType::PolyProxy),
119                    "POLY_GNOSIS_SAFE" => Ok(SignatureType::PolyGnosisSafe),
120                    _ => Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)),
121                }
122            }
123        }
124
125        deserializer.deserialize_any(SignatureTypeVisitor)
126    }
127}
128
129/// Market type indicators
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub struct MarketType {
132    pub is_neg_risk: bool,
133    pub is_yield_bearing: bool,
134}
135
136/// Order structure matching predict.fun specification
137#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct Order {
140    /// A unique salt to ensure entropy
141    pub salt: String,
142
143    /// The maker of the order, e.g. the order's signer
144    pub maker: String,
145
146    /// The signer of the order
147    pub signer: String,
148
149    /// The address of the order taker. The zero address is used to indicate a public order
150    pub taker: String,
151
152    /// The token ID of the CTF ERC-1155 asset to be bought or sold
153    pub token_id: String,
154
155    /// The maker amount
156    ///
157    /// For a BUY order, this represents the total `(price per asset * assets quantity)` collateral (e.g. USDT) being offered.
158    /// For a SELL order, this represents the total amount of CTF assets being offered.
159    pub maker_amount: String,
160
161    /// The taker amount
162    ///
163    /// For a BUY order, this represents the total amount of CTF assets to be received.
164    /// For a SELL order, this represents the total `(price per asset * assets quantity)` amount of collateral (e.g. USDT) to be received.
165    pub taker_amount: String,
166
167    /// The timestamp in seconds after which the order is expired
168    pub expiration: String,
169
170    /// The nonce used for on-chain cancellations
171    pub nonce: String,
172
173    /// The fee rate, in basis points
174    pub fee_rate_bps: String,
175
176    /// The side of the order, BUY (Bid) or SELL (Ask)
177    pub side: Side,
178
179    /// Signature type used by the Order (EOA also supports EIP-1271)
180    pub signature_type: SignatureType,
181}
182
183/// Signed order with signature and optional hash
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct SignedOrder {
187    #[serde(flatten)]
188    pub order: Order,
189
190    /// The order hash
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub hash: Option<String>,
193
194    /// The order signature
195    pub signature: String,
196}
197
198/// Input for building an order
199#[derive(Debug, Clone)]
200pub struct BuildOrderInput {
201    pub side: Side,
202    pub token_id: String,
203    pub maker_amount: String,
204    pub taker_amount: String,
205    pub fee_rate_bps: u64,
206    pub signer: Option<String>,
207    pub nonce: Option<String>,
208    pub salt: Option<String>,
209    pub maker: Option<String>,
210    pub taker: Option<String>,
211    pub signature_type: Option<SignatureType>,
212    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
213}
214
215/// Order strategy type
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum OrderStrategy {
218    Market,
219    Limit,
220}
221
222impl OrderStrategy {
223    pub fn as_str(&self) -> &'static str {
224        match self {
225            OrderStrategy::Market => "MARKET",
226            OrderStrategy::Limit => "LIMIT",
227        }
228    }
229}
230
231/// Input data for limit order amount calculations
232#[derive(Debug, Clone)]
233pub struct LimitOrderData {
234    pub side: Side,
235    /// Price per share in wei (18 decimals)
236    pub price_per_share_wei: rust_decimal::Decimal,
237    /// Quantity in wei (18 decimals)
238    pub quantity_wei: rust_decimal::Decimal,
239}
240
241/// Calculated order amounts for limit orders
242#[derive(Debug, Clone)]
243pub struct LimitOrderAmounts {
244    pub last_price: rust_decimal::Decimal,
245    pub price_per_share: rust_decimal::Decimal,
246    pub maker_amount: rust_decimal::Decimal,
247    pub taker_amount: rust_decimal::Decimal,
248}
249
250/// Orderbook depth level [price, size]
251pub type DepthLevel = (f64, f64);
252
253/// Orderbook structure
254#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct Book {
257    pub market_id: u64,
258    pub update_timestamp_ms: u64,
259    pub asks: Vec<DepthLevel>,
260    pub bids: Vec<DepthLevel>,
261}
262
263/// Options for canceling orders
264#[derive(Debug, Clone)]
265pub struct CancelOrdersOptions {
266    pub is_yield_bearing: bool,
267    pub is_neg_risk: bool,
268    pub with_validation: bool,
269}
270
271impl Default for CancelOrdersOptions {
272    fn default() -> Self {
273        Self {
274            is_yield_bearing: false,
275            is_neg_risk: false,
276            with_validation: true,
277        }
278    }
279}
280
281/// Options for redeeming positions
282#[derive(Debug, Clone)]
283pub struct RedeemPositionsOptions {
284    pub condition_id: String,
285    pub index_set: u8, // 1 or 2
286    pub is_neg_risk: bool,
287    pub is_yield_bearing: bool,
288    pub amount: Option<rust_decimal::Decimal>,
289}
290
291/// Options for merging positions
292#[derive(Debug, Clone)]
293pub struct MergePositionsOptions {
294    pub condition_id: String,
295    pub amount: rust_decimal::Decimal,
296    pub is_neg_risk: bool,
297    pub is_yield_bearing: bool,
298}
299
300/// Options for splitting positions
301#[derive(Debug, Clone)]
302pub struct SplitPositionsOptions {
303    pub condition_id: String,
304    pub amount: rust_decimal::Decimal,
305    pub is_neg_risk: bool,
306    pub is_yield_bearing: bool,
307}