hyperliquid 0.7.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67b19ec4599a62be35288947fdc8ce2a98c6a0f012f4bdb055a58bd9e4a37849
4
- data.tar.gz: 58470f99ebf59a1ba95936816c887210d5937240c55ea1b6e85f7d68b0d82a37
3
+ metadata.gz: 1e1cb7c8ae1c1b83ab34eaba6f6bf9ce34b620ca1af4500912d62e54d75a95ab
4
+ data.tar.gz: 9d026ad803f2fe0d2983c3dd242ee5d901cf56677e7c32cf756b5da38eb39c68
5
5
  SHA512:
6
- metadata.gz: fe341138c6085166b2ddd6e87fd7458554e178c4361e9ad16ed7ab1d239333c821dc0556d7f86d9080ec2002c3c36f26b3f32282acc2b6ccc010b85f83c83abe
7
- data.tar.gz: caaefe028d2a4727c01daeea7241290af00c8676d73c2371ce360279f4dc0c18337c1daa398ab968416ee2516f02d08882b0556d91b5e5e78d318bfbd2d7531f
6
+ metadata.gz: 99a8f5cf7847c9236f3b4b48cfb83734ecb6ae8b7679d8d5cc1aef003b1a510df3edefdf9f6481d7bdb4ff7d22546549247b9aa17771e95d58307eb93bdd0448
7
+ data.tar.gz: d649b243e0aab4ae25ffcd3c452878cce1be94dd86dde1aeb151b9478bba1c42fe534ec485f4ddd896e0f358b705d4b063180b26e23fcf7d09d42a9f71ffbc64
data/.rubocop.yml CHANGED
@@ -5,6 +5,7 @@ AllCops:
5
5
  Exclude:
6
6
  - 'test_*.rb' # Exclude ad-hoc integration test scripts
7
7
  - 'scripts/**/*' # Exclude integration test scripts
8
+ - 'local/**/*' # Exclude local test scripts
8
9
  - 'vendor/**/*' # Exclude vendored gems (CI bundles here)
9
10
 
10
11
  # Allow longer methods for complex logic
@@ -30,10 +31,11 @@ Metrics/ParameterLists:
30
31
  Exclude:
31
32
  - 'lib/hyperliquid/exchange.rb'
32
33
 
33
- # Allow higher complexity for order type conversion logic
34
+ # Allow higher complexity for order type conversion logic and WS message handling
34
35
  Metrics/CyclomaticComplexity:
35
36
  Exclude:
36
37
  - 'lib/hyperliquid/exchange.rb'
38
+ - 'lib/hyperliquid/ws/client.rb'
37
39
 
38
40
  Metrics/PerceivedComplexity:
39
41
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  ## [Ruby Hyperliquid SDK Changelog]
2
2
 
3
+ ## [1.0.1] - 2026-02-03
4
+
5
+ - Minor refactors to reduce method complexity
6
+
7
+ ## [1.0.0] - 2026-02-03
8
+
9
+ ### WebSocket Support
10
+
11
+ - Add real-time WebSocket client (`Hyperliquid::WS::Client`) with managed connection
12
+ - Three-thread architecture: read thread, dispatch thread, ping thread
13
+ - Automatic reconnection with exponential backoff (1s, 2s, 4s, ..., 30s cap)
14
+ - Heartbeat ping every 50 seconds to keep connection alive
15
+ - Bounded message queue (1024) with overflow detection
16
+ - Lifecycle callbacks: `on(:open)`, `on(:close)`, `on(:error)`
17
+
18
+ - Add 9 WebSocket subscription channels
19
+ - `allMids` — mid prices for all coins
20
+ - `l2Book` — level 2 order book updates
21
+ - `trades` — trade feed for a coin
22
+ - `bbo` — best bid/offer for a coin
23
+ - `candle` — candlestick updates
24
+ - `orderUpdates` — order status changes for a user
25
+ - `userEvents` — all events for a user (fills, liquidations, etc.)
26
+ - `userFills` — fill updates for a user
27
+ - `userFundings` — funding payments for a user
28
+
29
+ ### HIP-3 Support
30
+
31
+ - Add HIP-3 DEX abstraction Exchange actions
32
+ - `user_dex_abstraction` — enable/disable DEX abstraction for automatic collateral transfers (user-signed)
33
+ - `agent_enable_dex_abstraction` — enable DEX abstraction via agent (L1 action, enable only)
34
+ - Add full HIP-3 trading support
35
+ - Lazy loading of HIP-3 dex asset metadata when trading prefixed coins (e.g., `xyz:GOLD`)
36
+ - Correct HIP-3 asset ID calculation: `100000 + perp_dex_index * 10000 + index_in_meta`
37
+ - `market_order` and `market_close` automatically use dex-specific price endpoints
38
+ - Add `dex:` parameter to `all_mids` Info endpoint for HIP-3 perp dex prices
39
+
40
+ ### Info API
41
+
42
+ - Add 3 more Info endpoints
43
+ - `extra_agents` — get authorized agent addresses for a user
44
+ - `user_to_multi_sig_signers` — get multi-sig signer mappings for a user
45
+ - `user_dex_abstraction` — get dex abstraction config for a user
46
+
3
47
  ## [0.7.0] - 2026-01-30
