Skip to main content

o2_api_types/domain/
book.rs

1use crate::{
2    OrderId,
3    domain::{
4        event::{
5            CancellationReason,
6            Identity,
7        },
8        trade::TradeId,
9    },
10    fuel_types::{
11        AssetId,
12        Bytes32,
13        ContractId,
14        TxId,
15    },
16    parse::{
17        HexDisplayFromStr,
18        serialize_hex,
19    },
20    primitives::{
21        OrderType,
22        Side,
23    },
24};
25use serde_with::{
26    DisplayFromStr,
27    serde_as,
28};
29use sha2::{
30    Digest,
31    Sha256,
32};
33use std::collections::BTreeMap;
34
35pub type Price = u64;
36pub type Quantity = u64;
37pub type MarketId = Bytes32;
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
40pub enum BaseQuantity {
41    Quantity(Quantity),
42    Infinite,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
46pub enum UnderlyingAsset {
47    Base {
48        remaining_quantity: Quantity,
49    },
50    Quote {
51        desired_base_quantity: BaseQuantity,
52        remaining_quote_token: Quantity,
53    },
54}
55
56#[derive(
57    Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash,
58)]
59pub struct MarketIdAssets {
60    pub base_asset: AssetId,
61    pub quote_asset: AssetId,
62}
63
64impl MarketIdAssets {
65    pub fn market_id(&self) -> MarketId {
66        let bytes: Vec<u8> = self
67            .quote_asset
68            .into_iter()
69            .chain(self.base_asset.to_vec())
70            .collect();
71        let digest = Sha256::digest(bytes.as_slice());
72        MarketId::new(digest.into())
73    }
74}
75
76#[serde_as]
77#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
78pub struct AssetConfig {
79    pub symbol: String,
80    #[serde(serialize_with = "serialize_hex")]
81    pub asset: AssetId,
82    pub decimals: u8,
83    pub min_precision: u8,
84    pub max_precision: u8,
85}
86
87/// Deployment settings for the single House book paired with a canonical book.
88#[serde_as]
89#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
90#[serde(deny_unknown_fields)]
91pub struct SidecarConfig {
92    /// Existing proxy address, filled in by deployment and reused on later runs.
93    #[serde_as(as = "Option<HexDisplayFromStr>")]
94    pub contract_id: Option<ContractId>,
95    /// Current loader blob, filled in after deployment or upgrade.
96    #[serde_as(as = "Option<HexDisplayFromStr>")]
97    pub blob_id: Option<ContractId>,
98    /// Independent House fees in millionths, using the canonical JSON convention.
99    #[serde_as(as = "DisplayFromStr")]
100    pub maker_fee: u64,
101    #[serde_as(as = "DisplayFromStr")]
102    pub taker_fee: u64,
103}
104
105#[serde_as]
106#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
107pub struct OrderBookConfig {
108    #[serde_as(as = "Option<HexDisplayFromStr>")]
109    pub contract_id: Option<ContractId>,
110    #[serde_as(as = "Option<HexDisplayFromStr>")]
111    pub blob_id: Option<ContractId>,
112    #[serde_as(as = "HexDisplayFromStr")]
113    pub market_id: MarketId,
114    #[serde_as(as = "DisplayFromStr")]
115    pub taker_fee: u64,
116    #[serde_as(as = "DisplayFromStr")]
117    pub maker_fee: u64,
118    #[serde_as(as = "DisplayFromStr")]
119    pub min_order: u64,
120    #[serde_as(as = "DisplayFromStr")]
121    pub dust: u64,
122    pub price_window: u8,
123    /// Whether prices that would truncate quote coins are allowed. When `false`
124    /// (the default), order/trigger creation rejects truncating prices and
125    /// activations floor `exec_quantity` to the largest non-truncating amount.
126    #[serde(default)]
127    pub allow_fractional_price: bool,
128    pub base: AssetConfig,
129    pub quote: AssetConfig,
130    /// Identities to grant HOUSE_ROLE on canonical; omission never revokes a role.
131    #[serde(default, skip_serializing_if = "Vec::is_empty")]
132    pub house_identities: Vec<String>,
133    /// Presence requests deployment or reuse, never activation.
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub sidecar: Option<SidecarConfig>,
136}
137
138#[derive(
139    Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize,
140)]
141pub struct Fill {
142    pub order_id: OrderId,
143    pub quantity: Quantity,
144    pub price: Price,
145    pub timestamp: u128,
146    pub fee: u64,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
150pub enum HistoryStep {
151    Confirmed { tx_id: TxId },
152}
153
154impl HistoryStep {
155    pub fn kind(&self) -> String {
156        match self {
157            HistoryStep::Confirmed { .. } => "confirmed".to_string(),
158        }
159    }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
163pub enum OrderHistory {
164    Created {
165        tx: HistoryStep,
166    },
167    Trade {
168        tx: HistoryStep,
169        trade_id: TradeId,
170    },
171    Canceled {
172        tx: HistoryStep,
173        reason: CancellationReason,
174    },
175}
176
177impl OrderHistory {
178    pub fn kind(&self) -> String {
179        match self {
180            OrderHistory::Created { .. } => "created".to_string(),
181            OrderHistory::Trade { .. } => "trade".to_string(),
182            OrderHistory::Canceled { .. } => "canceled".to_string(),
183        }
184    }
185
186    pub fn status_kind(&self) -> String {
187        match self {
188            OrderHistory::Created { tx } => tx.kind(),
189            OrderHistory::Trade { tx, .. } => tx.kind(),
190            OrderHistory::Canceled { tx, .. } => tx.kind(),
191        }
192    }
193
194    pub fn tx_id(&self) -> TxId {
195        match self {
196            OrderHistory::Created { tx } => match tx {
197                HistoryStep::Confirmed { tx_id } => *tx_id,
198            },
199            OrderHistory::Trade { tx, .. } => match tx {
200                HistoryStep::Confirmed { tx_id } => *tx_id,
201            },
202            OrderHistory::Canceled { tx, .. } => match tx {
203                HistoryStep::Confirmed { tx_id } => *tx_id,
204            },
205        }
206    }
207}
208
209#[derive(
210    Copy,
211    Clone,
212    Debug,
213    strum_macros::EnumCount,
214    strum_macros::IntoStaticStr,
215    strum_macros::FromRepr,
216    PartialEq,
217    Eq,
218    enum_iterator::Sequence,
219    Hash,
220)]
221#[repr(u8)]
222pub enum OrderStatus {
223    Active,
224    Canceled,
225    Filled,
226    PartiallyFilled,
227}
228
229impl TryFrom<u8> for OrderStatus {
230    type Error = anyhow::Error;
231
232    fn try_from(value: u8) -> Result<Self, Self::Error> {
233        OrderStatus::from_repr(value).ok_or_else(|| {
234            anyhow::anyhow!("Invalid value for OrderStatus enum: {}", value)
235        })
236    }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
240pub struct Order {
241    pub order_id: OrderId,
242    pub account: Identity,
243    pub side: Side,
244    pub order_type: OrderType,
245    pub price: Price,
246    pub desired_quantity: UnderlyingAsset,
247    pub fill: Vec<Fill>,
248    pub timestamp: u128,
249    pub order_tx_history: Vec<OrderHistory>,
250    pub base_decimals: u64,
251}
252
253#[serde_as]
254#[derive(Default, serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
255pub struct OrderBookBalance {
256    #[serde_as(as = "DisplayFromStr")]
257    locked: u128,
258    #[serde_as(as = "DisplayFromStr")]
259    unlocked: u128,
260    #[serde_as(as = "DisplayFromStr")]
261    fee: u128,
262}
263
264impl OrderBookBalance {
265    pub fn locked(&self) -> u128 {
266        self.locked
267    }
268
269    pub fn unlocked(&self) -> u128 {
270        self.unlocked
271    }
272
273    pub fn fee(&self) -> u128 {
274        self.fee
275    }
276}
277
278pub type OrderBooksBalances = BTreeMap<ContractId, OrderBookBalance>;