Skip to main content

nautilus_hyperliquid/websocket/
enums.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use serde::{Deserialize, Serialize};
17use strum::{AsRefStr, Display, EnumIter, EnumString};
18
19/// WebSocket channel names for Hyperliquid.
20#[derive(
21    Clone,
22    Debug,
23    Display,
24    PartialEq,
25    Eq,
26    Hash,
27    AsRefStr,
28    EnumIter,
29    EnumString,
30    Serialize,
31    Deserialize,
32)]
33pub enum HyperliquidWsChannel {
34    #[serde(rename = "subscriptionResponse")]
35    SubscriptionResponse,
36    #[serde(rename = "trades")]
37    Trades,
38    #[serde(rename = "l2Book")]
39    L2Book,
40    #[serde(rename = "bbo")]
41    Bbo,
42    #[serde(rename = "candle")]
43    Candle,
44    #[serde(rename = "allMids")]
45    AllMids,
46    #[serde(rename = "notification")]
47    Notification,
48    #[serde(rename = "orderUpdates")]
49    OrderUpdates,
50    #[serde(rename = "userEvents")]
51    UserEvents,
52    #[serde(rename = "userFills")]
53    UserFills,
54    #[serde(rename = "userFundings")]
55    UserFundings,
56    #[serde(rename = "userNonFundingLedgerUpdates")]
57    UserNonFundingLedgerUpdates,
58    /// Generic user channel - Hyperliquid sends fills/events on this channel.
59    #[serde(rename = "user")]
60    User,
61    #[serde(rename = "post")]
62    Post,
63    #[serde(rename = "pong")]
64    Pong,
65    #[serde(rename = "error")]
66    Error,
67}
68
69impl HyperliquidWsChannel {
70    /// Returns the string representation of the channel.
71    pub fn as_str(&self) -> &'static str {
72        match self {
73            Self::SubscriptionResponse => "subscriptionResponse",
74            Self::Trades => "trades",
75            Self::L2Book => "l2Book",
76            Self::Bbo => "bbo",
77            Self::Candle => "candle",
78            Self::AllMids => "allMids",
79            Self::Notification => "notification",
80            Self::OrderUpdates => "orderUpdates",
81            Self::UserEvents => "userEvents",
82            Self::UserFills => "userFills",
83            Self::UserFundings => "userFundings",
84            Self::UserNonFundingLedgerUpdates => "userNonFundingLedgerUpdates",
85            Self::User => "user",
86            Self::Post => "post",
87            Self::Pong => "pong",
88            Self::Error => "error",
89        }
90    }
91
92    /// Returns true if this is a public channel (does not require authentication).
93    pub fn is_public(&self) -> bool {
94        matches!(
95            self,
96            Self::SubscriptionResponse
97                | Self::Trades
98                | Self::L2Book
99                | Self::Bbo
100                | Self::Candle
101                | Self::AllMids
102                | Self::Notification
103                | Self::Pong
104                | Self::Error
105        )
106    }
107
108    /// Returns true if this is a private channel (requires authentication).
109    pub fn is_private(&self) -> bool {
110        !self.is_public()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use rstest::rstest;
117    use serde_json;
118
119    use super::*;
120
121    #[rstest]
122    #[case(HyperliquidWsChannel::Trades, r#""trades""#)]
123    #[case(HyperliquidWsChannel::L2Book, r#""l2Book""#)]
124    #[case(HyperliquidWsChannel::UserFills, r#""userFills""#)]
125    #[case(HyperliquidWsChannel::Bbo, r#""bbo""#)]
126    #[case(
127        HyperliquidWsChannel::SubscriptionResponse,
128        r#""subscriptionResponse""#
129    )]
130    fn test_channel_serialization(#[case] channel: HyperliquidWsChannel, #[case] expected: &str) {
131        assert_eq!(serde_json::to_string(&channel).unwrap(), expected);
132    }
133
134    #[rstest]
135    #[case(r#""trades""#, HyperliquidWsChannel::Trades)]
136    #[case(r#""l2Book""#, HyperliquidWsChannel::L2Book)]
137    #[case(r#""userEvents""#, HyperliquidWsChannel::UserEvents)]
138    #[case(r#""bbo""#, HyperliquidWsChannel::Bbo)]
139    #[case(r#""pong""#, HyperliquidWsChannel::Pong)]
140    fn test_channel_deserialization(#[case] json: &str, #[case] expected: HyperliquidWsChannel) {
141        assert_eq!(
142            serde_json::from_str::<HyperliquidWsChannel>(json).unwrap(),
143            expected
144        );
145    }
146
147    #[rstest]
148    #[case(HyperliquidWsChannel::Trades, "trades")]
149    #[case(HyperliquidWsChannel::L2Book, "l2Book")]
150    #[case(HyperliquidWsChannel::UserFills, "userFills")]
151    #[case(
152        HyperliquidWsChannel::UserNonFundingLedgerUpdates,
153        "userNonFundingLedgerUpdates"
154    )]
155    #[case(HyperliquidWsChannel::Bbo, "bbo")]
156    fn test_as_str_method(#[case] channel: HyperliquidWsChannel, #[case] expected: &str) {
157        assert_eq!(channel.as_str(), expected);
158    }
159
160    #[rstest]
161    fn test_display_trait() {
162        assert_eq!(format!("{}", HyperliquidWsChannel::Trades), "Trades");
163        assert_eq!(format!("{}", HyperliquidWsChannel::L2Book), "L2Book");
164        assert_eq!(format!("{}", HyperliquidWsChannel::UserFills), "UserFills");
165    }
166
167    #[rstest]
168    fn test_is_public_channel() {
169        assert!(HyperliquidWsChannel::Trades.is_public());
170        assert!(HyperliquidWsChannel::L2Book.is_public());
171        assert!(HyperliquidWsChannel::Bbo.is_public());
172        assert!(HyperliquidWsChannel::SubscriptionResponse.is_public());
173        assert!(HyperliquidWsChannel::Pong.is_public());
174
175        assert!(!HyperliquidWsChannel::OrderUpdates.is_public());
176        assert!(!HyperliquidWsChannel::UserEvents.is_public());
177        assert!(!HyperliquidWsChannel::UserFills.is_public());
178        assert!(!HyperliquidWsChannel::UserFundings.is_public());
179        assert!(!HyperliquidWsChannel::UserNonFundingLedgerUpdates.is_public());
180        assert!(!HyperliquidWsChannel::Post.is_public());
181    }
182
183    #[rstest]
184    fn test_is_private_channel() {
185        assert!(!HyperliquidWsChannel::Trades.is_private());
186        assert!(!HyperliquidWsChannel::L2Book.is_private());
187        assert!(!HyperliquidWsChannel::Bbo.is_private());
188
189        assert!(HyperliquidWsChannel::OrderUpdates.is_private());
190        assert!(HyperliquidWsChannel::UserEvents.is_private());
191        assert!(HyperliquidWsChannel::UserFills.is_private());
192        assert!(HyperliquidWsChannel::UserFundings.is_private());
193        assert!(HyperliquidWsChannel::UserNonFundingLedgerUpdates.is_private());
194        assert!(HyperliquidWsChannel::Post.is_private());
195    }
196
197    #[rstest]
198    fn test_enum_iter() {
199        use strum::IntoEnumIterator;
200
201        let channels: Vec<HyperliquidWsChannel> = HyperliquidWsChannel::iter().collect();
202        assert_eq!(channels.len(), 16);
203        assert!(channels.contains(&HyperliquidWsChannel::Trades));
204        assert!(channels.contains(&HyperliquidWsChannel::L2Book));
205        assert!(channels.contains(&HyperliquidWsChannel::UserFills));
206        assert!(channels.contains(&HyperliquidWsChannel::Candle));
207        assert!(channels.contains(&HyperliquidWsChannel::AllMids));
208        assert!(channels.contains(&HyperliquidWsChannel::Notification));
209    }
210
211    #[rstest]
212    fn test_from_str() {
213        use std::str::FromStr;
214
215        assert_eq!(
216            HyperliquidWsChannel::from_str("Trades").unwrap(),
217            HyperliquidWsChannel::Trades
218        );
219        assert_eq!(
220            HyperliquidWsChannel::from_str("L2Book").unwrap(),
221            HyperliquidWsChannel::L2Book
222        );
223        assert_eq!(
224            HyperliquidWsChannel::from_str("UserFills").unwrap(),
225            HyperliquidWsChannel::UserFills
226        );
227
228        assert!(HyperliquidWsChannel::from_str("InvalidChannel").is_err());
229    }
230}