Skip to main content

nado_sdk/nado_utils/
trigger.rs

1use crate::eip712_structs::{Cancellation, DependencyUpdate, ListTriggerOrders};
2use crate::eip712_structs::{CancellationProducts, Order};
3use crate::engine::{ExecuteResponse, PlaceOrder, Status};
4use crate::serialize_utils::{deserialize_i128, serialize_i128, WrappedBytes32, WrappedI128};
5use ethers::types::{Bytes, H256};
6use eyre::Result;
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum CancelReason {
12    UserRequested,
13    LinkedSignerChanged,
14    Expired,
15    AccountHealth,
16    IsolatedSubaccountClosed,
17    DependentOrderCancelled,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
21pub struct ExecuteStatus {
22    pub execute_response: ExecuteResponse,
23    pub place_time: u64,
24}
25
26#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum TriggerOrderStatus {
29    Cancelled(CancelReason),
30    Triggered(ExecuteResponse),
31    InternalError(String),
32    Triggering,
33    WaitingPrice,
34    WaitingDependency,
35    // TWAP-specific statuses
36    TwapExecuting {
37        current_execution: u32,
38        total_executions: u32,
39    },
40    TwapCompleted {
41        executed: u32,
42        total: u32,
43    },
44}
45
46impl TriggerOrderStatus {
47    pub fn byte(&self) -> i32 {
48        (match self {
49            Self::Cancelled(_) => TriggerOrderStatusType::Cancelled,
50            Self::Triggered(_) => TriggerOrderStatusType::Triggered,
51            Self::InternalError(_) => TriggerOrderStatusType::InternalError,
52            Self::Triggering => TriggerOrderStatusType::Triggering,
53            Self::WaitingPrice => TriggerOrderStatusType::WaitingPrice,
54            Self::WaitingDependency => TriggerOrderStatusType::WaitingDependency,
55            Self::TwapExecuting { .. } => TriggerOrderStatusType::TwapExecuting,
56            Self::TwapCompleted { .. } => TriggerOrderStatusType::TwapCompleted,
57        }) as i32
58    }
59
60    pub fn byte_from_str(s: &str) -> Result<i32> {
61        match s {
62            "cancelled" => Ok(0),
63            "triggered" => Ok(1),
64            "internal_error" => Ok(2),
65            "triggering" => Ok(3),
66            "waiting_price" => Ok(4),
67            "waiting_dependency" => Ok(5),
68            "twap_executing" => Ok(6),
69            "twap_completed" => Ok(7),
70            _ => Err(eyre::eyre!("Invalid status string")),
71        }
72    }
73
74    pub fn data(&self) -> Vec<u8> {
75        serde_json::to_vec(self).unwrap()
76    }
77
78    pub fn from_status_data(data: &[u8]) -> Self {
79        serde_json::from_slice(data).unwrap()
80    }
81}
82
83#[derive(Clone, Serialize, Deserialize, Debug)]
84#[serde(rename_all = "snake_case")]
85pub enum TriggerCriteria {
86    PriceTrigger {
87        price_requirement: PriceRequirement,
88        dependency: Option<Dependency>,
89    },
90    TimeTrigger {
91        interval: u64,
92        amounts: Option<Vec<WrappedI128>>,
93    },
94}
95
96#[derive(Clone, Serialize, Deserialize, Debug)]
97pub struct Dependency {
98    pub digest: H256,
99    pub on_partial_fill: bool, // trigger on partial fill
100}
101
102#[derive(Clone, Serialize, Deserialize, Debug)]
103#[serde(rename_all = "snake_case")]
104pub enum PriceRequirement {
105    OraclePriceAbove(
106        #[serde(
107            serialize_with = "serialize_i128",
108            deserialize_with = "deserialize_i128"
109        )]
110        i128,
111    ),
112    OraclePriceBelow(
113        #[serde(
114            serialize_with = "serialize_i128",
115            deserialize_with = "deserialize_i128"
116        )]
117        i128,
118    ),
119
120    LastPriceAbove(
121        #[serde(
122            serialize_with = "serialize_i128",
123            deserialize_with = "deserialize_i128"
124        )]
125        i128,
126    ),
127    LastPriceBelow(
128        #[serde(
129            serialize_with = "serialize_i128",
130            deserialize_with = "deserialize_i128"
131        )]
132        i128,
133    ),
134
135    MidPriceAbove(
136        #[serde(
137            serialize_with = "serialize_i128",
138            deserialize_with = "deserialize_i128"
139        )]
140        i128,
141    ),
142
143    MidPriceBelow(
144        #[serde(
145            serialize_with = "serialize_i128",
146            deserialize_with = "deserialize_i128"
147        )]
148        i128,
149    ),
150}
151
152#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
153#[serde(rename_all = "snake_case")]
154pub enum TriggerType {
155    PriceTrigger = 0,
156    TimeTrigger = 1,
157    None = 2,
158}
159
160impl TriggerType {
161    pub fn from_appendix(appendix: u128) -> Self {
162        match appendix >> 12 & 3 {
163            1 => Self::PriceTrigger,
164            2 | 3 => Self::TimeTrigger,
165            _ => Self::None,
166        }
167    }
168}
169
170#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
171#[serde(rename_all = "snake_case")]
172pub enum TriggerOrderStatusType {
173    Cancelled = 0,
174    Triggered = 1,
175    InternalError = 2,
176    Triggering = 3,
177    WaitingPrice = 4,
178    WaitingDependency = 5,
179    TwapExecuting = 6,
180    TwapCompleted = 7,
181}
182
183impl TriggerOrderStatusType {
184    pub fn pending() -> Vec<Self> {
185        vec![
186            Self::WaitingPrice,
187            Self::WaitingDependency,
188            Self::TwapExecuting,
189        ]
190    }
191    pub fn pending_byte() -> Vec<i32> {
192        Self::pending().into_iter().map(|x| x as i32).collect()
193    }
194}
195
196impl TriggerCriteria {
197    pub fn byte(&self) -> i32 {
198        match self {
199            Self::PriceTrigger { .. } => 0,
200            Self::TimeTrigger { .. } => 1,
201        }
202    }
203
204    pub fn price_str(&self) -> Option<String> {
205        match self {
206            Self::PriceTrigger {
207                price_requirement, ..
208            } => Some(price_requirement.price_str()),
209            _ => None,
210        }
211    }
212
213    pub fn criteria_price_byte(&self) -> Option<i32> {
214        match self {
215            Self::PriceTrigger {
216                price_requirement, ..
217            } => Some(price_requirement.byte()),
218            _ => None,
219        }
220    }
221
222    pub fn dependency(&self) -> Option<[u8; 32]> {
223        match self {
224            Self::PriceTrigger { dependency, .. } => dependency.clone().map(|x| x.digest.0),
225            _ => None,
226        }
227    }
228}
229
230impl PriceRequirement {
231    pub fn byte(&self) -> i32 {
232        match self {
233            Self::OraclePriceAbove(_) => 0,
234            Self::OraclePriceBelow(_) => 1,
235            Self::LastPriceAbove(_) => 2,
236            Self::LastPriceBelow(_) => 3,
237            Self::MidPriceAbove(_) => 4,
238            Self::MidPriceBelow(_) => 5,
239        }
240    }
241
242    pub fn price(&self) -> i128 {
243        match self {
244            Self::OraclePriceAbove(price) => *price,
245            Self::OraclePriceBelow(price) => *price,
246            Self::LastPriceAbove(price) => *price,
247            Self::LastPriceBelow(price) => *price,
248            Self::MidPriceAbove(price) => *price,
249            Self::MidPriceBelow(price) => *price,
250        }
251    }
252
253    pub fn price_str(&self) -> String {
254        // for postgresql indexing, which doesn't support i128
255        format!("{:0>40}", self.price())
256    }
257}
258
259#[derive(Clone, Debug, Serialize, Deserialize)]
260#[serde(rename_all = "snake_case")]
261pub struct PlaceTriggerOrder {
262    pub order: Order,
263    pub signature: Bytes,
264    pub product_id: u32,
265    pub spot_leverage: Option<bool>,
266    pub borrow_margin: Option<bool>,
267    pub trigger: TriggerCriteria,
268    pub digest: Option<H256>,
269    pub id: Option<u64>,
270}
271
272impl PlaceTriggerOrder {
273    pub fn to_place_order(&self) -> PlaceOrder {
274        PlaceOrder {
275            order: self.order.clone(),
276            signature: self.signature.to_vec(),
277            product_id: self.product_id,
278            digest: None,
279            id: self.id,
280            spot_leverage: self.spot_leverage,
281            borrow_margin: self.borrow_margin,
282        }
283    }
284
285    pub fn order_data(&self) -> Vec<u8> {
286        bincode::serialize(self).unwrap()
287    }
288
289    pub fn from_order_data(data: &[u8]) -> Self {
290        bincode::deserialize(data).unwrap()
291    }
292
293    pub fn expiration(&self) -> u64 {
294        self.order.expiration
295    }
296
297    pub fn is_bid(&self) -> bool {
298        self.order.amount.is_positive()
299    }
300
301    pub fn trigger_amount(&self, index: u32) -> Option<i128> {
302        if !self.order.is_twap_random() {
303            None
304        } else {
305            match &self.trigger {
306                TriggerCriteria::TimeTrigger { amounts, .. } => {
307                    Some(amounts.as_ref().unwrap()[index as usize].0)
308                }
309                _ => unreachable!(),
310            }
311        }
312    }
313}
314
315#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
316#[serde(rename_all = "snake_case")]
317pub struct PlaceTriggerOrdersItemResponse {
318    pub digest: Option<[u8; 32]>,
319    pub error: Option<String>,
320}
321
322pub type PlaceTriggerOrdersResponse = Vec<PlaceTriggerOrdersItemResponse>;
323
324#[derive(Clone, Serialize, Deserialize, Debug)]
325#[serde(rename_all = "snake_case")]
326// #[ts(export)]ยท
327// #[ts(export_to = "tsBindings/msg/")]
328pub enum Execute {
329    PlaceOrder(PlaceTriggerOrder),
330    PlaceOrders {
331        orders: Vec<PlaceTriggerOrder>,
332        stop_on_failure: Option<bool>,
333    },
334    CancelOrders {
335        tx: Cancellation,
336        signature: Bytes,
337    },
338    CancelProductOrders {
339        tx: CancellationProducts,
340        signature: Bytes,
341    },
342    UpdateDependency {
343        tx: DependencyUpdate,
344        signature: Bytes,
345    },
346}
347
348#[derive(Debug, Deserialize, Serialize)]
349#[serde(rename_all = "snake_case")]
350#[serde(tag = "type")]
351pub enum Query {
352    ListTriggerOrders {
353        tx: ListTriggerOrders,
354        signature: Bytes,
355        product_ids: Option<Vec<u32>>,
356        trigger_types: Option<Vec<TriggerType>>,
357        status_types: Option<Vec<TriggerOrderStatusType>>,
358        max_update_time: Option<u64>,
359        max_digest: Option<WrappedBytes32>,
360        digests: Option<Vec<WrappedBytes32>>,
361        limit: Option<u32>,
362        reduce_only: Option<bool>,
363    },
364    ListTwapExecutions {
365        digest: WrappedBytes32,
366    },
367}
368
369#[derive(Clone, Debug, Serialize, Deserialize)]
370pub struct TriggerOrderInfo {
371    pub order: PlaceTriggerOrder,
372    pub status: TriggerOrderStatus,
373    pub placed_at: u64,
374    pub updated_at: u64,
375}
376
377#[derive(Clone, Debug, Serialize, Deserialize)]
378pub struct ListTriggerOrdersResponse {
379    pub orders: Vec<TriggerOrderInfo>,
380}
381
382impl ListTriggerOrdersResponse {
383    pub fn contains_digest(&self, digest: H256) -> bool {
384        self.orders
385            .iter()
386            .any(|order| order.order.digest == Some(digest))
387    }
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
391#[serde(rename_all = "snake_case")]
392pub enum TwapExecutionStatusData {
393    Pending,
394    Executing,
395    Executed {
396        executed_time: i64,
397        execute_response: ExecuteResponse,
398    },
399    Failed(String),
400    Cancelled(CancelReason),
401}
402
403impl TwapExecutionStatusData {
404    pub fn data(&self) -> Vec<u8> {
405        serde_json::to_vec(self).unwrap()
406    }
407
408    pub fn from_status_data(data: &[u8]) -> Self {
409        serde_json::from_slice(data).unwrap()
410    }
411}
412
413#[derive(Clone, Debug, Serialize, Deserialize)]
414pub struct ListTwapExecutionsResponse {
415    pub executions: Vec<TwapExecutionDetail>,
416}
417
418#[derive(Clone, Debug, Serialize, Deserialize)]
419pub struct TwapExecutionDetail {
420    pub execution_id: u32,
421    pub scheduled_time: i64,
422    pub status: TwapExecutionStatusData,
423    pub updated_at: i64,
424}
425
426#[derive(Clone, Debug, Serialize, Deserialize)]
427#[serde(rename_all = "snake_case")]
428#[serde(untagged)]
429pub enum QueryResponseData {
430    ListTriggerOrders(ListTriggerOrdersResponse),
431    ListTwapExecutions(ListTwapExecutionsResponse),
432    Error(String),
433}
434
435#[derive(Clone, Debug, Serialize, Deserialize)]
436// #[ts(export)]
437// #[ts(export_to = "tsBindings/msgResponses/")]
438#[serde(rename_all = "snake_case")]
439pub struct QueryResponse {
440    pub status: Status,
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub data: Option<QueryResponseData>,
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub error_code: Option<i32>,
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub error: Option<String>,
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub request_type: Option<String>,
449}