diff --git a/src/book.rs b/src/book.rs index 24be36e..3fa6030 100644 --- a/src/book.rs +++ b/src/book.rs @@ -460,6 +460,8 @@ impl OrderBook { bids: self.bids(None), // Get all bids (up to max_depth) asks: self.asks(None), // Get all asks (up to max_depth) sequence: self.last_delta_sequence, + last_delta_sequence: self.last_delta_sequence, + last_snapshot_timestamp_ms: self.last_snapshot_timestamp_ms, } } @@ -1526,6 +1528,11 @@ mod tests { assert_eq!(book.last_snapshot_timestamp_ms, 1_000); assert_eq!(book.best_bid().unwrap().price, dec!(0.50)); assert_eq!(book.best_ask().unwrap().price, dec!(0.60)); + + let snapshot = book.snapshot(); + assert_eq!(snapshot.sequence, 10_000); + assert_eq!(snapshot.last_delta_sequence, 10_000); + assert_eq!(snapshot.last_snapshot_timestamp_ms, 1_000); } #[test] diff --git a/src/decode.rs b/src/decode.rs index d81624b..75a7a6a 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -341,6 +341,8 @@ impl Decoder for RawOrderBookResponse { bids, asks, sequence: 0, // TODO: Get from response if available + last_delta_sequence: 0, + last_snapshot_timestamp_ms: 0, }) } } diff --git a/src/types.rs b/src/types.rs index 27d9abd..c3f4da8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -358,7 +358,7 @@ impl FastBookLevel { } /// Full order book state -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize)] pub struct OrderBook { /// Token ID pub token_id: String, @@ -368,8 +368,47 @@ pub struct OrderBook { pub bids: Vec, /// Ask orders pub asks: Vec, - /// Sequence number + /// Legacy/incremental delta sequence number. + /// + /// This is kept for compatibility. For snapshots produced by this client, + /// it is equivalent to [`Self::last_delta_sequence`]. WebSocket snapshot + /// timestamps are exposed separately in [`Self::last_snapshot_timestamp_ms`]. pub sequence: u64, + /// Last accepted legacy/incremental delta sequence number. + pub last_delta_sequence: u64, + /// Last accepted full-book snapshot timestamp in milliseconds. + pub last_snapshot_timestamp_ms: u64, +} + +impl<'de> Deserialize<'de> for OrderBook { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct WireOrderBook { + token_id: String, + timestamp: DateTime, + bids: Vec, + asks: Vec, + sequence: u64, + #[serde(default)] + last_delta_sequence: Option, + #[serde(default)] + last_snapshot_timestamp_ms: Option, + } + + let wire = WireOrderBook::deserialize(deserializer)?; + Ok(Self { + token_id: wire.token_id, + timestamp: wire.timestamp, + bids: wire.bids, + asks: wire.asks, + sequence: wire.sequence, + last_delta_sequence: wire.last_delta_sequence.unwrap_or(wire.sequence), + last_snapshot_timestamp_ms: wire.last_snapshot_timestamp_ms.unwrap_or_default(), + }) + } } /// Order book delta for streaming updates - EXTERNAL API VERSION @@ -1922,4 +1961,20 @@ mod tests { Decimal::from_str("0.00005").unwrap() )); } + + #[test] + fn order_book_snapshot_clocks_default_for_legacy_payloads() { + let json = r#"{ + "token_id":"test_token", + "timestamp":"2026-06-23T00:00:00Z", + "bids":[], + "asks":[], + "sequence":42 + }"#; + + let book: OrderBook = serde_json::from_str(json).unwrap(); + assert_eq!(book.sequence, 42); + assert_eq!(book.last_delta_sequence, 42); + assert_eq!(book.last_snapshot_timestamp_ms, 0); + } } diff --git a/src/ws_hot_path.rs b/src/ws_hot_path.rs index bc42aed..d78d8dc 100644 --- a/src/ws_hot_path.rs +++ b/src/ws_hot_path.rs @@ -371,6 +371,8 @@ mod tests { let snapshot = books.get_book("test_asset_id").unwrap(); assert_eq!(snapshot.sequence, 0); + assert_eq!(snapshot.last_delta_sequence, 0); + assert_eq!(snapshot.last_snapshot_timestamp_ms, 1000); assert_eq!(snapshot.timestamp.timestamp_millis(), 1000); assert_eq!(snapshot.bids.len(), 1); assert_eq!(snapshot.asks.len(), 1); @@ -409,6 +411,8 @@ mod tests { let snapshot = books.get_book("test_asset_id").unwrap(); assert_eq!(snapshot.sequence, 0); + assert_eq!(snapshot.last_delta_sequence, 0); + assert_eq!(snapshot.last_snapshot_timestamp_ms, 1000); assert_eq!(snapshot.timestamp.timestamp_millis(), 1000); assert_eq!(snapshot.bids.len(), 1); assert_eq!(snapshot.asks.len(), 1); @@ -436,6 +440,8 @@ mod tests { let snapshot = books.get_book("test_asset_id").unwrap(); assert_eq!(snapshot.sequence, 0); + assert_eq!(snapshot.last_delta_sequence, 0); + assert_eq!(snapshot.last_snapshot_timestamp_ms, 1000); assert_eq!(snapshot.timestamp.timestamp_millis(), 1000); assert_eq!(snapshot.bids[0].price, dec!(0.51)); assert_eq!(snapshot.bids[0].size, dec!(11));