hyperliquid_sdk_rs/types/
ws.rs

1//! WebSocket message types for Hyperliquid
2
3use std::collections::HashMap;
4
5use alloy::primitives::Address;
6use serde::{Deserialize, Serialize};
7
8// Subscription types
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(tag = "type", rename_all = "camelCase")]
11pub enum Subscription {
12    AllMids,
13    Notification { user: Address },
14    WebData2 { user: Address },
15    Candle { coin: String, interval: String },
16    L2Book { coin: String },
17    Trades { coin: String },
18    OrderUpdates { user: Address },
19    UserEvents { user: Address },
20    UserFills { user: Address },
21    UserFundings { user: Address },
22    UserNonFundingLedgerUpdates { user: Address },
23    // Phase 1 new subscriptions
24    Bbo { coin: String },
25    OpenOrders { user: Address },
26    ClearinghouseState { user: Address },
27    // Phase 2 new subscriptions
28    WebData3 { user: Address },
29    TwapStates { user: Address },
30    ActiveAssetCtx { coin: String },
31    ActiveAssetData { user: Address, coin: String },
32    UserTwapSliceFills { user: Address },
33    UserTwapHistory { user: Address },
34}
35
36// Incoming message types
37#[derive(Debug, Clone, Deserialize)]
38#[serde(tag = "channel", rename_all = "camelCase")]
39pub enum Message {
40    AllMids(AllMids),
41    Trades(Trades),
42    L2Book(L2Book),
43    Candle(Candle),
44    OrderUpdates(OrderUpdates),
45    UserFills(UserFills),
46    UserFundings(UserFundings),
47    UserNonFundingLedgerUpdates(UserNonFundingLedgerUpdates),
48    Notification(Notification),
49    WebData2(WebData2),
50    User(User),
51    SubscriptionResponse,
52    Pong,
53    // Phase 1 new message types
54    Bbo(Bbo),
55    OpenOrders(OpenOrdersWs),
56    ClearinghouseState(ClearinghouseStateWs),
57    // Phase 2 new message types
58    WebData3(WebData3Ws),
59    TwapStates(TwapStatesWs),
60    ActiveAssetCtx(ActiveAssetCtxWs),
61    ActiveAssetData(ActiveAssetDataWs),
62    UserTwapSliceFills(UserTwapSliceFillsWs),
63    UserTwapHistory(UserTwapHistoryWs),
64}
65
66// Market data structures
67#[derive(Debug, Clone, Deserialize)]
68pub struct AllMids {
69    pub data: AllMidsData,
70}
71
72#[derive(Debug, Clone, Deserialize)]
73pub struct AllMidsData {
74    pub mids: HashMap<String, String>,
75}
76
77#[derive(Debug, Clone, Deserialize)]
78pub struct Trades {
79    pub data: Vec<Trade>,
80}
81
82#[derive(Debug, Clone, Deserialize)]
83pub struct Trade {
84    pub coin: String,
85    pub side: String,
86    pub px: String,
87    pub sz: String,
88    pub time: u64,
89    pub hash: String,
90    pub tid: u64,
91}
92
93#[derive(Debug, Clone, Deserialize)]
94pub struct L2Book {
95    pub data: L2BookData,
96}
97
98#[derive(Debug, Clone, Deserialize)]
99pub struct L2BookData {
100    pub coin: String,
101    pub time: u64,
102    pub levels: Vec<Vec<BookLevel>>,
103}
104
105#[derive(Debug, Clone, Deserialize)]
106pub struct BookLevel {
107    pub px: String,
108    pub sz: String,
109    pub n: u64,
110}
111
112#[derive(Debug, Clone, Deserialize)]
113pub struct Candle {
114    pub data: CandleData,
115}
116
117#[derive(Debug, Clone, Deserialize)]
118pub struct CandleData {
119    #[serde(rename = "T")]
120    pub time_close: u64,
121    #[serde(rename = "c")]
122    pub close: String,
123    #[serde(rename = "h")]
124    pub high: String,
125    #[serde(rename = "i")]
126    pub interval: String,
127    #[serde(rename = "l")]
128    pub low: String,
129    #[serde(rename = "n")]
130    pub num_trades: u64,
131    #[serde(rename = "o")]
132    pub open: String,
133    #[serde(rename = "s")]
134    pub coin: String,
135    #[serde(rename = "t")]
136    pub time_open: u64,
137    #[serde(rename = "v")]
138    pub volume: String,
139}
140
141// User event structures
142#[derive(Debug, Clone, Deserialize)]
143pub struct OrderUpdates {
144    pub data: Vec<OrderUpdate>,
145}
146
147#[derive(Debug, Clone, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct OrderUpdate {
150    pub order: BasicOrder,
151    pub status: String,
152    pub status_timestamp: u64,
153}
154
155#[derive(Debug, Clone, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct BasicOrder {
158    pub coin: String,
159    pub side: String,
160    pub limit_px: String,
161    pub sz: String,
162    pub oid: u64,
163    pub timestamp: u64,
164    pub orig_sz: String,
165    pub cloid: Option<String>,
166}
167
168#[derive(Debug, Clone, Deserialize)]
169pub struct UserFills {
170    pub data: UserFillsData,
171}
172
173#[derive(Debug, Clone, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct UserFillsData {
176    pub is_snapshot: Option<bool>,
177    pub user: Address,
178    pub fills: Vec<TradeInfo>,
179}
180
181#[derive(Debug, Clone, Deserialize)]
182#[serde(rename_all = "camelCase")]
183pub struct TradeInfo {
184    pub coin: String,
185    pub side: String,
186    pub px: String,
187    pub sz: String,
188    pub time: u64,
189    pub hash: String,
190    pub start_position: String,
191    pub dir: String,
192    pub closed_pnl: String,
193    pub oid: u64,
194    pub cloid: Option<String>,
195    pub crossed: bool,
196    pub fee: String,
197    pub fee_token: String,
198    pub tid: u64,
199}
200
201#[derive(Debug, Clone, Deserialize)]
202pub struct UserFundings {
203    pub data: UserFundingsData,
204}
205
206#[derive(Debug, Clone, Deserialize)]
207#[serde(rename_all = "camelCase")]
208pub struct UserFundingsData {
209    pub is_snapshot: Option<bool>,
210    pub user: Address,
211    pub fundings: Vec<UserFunding>,
212}
213
214#[derive(Debug, Clone, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct UserFunding {
217    pub time: u64,
218    pub coin: String,
219    pub usdc: String,
220    pub szi: String,
221    pub funding_rate: String,
222}
223
224#[derive(Debug, Clone, Deserialize)]
225pub struct UserNonFundingLedgerUpdates {
226    pub data: UserNonFundingLedgerUpdatesData,
227}
228
229#[derive(Debug, Clone, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct UserNonFundingLedgerUpdatesData {
232    pub is_snapshot: Option<bool>,
233    pub user: Address,
234    pub non_funding_ledger_updates: Vec<LedgerUpdateData>,
235}
236
237#[derive(Debug, Clone, Deserialize)]
238pub struct LedgerUpdateData {
239    pub time: u64,
240    pub hash: String,
241    pub delta: LedgerUpdate,
242}
243
244#[derive(Debug, Clone, Deserialize)]
245#[serde(rename_all = "camelCase")]
246#[serde(tag = "type")]
247pub enum LedgerUpdate {
248    Deposit {
249        usdc: String,
250    },
251    Withdraw {
252        usdc: String,
253        nonce: u64,
254        fee: String,
255    },
256    InternalTransfer {
257        usdc: String,
258        user: Address,
259        destination: Address,
260        fee: String,
261    },
262    SubAccountTransfer {
263        usdc: String,
264        user: Address,
265        destination: Address,
266    },
267    SpotTransfer {
268        token: String,
269        amount: String,
270        user: Address,
271        destination: Address,
272        fee: String,
273    },
274}
275
276#[derive(Debug, Clone, Deserialize)]
277pub struct Notification {
278    pub data: NotificationData,
279}
280
281#[derive(Debug, Clone, Deserialize)]
282pub struct NotificationData {
283    pub notification: String,
284}
285
286#[derive(Debug, Clone, Deserialize)]
287pub struct WebData2 {
288    pub data: WebData2Data,
289}
290
291#[derive(Debug, Clone, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct WebData2Data {
294    pub user: Address,
295}
296
297#[derive(Debug, Clone, Deserialize)]
298pub struct User {
299    pub data: UserData,
300}
301
302#[derive(Debug, Clone, Deserialize)]
303#[serde(rename_all = "camelCase")]
304#[serde(untagged)]
305pub enum UserData {
306    Fills(Vec<TradeInfo>),
307    Funding(UserFunding),
308}
309
310// WebSocket protocol messages
311#[derive(Debug, Serialize)]
312pub struct WsRequest {
313    pub method: &'static str,
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub subscription: Option<Subscription>,
316}
317
318impl WsRequest {
319    pub fn subscribe(subscription: Subscription) -> Self {
320        Self {
321            method: "subscribe",
322            subscription: Some(subscription),
323        }
324    }
325
326    pub fn unsubscribe(subscription: Subscription) -> Self {
327        Self {
328            method: "unsubscribe",
329            subscription: Some(subscription),
330        }
331    }
332
333    pub fn ping() -> Self {
334        Self {
335            method: "ping",
336            subscription: None,
337        }
338    }
339}
340
341// ==================== Phase 1 New Message Types ====================
342
343/// Best bid/offer update
344#[derive(Debug, Clone, Deserialize)]
345pub struct Bbo {
346    pub data: BboData,
347}
348
349#[derive(Debug, Clone, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub struct BboData {
352    pub coin: String,
353    pub time: u64,
354    pub bbo: BboLevel,
355}
356
357#[derive(Debug, Clone, Deserialize)]
358pub struct BboLevel {
359    pub bid: PriceLevel,
360    pub ask: PriceLevel,
361}
362
363#[derive(Debug, Clone, Deserialize)]
364pub struct PriceLevel {
365    pub px: String,
366    pub sz: String,
367}
368
369/// Real-time open orders
370#[derive(Debug, Clone, Deserialize)]
371pub struct OpenOrdersWs {
372    pub data: OpenOrdersWsData,
373}
374
375#[derive(Debug, Clone, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct OpenOrdersWsData {
378    pub user: Address,
379    pub is_snapshot: Option<bool>,
380    pub orders: Vec<BasicOrder>,
381}
382
383/// Real-time clearinghouse state
384#[derive(Debug, Clone, Deserialize)]
385pub struct ClearinghouseStateWs {
386    pub data: ClearinghouseStateWsData,
387}
388
389#[derive(Debug, Clone, Deserialize)]
390#[serde(rename_all = "camelCase")]
391pub struct ClearinghouseStateWsData {
392    pub user: Address,
393    pub margin_summary: MarginSummaryWs,
394    pub cross_margin_summary: MarginSummaryWs,
395    pub withdrawable: String,
396    pub asset_positions: Vec<AssetPositionWs>,
397}
398
399#[derive(Debug, Clone, Deserialize)]
400#[serde(rename_all = "camelCase")]
401pub struct MarginSummaryWs {
402    pub account_value: String,
403    pub total_margin_used: String,
404    pub total_ntl_pos: String,
405    pub total_raw_usd: String,
406}
407
408#[derive(Debug, Clone, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct AssetPositionWs {
411    pub position: PositionWs,
412    #[serde(rename = "type")]
413    pub type_string: String,
414}
415
416#[derive(Debug, Clone, Deserialize)]
417#[serde(rename_all = "camelCase")]
418pub struct PositionWs {
419    pub coin: String,
420    pub entry_px: Option<String>,
421    pub liquidation_px: Option<String>,
422    pub margin_used: String,
423    pub position_value: String,
424    pub return_on_equity: String,
425    pub szi: String,
426    pub unrealized_pnl: String,
427}
428
429// ==================== Phase 2 New Message Types ====================
430
431/// WebData3 - Aggregate user information (newer version)
432#[derive(Debug, Clone, Deserialize)]
433pub struct WebData3Ws {
434    pub data: WebData3Data,
435}
436
437#[derive(Debug, Clone, Deserialize)]
438#[serde(rename_all = "camelCase")]
439pub struct WebData3Data {
440    pub user: Address,
441    #[serde(default)]
442    pub is_snapshot: Option<bool>,
443    #[serde(default)]
444    pub clearinghouse_state: Option<ClearinghouseStateWsData>,
445    #[serde(default)]
446    pub open_orders: Option<Vec<BasicOrder>>,
447    #[serde(default)]
448    pub fills: Option<Vec<TradeInfo>>,
449    #[serde(default)]
450    pub fundings: Option<Vec<UserFunding>>,
451}
452
453/// TWAP order states
454#[derive(Debug, Clone, Deserialize)]
455pub struct TwapStatesWs {
456    pub data: TwapStatesData,
457}
458
459#[derive(Debug, Clone, Deserialize)]
460#[serde(rename_all = "camelCase")]
461pub struct TwapStatesData {
462    pub user: Address,
463    #[serde(default)]
464    pub is_snapshot: Option<bool>,
465    pub twap_states: Vec<TwapState>,
466}
467
468#[derive(Debug, Clone, Deserialize)]
469#[serde(rename_all = "camelCase")]
470pub struct TwapState {
471    pub twap_id: u64,
472    pub coin: String,
473    pub side: String,
474    pub sz: String,
475    pub sz_filled: String,
476    pub duration_minutes: u32,
477    pub start_time: u64,
478    pub end_time: u64,
479    pub status: String,
480    #[serde(default)]
481    pub randomize: Option<bool>,
482}
483
484/// Active asset context
485#[derive(Debug, Clone, Deserialize)]
486pub struct ActiveAssetCtxWs {
487    pub data: ActiveAssetCtxData,
488}
489
490#[derive(Debug, Clone, Deserialize)]
491#[serde(rename_all = "camelCase")]
492pub struct ActiveAssetCtxData {
493    pub coin: String,
494    pub ctx: AssetCtx,
495}
496
497#[derive(Debug, Clone, Deserialize)]
498#[serde(rename_all = "camelCase")]
499pub struct AssetCtx {
500    pub funding: String,
501    pub open_interest: String,
502    pub prev_day_px: String,
503    pub day_ntl_vlm: String,
504    pub premium: Option<String>,
505    pub oracle_px: String,
506    pub mark_px: String,
507    pub mid_px: Option<String>,
508    pub impact_pxs: Option<Vec<String>>,
509}
510
511/// Active asset data (perps only)
512#[derive(Debug, Clone, Deserialize)]
513pub struct ActiveAssetDataWs {
514    pub data: ActiveAssetDataData,
515}
516
517#[derive(Debug, Clone, Deserialize)]
518#[serde(rename_all = "camelCase")]
519pub struct ActiveAssetDataData {
520    pub user: Address,
521    pub coin: String,
522    pub leverage: LeverageWs,
523    #[serde(default)]
524    pub max_trade_szs: Option<Vec<String>>,
525}
526
527#[derive(Debug, Clone, Deserialize)]
528#[serde(rename_all = "camelCase")]
529pub struct LeverageWs {
530    #[serde(rename = "type")]
531    pub leverage_type: String,
532    pub value: u32,
533    #[serde(default)]
534    pub raw_usd: Option<String>,
535}
536
537/// User TWAP slice fills
538#[derive(Debug, Clone, Deserialize)]
539pub struct UserTwapSliceFillsWs {
540    pub data: UserTwapSliceFillsData,
541}
542
543#[derive(Debug, Clone, Deserialize)]
544#[serde(rename_all = "camelCase")]
545pub struct UserTwapSliceFillsData {
546    pub user: Address,
547    #[serde(default)]
548    pub is_snapshot: Option<bool>,
549    pub twap_slice_fills: Vec<TwapSliceFill>,
550}
551
552#[derive(Debug, Clone, Deserialize)]
553#[serde(rename_all = "camelCase")]
554pub struct TwapSliceFill {
555    pub twap_id: u64,
556    pub coin: String,
557    pub side: String,
558    pub px: String,
559    pub sz: String,
560    pub time: u64,
561    pub fee: String,
562    pub oid: u64,
563    pub hash: String,
564}
565
566/// User TWAP history
567#[derive(Debug, Clone, Deserialize)]
568pub struct UserTwapHistoryWs {
569    pub data: UserTwapHistoryData,
570}
571
572#[derive(Debug, Clone, Deserialize)]
573#[serde(rename_all = "camelCase")]
574pub struct UserTwapHistoryData {
575    pub user: Address,
576    #[serde(default)]
577    pub is_snapshot: Option<bool>,
578    pub twap_history: Vec<TwapHistoryEntry>,
579}
580
581#[derive(Debug, Clone, Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct TwapHistoryEntry {
584    pub twap_id: u64,
585    pub coin: String,
586    pub side: String,
587    pub sz: String,
588    pub sz_filled: String,
589    pub avg_px: Option<String>,
590    pub duration_minutes: u32,
591    pub start_time: u64,
592    pub end_time: u64,
593    pub status: String,
594    #[serde(default)]
595    pub randomize: Option<bool>,
596}