From 871f9e0ee0c680feb0e24b9c166e18ff5724d2d9 Mon Sep 17 00:00:00 2001 From: "D.H" Date: Thu, 20 Aug 2026 15:49:30 +0800 Subject: [PATCH] fix(ws): keep spot asset ctx fields and map identifier activeSpotAssetCtx payloads include totalSupply and dayBaseVlm which were dropped, and there was no test that spot ctx still routes to the ActiveAssetCtx subscription key. Persist those optional fields and add deserialize plus identifier regression tests. Fixes hyperliquid-dex/hyperliquid-rust-sdk#88. Co-authored-by: Cursor --- src/ws/sub_structs.rs | 4 +++ src/ws/ws_manager.rs | 82 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/ws/sub_structs.rs b/src/ws/sub_structs.rs index 4c3c94e5..53d13547 100644 --- a/src/ws/sub_structs.rs +++ b/src/ws/sub_structs.rs @@ -332,6 +332,10 @@ pub struct SpotAssetCtx { #[serde(flatten)] pub shared: SharedAssetCtx, pub circulating_supply: String, + #[serde(default)] + pub total_supply: Option, + #[serde(default)] + pub day_base_vlm: Option, } #[derive(Deserialize, Serialize, Clone, Debug)] diff --git a/src/ws/ws_manager.rs b/src/ws/ws_manager.rs index 4035baf3..e48b8c3c 100755 --- a/src/ws/ws_manager.rs +++ b/src/ws/ws_manager.rs @@ -495,3 +495,85 @@ impl Drop for WsManager { self.stop_flag.store(true, Ordering::Relaxed); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn active_spot_asset_ctx_deserializes_and_maps_identifier() { + let json = r#"{ + "channel": "activeSpotAssetCtx", + "data": { + "coin": "@107", + "ctx": { + "prevDayPx": "23.993", + "dayNtlVlm": "228614808.04913011", + "markPx": "23.277", + "midPx": "23.281", + "circulatingSupply": "334395529.28086698", + "coin": "@107", + "totalSupply": "999840322.48575342", + "dayBaseVlm": "9756716.93999999" + } + } + }"#; + + let msg: Message = serde_json::from_str(json).expect("sample activeSpotAssetCtx JSON"); + match &msg { + Message::ActiveSpotAssetCtx(spot) => { + assert_eq!(spot.data.coin, "@107"); + assert_eq!( + spot.data.ctx.total_supply.as_deref(), + Some("999840322.48575342") + ); + assert_eq!( + spot.data.ctx.day_base_vlm.as_deref(), + Some("9756716.93999999") + ); + } + other => panic!("expected ActiveSpotAssetCtx, got {other:?}"), + } + + assert_eq!( + WsManager::get_identifier(&msg).unwrap(), + serde_json::to_string(&Subscription::ActiveAssetCtx { + coin: "@107".into() + }) + .unwrap() + ); + } + + #[test] + fn active_asset_ctx_perps_maps_identifier() { + let json = r#"{ + "channel": "activeAssetCtx", + "data": { + "coin": "BTC", + "ctx": { + "funding": "0.0001", + "openInterest": "1000.0", + "oraclePx": "50000.0", + "dayNtlVlm": "1.0", + "prevDayPx": "49000.0", + "markPx": "50000.0", + "midPx": "50000.0" + } + } + }"#; + + let msg: Message = serde_json::from_str(json).expect("minimal perps activeAssetCtx JSON"); + match &msg { + Message::ActiveAssetCtx(_) => {} + other => panic!("expected ActiveAssetCtx, got {other:?}"), + } + + assert_eq!( + WsManager::get_identifier(&msg).unwrap(), + serde_json::to_string(&Subscription::ActiveAssetCtx { + coin: "BTC".into() + }) + .unwrap() + ); + } +}