lightcone 0.7.1

Rust SDK for the Lightcone Protocol — unified native + WASM client
Documentation
//! Wire types for order and user WS messages.

use super::OrderStatus;
use crate::shared::{
    serde_util, OrderBookId, OrderUpdateType, PubkeyStr, Side, TimeInForce, TriggerResultStatus,
    TriggerStatus, TriggerType, TriggerUpdateType,
};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};

// ─── WS balance wire types ──────────────────────────────────────────────────

/// Balance for a single conditional token (from WS user updates).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ConditionalBalance {
    pub outcome_index: i16,
    #[serde(alias = "conditional_token")]
    pub mint: PubkeyStr,
    pub idle: Decimal,
    pub on_book: Decimal,
}

/// WS user balance update.
#[derive(Deserialize, Debug, Clone)]
pub struct UserBalanceUpdate {
    pub market_pubkey: PubkeyStr,
    pub orderbook_id: OrderBookId,
    pub balance: BalanceUpdateOutcomes,
    pub timestamp: DateTime<Utc>,
}

/// WS balance update payload.
#[derive(Deserialize, Debug, Clone)]
pub struct BalanceUpdateOutcomes {
    pub outcomes: Vec<ConditionalBalance>,
}

/// WS user snapshot balance.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct UserSnapshotBalance {
    pub market_pubkey: PubkeyStr,
    pub orderbook_id: OrderBookId,
    pub outcomes: Vec<ConditionalBalance>,
}

/// Global deposit balance for a single mint (used in snapshots).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GlobalDepositBalance {
    pub mint: PubkeyStr,
    pub balance: Decimal,
}

/// WS global deposit update event.
#[derive(Deserialize, Debug, Clone)]
pub struct GlobalDepositUpdate {
    pub mint: PubkeyStr,
    pub balance: Decimal,
    pub timestamp: DateTime<Utc>,
}

/// WS nonce update event.
#[derive(Deserialize, Debug, Clone)]
pub struct NonceUpdate {
    pub user_pubkey: PubkeyStr,
    pub new_nonce: u64,
    pub timestamp: DateTime<Utc>,
}

// ─── WS order wire types ────────────────────────────────────────────────────

/// WS order update event (limit orders).
#[derive(Deserialize, Debug, Clone)]
pub struct OrderUpdate {
    pub market_pubkey: PubkeyStr,
    pub orderbook_id: OrderBookId,
    pub timestamp: DateTime<Utc>,
    pub tx_signature: Option<String>,
    /// "PLACEMENT", "UPDATE", or "CANCELLATION".
    #[serde(rename = "type", default)]
    pub update_type: OrderUpdateType,
    pub order: WsOrder,
}

/// Individual order within a WS update.
#[derive(Deserialize, Debug, Clone)]
pub struct WsOrder {
    pub order_hash: String,
    pub price: Decimal,
    pub is_maker: bool,
    pub remaining: Decimal,
    pub filled: Decimal,
    pub fill_amount: Decimal,
    pub side: Side,
    #[serde(with = "serde_util::timestamp_ms")]
    pub created_at: DateTime<Utc>,
    pub base_mint: PubkeyStr,
    pub quote_mint: PubkeyStr,
    pub outcome_index: i16,
    #[serde(default)]
    pub status: OrderStatus,
    /// Balance is absent on cancellation events.
    #[serde(default)]
    pub balance: Option<UserOrderUpdateBalance>,
}

/// Fields shared by both limit and trigger order snapshots.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct UserSnapshotOrderCommon {
    pub order_hash: String,
    pub market_pubkey: PubkeyStr,
    pub orderbook_id: OrderBookId,
    pub side: Side,
    #[serde(alias = "maker_amount")]
    pub amount_in: Decimal,
    #[serde(alias = "taker_amount")]
    pub amount_out: Decimal,
    #[serde(default)]
    pub remaining: Decimal,
    #[serde(default)]
    pub filled: Decimal,
    #[serde(default)]
    pub price: Decimal,
    #[serde(with = "serde_util::timestamp_ms")]
    pub created_at: DateTime<Utc>,
    #[serde(default)]
    pub expiration: u64,
    pub base_mint: PubkeyStr,
    pub quote_mint: PubkeyStr,
    pub outcome_index: i16,
    #[serde(default)]
    pub status: OrderStatus,
}

/// Order snapshot — tagged enum discriminated by `order_type`.
///
/// Used in REST `GET /api/users/orders` and WS user snapshots.
/// The backend returns limit and trigger orders in the same array,
/// distinguished by the `order_type` field.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(tag = "order_type", rename_all = "lowercase")]
pub enum UserSnapshotOrder {
    Limit {
        #[serde(flatten)]
        common: UserSnapshotOrderCommon,
        #[serde(default)]
        tx_signature: Option<String>,
    },
    Trigger {
        #[serde(flatten)]
        common: UserSnapshotOrderCommon,
        trigger_order_id: String,
        trigger_price: Decimal,
        trigger_type: TriggerType,
        #[serde(
            default,
            with = "serde_util::tif_numeric_opt",
            skip_serializing_if = "Option::is_none"
        )]
        time_in_force: Option<TimeInForce>,
    },
}

impl UserSnapshotOrder {
    pub fn common(&self) -> &UserSnapshotOrderCommon {
        match self {
            UserSnapshotOrder::Limit { common, .. } => common,
            UserSnapshotOrder::Trigger { common, .. } => common,
        }
    }
}

/// Balance information attached to an order update.
#[derive(Deserialize, Debug, Clone)]
pub struct UserOrderUpdateBalance {
    pub outcomes: Vec<ConditionalBalance>,
}