4
48
 
5
49
  - Add agent, builder & delegation actions to Exchange API
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  A Ruby SDK for interacting with the Hyperliquid decentralized exchange API.
8
8
 
9
- The SDK supports both read operations (Info API) and authenticated write operations (Exchange API) for trading.
9
+ Full-featured SDK with Info API (market data), Exchange API (trading), real-time WebSocket streaming, and HIP-3 builder-deployed perpetuals support.
10
10
 
11
11
  ## Installation
12
12
 
@@ -54,6 +54,7 @@ exchange = trading_sdk.exchange
54
54
 
55
55
  - [API Reference](docs/API.md) - Complete list of available methods
56
56
  - [Examples](docs/EXAMPLES.md) - Code examples for Info and Exchange APIs
57
+ - [Web Sockets](docs/WS.md) - Web Sockets implementation
57
58
  - [Configuration](docs/CONFIGURATION.md) - SDK configuration options
58
59
  - [Error Handling](docs/ERRORS.md) - Error types and handling
59
60
  - [Development](docs/DEVELOPMENT.md) - Contributing and running tests
data/docs/API.md CHANGED
@@ -6,8 +6,8 @@ Read-only methods for querying market data and user information.
6
6
 
7
7
  ### General Info
8
8
 
9
- - `all_mids()` - Retrieve mids for all coins
10
- - `open_orders(user)` - Retrieve a user's open orders
9
+ - `all_mids(dex: nil)` - Retrieve mids for all coins (optional dex for HIP-3 perp dexs; spot mids only included with default dex)
10
+ - `open_orders(user, dex: nil)` - Retrieve a user's open orders (optional dex for HIP-3)
11
11
  - `frontend_open_orders(user, dex: nil)` - Retrieve a user's open orders with additional frontend info
12
12
  - `user_fills(user)` - Retrieve a user's fills
13
13
  - `user_fills_by_time(user, start_time, end_time = nil)` - Retrieve a user's fills by time (optional end time)
@@ -30,6 +30,9 @@ Read-only methods for querying market data and user information.
30
30
  - `delegator_summary(user)` - Query a user's staking summary
31
31
  - `delegator_history(user)` - Query a user's staking history
32
32
  - `delegator_rewards(user)` - Query a user's staking rewards
33
+ - `extra_agents(user)` - Get authorized agent addresses for a user
34
+ - `user_to_multi_sig_signers(user)` - Get multi-sig signer mappings for a user
35
+ - `user_dex_abstraction(user)` - Get dex abstraction config for a user
33
36
 
34
37
  ### Perpetuals Methods
35
38
 
@@ -103,6 +106,13 @@ Read-only methods for querying market data and user information.
103
106
  - `approve_builder_fee(builder:, max_fee_rate:)` - Approve a builder fee rate for a builder address
104
107
  - `token_delegate(validator:, wei:, is_undelegate:)` - Delegate or undelegate HYPE tokens to a validator
105
108
 
109
+ ### HIP-3 DEX Abstraction
110
+
111
+ HIP-3 DEX abstraction allows automatic collateral transfers when trading on builder-deployed perpetual DEXs.
112
+
113
+ - `user_dex_abstraction(enabled:, user: nil)` - Enable or disable DEX abstraction for an account (user-signed)
114
+ - `agent_enable_dex_abstraction(vault_address: nil)` - Enable DEX abstraction via agent (L1 action, enable only)
115
+
106
116
  ### Other
107
117
 
108
118
  - `address` - Get the wallet address associated with the private key
@@ -134,3 +144,51 @@ Factory methods:
134
144
  - `Hyperliquid::Cloid.from_str(s)` - Create from hex string
135
145
  - `Hyperliquid::Cloid.from_uuid(uuid)` - Create from UUID
136
146
  - `Hyperliquid::Cloid.random` - Generate random
147
+
148
+ ## WebSocket
149
+
150
+ Real-time data streaming via WebSocket. No private key required.
151
+
152
+ ### Connection
153
+
154
+ - `ws.connect` - Connect to the WebSocket server (also called automatically on first `subscribe`)
155
+ - `ws.connected?` - Check if the WebSocket is connected
156
+ - `ws.close` - Disconnect and stop all background threads
157
+
158
+ ### Subscriptions
159
+
160
+ - `ws.subscribe(subscription, &callback)` - Subscribe to a channel. Returns a subscription ID.
161
+ - `ws.unsubscribe(id)` - Unsubscribe by subscription ID. Sends unsubscribe to server when the last callback for a channel is removed.
162
+
163
+ ### Lifecycle Events
164
+
165
+ - `ws.on(:open, &callback)` - Called when connection is established
166
+ - `ws.on(:close, &callback)` - Called when connection is closed
167
+ - `ws.on(:error, &callback)` - Called on connection error
168
+
169
+ ### Monitoring
170
+
171
+ - `ws.dropped_message_count` - Number of messages dropped due to a full internal queue (callbacks too slow)
172
+
173
+ ### Available Channels
174
+
175
+ | Channel | Subscription | Description |
176
+ |---------|-------------|-------------|
177
+ | `allMids` | `{ type: 'allMids' }` | Mid prices for all coins |
178
+ | `l2Book` | `{ type: 'l2Book', coin: 'ETH' }` | Level 2 order book updates |
179
+ | `trades` | `{ type: 'trades', coin: 'ETH' }` | Trade feed for a coin |
180
+ | `bbo` | `{ type: 'bbo', coin: 'ETH' }` | Best bid/offer for a coin |
181
+ | `candle` | `{ type: 'candle', coin: 'ETH', interval: '1m' }` | Candlestick updates |
182
+ | `orderUpdates` | `{ type: 'orderUpdates', user: '0x...' }` | Order status changes for a user |
183
+ | `userEvents` | `{ type: 'userEvents', user: '0x...' }` | All events for a user (fills, liquidations, etc.) |
184
+ | `userFills` | `{ type: 'userFills', user: '0x...' }` | Fill updates for a user |
185
+ | `userFundings` | `{ type: 'userFundings', user: '0x...' }` | Funding payments for a user |
186
+
187
+ Candle intervals: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
188
+
189
+ ### Configuration
190
+
191
+ `Hyperliquid::WS::Client.new` accepts:
192
+ - `testnet:` (Boolean, default: false) - Use testnet WebSocket endpoint
193
+ - `max_queue_size:` (Integer, default: 1024) - Max messages buffered before dropping
194
+ - `reconnect:` (Boolean, default: true) - Auto-reconnect on unexpected disconnect
data/docs/EXAMPLES.md CHANGED
@@ -9,12 +9,20 @@
9
9
  mids = sdk.info.all_mids
10
10
  # => { "BTC" => "50000", "ETH" => "3000", ... }
11
11
 
12
+ # Retrieve mids for a HIP-3 perp dex (e.g., xyz)
13
+ hip3_mids = sdk.info.all_mids(dex: 'xyz')
14
+ # => { "xyz:GOLD" => "2500", "xyz:SILVER" => "30", ... }
15
+
12
16
  user_address = "0x..."
13
17
 
14
18
  # Retrieve a user's open orders
15
19
  orders = sdk.info.open_orders(user_address)
16
20
  # => [{ "coin" => "BTC", "sz" => "0.1", "px" => "50000", "side" => "A" }]
17
21
 