/// WS user snapshot.
#[derive(Deserialize, Debug, Clone)]
pub struct UserSnapshot {
    pub orders: Vec<UserSnapshotOrder>,
    pub balances: std::collections::HashMap<OrderBookId, UserSnapshotBalance>,
    #[serde(default)]
    pub global_deposits: Vec<GlobalDepositBalance>,
    #[serde(default)]
    pub notifications: Vec<crate::domain::notification::Notification>,
    #[serde(default)]
    pub nonce: u64,
}

// ─── Trigger order wire types ───────────────────────────────────────────────

/// Trigger order WS update event on `user_events` channel.
#[derive(Debug, Clone, Deserialize)]
pub struct TriggerOrderUpdate {
    pub trigger_order_id: String,
    #[serde(default)]
    pub user_pubkey: PubkeyStr,
    pub market_pubkey: PubkeyStr,
    pub orderbook_id: OrderBookId,
    pub trigger_price: Decimal,
    pub trigger_above: bool,
    pub status: TriggerStatus,
    /// Uppercase version of status: "CREATED", "TRIGGERED", "FAILED", or "EXPIRED".
    #[serde(rename = "type", default)]
    pub update_type: TriggerUpdateType,
    pub order_hash: String,
    pub side: Side,
    #[serde(default, with = "serde_util::empty_string_as_none")]
    pub result_status: Option<TriggerResultStatus>,
    #[serde(default)]
    pub result_filled: Decimal,
    #[serde(default)]
    pub result_remaining: Decimal,
    pub timestamp: DateTime<Utc>,
    #[serde(default)]
    pub maker_amount: Decimal,
    #[serde(default)]
    pub taker_amount: Decimal,
    #[serde(default, with = "serde_util::tif_numeric")]
    pub tif: TimeInForce,
}

#[cfg(feature = "trigger_orders")]
impl TriggerOrderUpdate {
    /// Convert this update into a domain TriggerOrder.
    pub fn into_trigger_order(self) -> super::TriggerOrder {
        let trigger_type = if self.trigger_above {
            TriggerType::TakeProfit
        } else {
            TriggerType::StopLoss
        };

        super::TriggerOrder {
            trigger_order_id: self.trigger_order_id,
            order_hash: self.order_hash,
            market_pubkey: self.market_pubkey,
            orderbook_id: self.orderbook_id,
            trigger_price: self.trigger_price,
            trigger_type,
            side: self.side,
            amount_in: self.maker_amount,
            amount_out: self.taker_amount,
            time_in_force: self.tif,
            created_at: self.timestamp,
        }
    }
}

/// WS order event — two-level dispatch on `order_type`.
///
/// Both limit and trigger order updates arrive as `event_type: "order"`,
/// discriminated by `order_type: "limit"` or `"trigger"`.
#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "order_type")]
pub enum OrderEvent {
    #[serde(rename = "limit")]
    Limit(OrderUpdate),
    #[serde(rename = "trigger")]
    Trigger(TriggerOrderUpdate),
}

/// WS user update — tagged enum.
#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "event_type")]
pub enum UserUpdate {
    #[serde(rename = "snapshot")]
    Snapshot(UserSnapshot),
    #[serde(rename = "order")]
    Order(OrderEvent),
    #[serde(rename = "balance_update")]
    BalanceUpdate(UserBalanceUpdate),
    #[serde(rename = "global_deposit_update")]
    GlobalDepositUpdate(GlobalDepositUpdate),
    #[serde(rename = "nonce")]
    NonceUpdate(NonceUpdate),
    #[serde(rename = "notification")]
    Notification(NotificationUpdate),
}

/// WS notification push event.
#[derive(Deserialize, Debug, Clone)]
pub struct NotificationUpdate {
    pub notification: crate::domain::notification::Notification,
}

/// WS auth update.
#[derive(Deserialize, Debug, Clone)]
pub struct Authenticated {
    pub wallet: PubkeyStr,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(tag = "status")]
pub enum AuthUpdate {
    #[serde(rename = "authenticated")]
    Authenticated(Authenticated),
    #[serde(rename = "anonymous")]
    Anonymous {
        #[serde(default)]
        reason: Option<String>,
    },
}

// ─── User order fills (REST) ───────────────────────────────────────────────

/// Response from `GET /api/users/order-fills`.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UserOrderFillsResponse {
    pub orders: Vec<UserOrderFill>,
    pub next_cursor: Option<String>,
    pub has_more: bool,
}

/// An order the user participated in, with nested fill events.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UserOrderFill {
    pub order_hash: String,
    pub market_pubkey: PubkeyStr,
    pub orderbook_id: OrderBookId,
    pub side: Side,
    pub role: Role,
    pub price: Decimal,
    pub size: Decimal,
    pub filled_size: Decimal,
    pub remaining_size: Decimal,
    pub base_mint: PubkeyStr,
    pub quote_mint: PubkeyStr,
    pub outcome_index: i16,
    pub status: FillStatus,
    #[serde(with = "serde_util::timestamp_ms")]
    pub created_at: DateTime<Utc>,
    pub fills: Vec<OrderFillEvent>,
}

/// A single fill event within an order.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct OrderFillEvent {
    pub fill_amount: Decimal,
    pub tx_signature: String,
    #[serde(with = "serde_util::timestamp_ms")]
    pub filled_at: DateTime<Utc>,
}

/// Whether the user was the maker or taker on an order.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Role {
    Maker,
    Taker,
}

/// Status of a filled order, derived from DB state after the fact.
///
/// Distinct from `OrderStatus` which is the engine's real-time state.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum FillStatus {
    Filled,
    Cancelled,
    PartiallyFilled,
}