22
+ # Retrieve a user's open orders on a HIP-3 dex
23
+ hip3_orders = sdk.info.open_orders(user_address, dex: 'xyz')
24
+ # => [{ "coin" => "xyz:GOLD", "sz" => "1.0", ... }]
25
+
18
26
  # Retrieve a user's open orders with additional frontend info
19
27
  frontend_orders = sdk.info.frontend_open_orders(user_address)
20
28
  # => [{ "coin" => "BTC", "isTrigger" => false, ... }]
@@ -114,6 +122,18 @@ history = sdk.info.delegator_history(user_address)
114
122
  # Query a user's staking rewards
115
123
  rewards = sdk.info.delegator_rewards(user_address)
116
124
  # => [{ "time" => 1_736_726_400_073, "source" => "delegation", "totalAmount" => "0.123" }, ...]
125
+
126
+ # Get authorized agent addresses for a user
127
+ agents = sdk.info.extra_agents(user_address)
128
+ # => [{ "address" => "0x...", "name" => "agent1" }, ...]
129
+
130
+ # Get multi-sig signer mappings for a user
131
+ signers = sdk.info.user_to_multi_sig_signers(user_address)
132
+ # => { "signers" => ["0x...", "0x..."], "threshold" => 2 }
133
+
134
+ # Get dex abstraction config for a user
135
+ dex_abstraction = sdk.info.user_dex_abstraction(user_address)
136
+ # => { "enabled" => true }
117
137
  ```
118
138
 
119
139
  **Note:** `l2_book` and `candles_snapshot` work for both Perpetuals and Spot. For spot, use `"{BASE}/USDC"` when available (e.g., `"PURR/USDC"`). Otherwise, use the index alias `"@{index}"` from `spot_meta["universe"]`.
@@ -535,6 +555,177 @@ sdk.exchange.token_delegate(
535
555
  )
536
556
  ```
537
557
 
558
+ ### HIP-3 DEX Abstraction
559
+
560
+ HIP-3 DEX abstraction allows automatic collateral transfers when trading on builder-deployed perpetual DEXs.
561
+
562
+ ```ruby
563
+ # Enable DEX abstraction for your account (user-signed action)
564
+ sdk.exchange.user_dex_abstraction(enabled: true)
565
+
566
+ # Disable DEX abstraction
567
+ sdk.exchange.user_dex_abstraction(enabled: false)
568
+
569
+ # Enable for a specific user address
570
+ sdk.exchange.user_dex_abstraction(enabled: true, user: '0x...')
571
+
572
+ # Enable DEX abstraction via agent (L1 action, enable only)
573
+ # Use this when trading as an agent on behalf of another account
574
+ sdk.exchange.agent_enable_dex_abstraction
575
+
576
+ # Enable DEX abstraction for a vault via agent
577
+ sdk.exchange.agent_enable_dex_abstraction(vault_address: '0x...')
578
+
579
+ # Check current DEX abstraction status
580
+ status = sdk.info.user_dex_abstraction(sdk.exchange.address)
581
+ # => { "enabled" => true }
582
+ ```
583
+
584
+ ## WebSocket
585
+
586
+ ### l2Book (Order Book)
587
+
588
+ ```ruby
589
+ sdk = Hyperliquid.new(testnet: true)
590
+
591
+ sdk.ws.on(:open) { puts 'Connected!' }
592
+
593
+ sub_id = sdk.ws.subscribe({ type: 'l2Book', coin: 'ETH' }) do |data|
594
+ levels = data['levels']
595
+ best_bid = levels[0]&.first
596
+ best_ask = levels[1]&.first
597
+ puts "ETH bid=#{best_bid['px']} ask=#{best_ask['px']}"
598
+ end
599
+
600
+ sleep 10
601
+ sdk.ws.unsubscribe(sub_id)
602
+ sdk.ws.close
603
+ ```
604
+
605
+ ### allMids (Mid Prices)
606
+
607
+ ```ruby
608
+ sdk = Hyperliquid.new(testnet: true)
609
+
610
+ sdk.ws.subscribe({ type: 'allMids' }) do |data|
611
+ puts "BTC mid: #{data['mids']['BTC']}"
612
+ puts "ETH mid: #{data['mids']['ETH']}"
613
+ end
614
+
615
+ sleep 10
616
+ sdk.ws.close
617
+ ```
618
+
619
+ ### trades
620
+
621
+ ```ruby
622
+ sdk = Hyperliquid.new(testnet: true)
623
+
624
+ sdk.ws.subscribe({ type: 'trades', coin: 'ETH' }) do |trades|
625
+ trades.each do |t|
626
+ side = t['side'] == 'B' ? 'BUY' : 'SELL'
627
+ puts "#{side} #{t['sz']} ETH @ #{t['px']}"
628
+ end
629
+ end
630
+
631
+ sleep 30
632
+ sdk.ws.close
633
+ ```
634
+
635
+ ### bbo (Best Bid/Offer)
636
+
637
+ ```ruby
638
+ sdk = Hyperliquid.new(testnet: true)
639
+
640
+ sdk.ws.subscribe({ type: 'bbo', coin: 'BTC' }) do |data|
641
+ puts "BTC bid=#{data['bid']} ask=#{data['ask']}"
642
+ end
643
+
644
+ sleep 10
645
+ sdk.ws.close
646
+ ```
647
+
648
+ ### candle (Candlesticks)
649
+
650
+ ```ruby
651
+ sdk = Hyperliquid.new(testnet: true)
652
+
653
+ sdk.ws.subscribe({ type: 'candle', coin: 'ETH', interval: '1m' }) do |data|
654
+ puts "ETH 1m candle o=#{data['o']} h=#{data['h']} l=#{data['l']} c=#{data['c']}"
655
+ end
656
+
657
+ sleep 120
658
+ sdk.ws.close
659
+ ```
660
+
661
+ ### User Channels
662
+
663
+ ```ruby
664
+ sdk = Hyperliquid.new(testnet: true, private_key: ENV['HYPERLIQUID_PRIVATE_KEY'])
665
+ user = sdk.exchange.address
666
+
667
+ # Order status changes
668
+ sdk.ws.subscribe({ type: 'orderUpdates', user: user }) do |updates|
669
+ updates.each { |u| puts "Order #{u['order']['oid']}: #{u['status']}" }
670
+ end
671
+
672
+ # Fill notifications
673
+ sdk.ws.subscribe({ type: 'userFills', user: user }) do |data|
674
+ data['fills'].each { |f| puts "Fill: #{f['sz']} #{f['coin']} @ #{f['px']}" }
675
+ end
676
+
677
+ # Funding payments
678
+ sdk.ws.subscribe({ type: 'userFundings', user: user }) do |data|
679
+ puts "Funding update for #{data['user']}"
680
+ end
681
+
682
+ # All user events (fills, liquidations, etc.)
683
+ sdk.ws.subscribe({ type: 'userEvents', user: user }) do |data|
684
+ puts "User event received"
685
+ end
686
+
687
+ sleep 60
688
+ sdk.ws.close
689
+ ```
690
+
691
+ ### Multiple Subscriptions
692
+
693
+ ```ruby
694
+ sdk = Hyperliquid.new(testnet: true)
695
+
696
+ sdk.ws.subscribe({ type: 'l2Book', coin: 'ETH' }) do |data|
697
+ puts "ETH book: #{data['levels'][0]&.first&.dig('px')}"
698
+ end
699
+
700
+ sdk.ws.subscribe({ type: 'l2Book', coin: 'BTC' }) do |data|
701
+ puts "BTC book: #{data['levels'][0]&.first&.dig('px')}"
702
+ end
703
+
704
+ sdk.ws.subscribe({ type: 'trades', coin: 'ETH' }) do |trades|
705
+ puts "ETH trade: #{trades.first['px']}" if trades.any?
706
+ end
707
+
708
+ sleep 10
709
+ sdk.ws.close
710
+ ```
711
+
712
+ ### Handling Reconnection
713
+
714
+ ```ruby
715
+ sdk = Hyperliquid.new(testnet: true)
716
+
717
+ sdk.ws.on(:open) { puts 'Connected (or reconnected)!' }
718
+ sdk.ws.on(:close) { puts 'Connection lost. Reconnecting...' }
719
+
720
+ # Subscriptions are automatically replayed on reconnect
721
+ sdk.ws.subscribe({ type: 'l2Book', coin: 'ETH' }) do |data|
722
+ puts "ETH: #{data['levels'][0]&.first&.dig('px')}"
723
+ end
724
+
725
+ sleep 300
726
+ sdk.ws.close
727
+ ```
728
+
538
729
  ### Client Order IDs (Cloid)
539
730
 
540
731
  ```ruby
data/docs/WS.md ADDED
@@ -0,0 +1,49 @@
1
+ # WebSocket Implementation
2
+
3
+ ## Architecture
4
+
5
+ `Hyperliquid::WS::Client` is a managed WebSocket client backed by three background threads:
6
+
7
+ ```
8
+ WS Read Thread ──> Bounded Queue (1024) ──> Dispatch Thread ──> User Callbacks
9
+ Ping Thread (every 50s)
10
+ ```
11
+
12
+ - **Read thread** (`ws_lite`): receives frames, parses JSON, pushes onto the queue. Never blocks on user code.
13
+ - **Dispatch thread** (`hl-ws-dispatch`): pops messages from the queue and invokes matching callbacks in order. If a callback is slow, only this thread blocks.
14
+ - **Ping thread** (`hl-ws-ping`): sends `{"method":"ping"}` every 50 seconds to keep the connection alive.
15
+
16
+ ## Message Flow
17
+
18
+ 1. Raw frame arrives on the read thread.
19
+ 2. Non-JSON messages (e.g. `"Websocket connection established."`) and `pong` responses are discarded.
20
+ 3. A channel identifier is computed from the message (e.g. `l2Book:eth`).
21
+ 4. The message is pushed onto the bounded `Queue`. If the queue is full, the message is dropped and a warning is emitted.
22
+ 5. The dispatch thread pops the message, looks up callbacks by identifier, and calls each one.
23
+
24
+ ## Subscription Routing
25
+
26
+ Subscriptions are keyed by an identifier string derived from the channel type and its parameters:
27
+
28
+ | Channel | Identifier format | Example |
29
+ |----------------|------------------------------------|---------------------|
30
+ | `allMids` | `allMids` | `allMids` |
31
+ | `l2Book` | `l2Book:<coin>` | `l2Book:eth` |
32
+ | `candle` | `candle:<coin>:<interval>` | `candle:eth:1h` |
33
+ | `userEvents` | `userEvents:<user>` | `userEvents:0xabc` |
34
+
35
+ Multiple callbacks can be registered for the same identifier. The server unsubscribe message is only sent when the last callback for an identifier is removed.
36
+
37
+ ## Queue Overflow
38
+
39
+ The internal queue is bounded (default 1024 messages). When full, new messages are dropped (oldest retained). Warnings print on the 1st drop and every 100th drop. Monitor via `dropped_message_count`.
40
+
41
+ ## Reconnection
42
+
43
+ On unexpected disconnect (when `reconnect: true`, the default), the client spawns a thread that retries with exponential backoff: 1s, 2s, 4s, ..., capped at 30s. On reconnect, all active subscriptions are replayed automatically.
44
+
45
+ ## Thread Safety
46
+
47
+ - `@subscriptions` and `@pending_subscriptions` are protected by a `Mutex` as they are accessed across the three threads.
48
+ - Ruby's `Queue` is inherently thread-safe.
49
+ - Callbacks are invoked serially on the dispatch thread (never concurrently).
@@ -11,6 +11,11 @@ module Hyperliquid
11
11
  INFO_ENDPOINT = '/info'
12
12
  EXCHANGE_ENDPOINT = '/exchange'
13
13
 
14
+ # WebSocket
15
+ WS_ENDPOINT = '/ws'
16
+ WS_PING_INTERVAL = 50 # seconds between pings
17
+ WS_MAX_QUEUE_SIZE = 1024 # max queued messages before dropping
18
+
14
19
  # Request timeouts (seconds)
15
20
  DEFAULT_TIMEOUT = 30
16
21
  DEFAULT_READ_TIMEOUT = 30
@@ -35,4 +35,7 @@ module Hyperliquid
35
35
 
36
36
  # Error for network connectivity issues
37
37
  class NetworkError < Error; end
38
+
39
+ # Error for WebSocket issues
40
+ class WebSocketError < Error; end
38
41
  end