Skip to content

Commit dd99a9d

Browse files
authored
feat(hyperliquid): implement WebSocket functionality for HyperLiquid … (#38)
* feat(hyperliquid): implement WebSocket functionality for HyperLiquid exchange - Add async-stream and derive_more dependencies for WebSocket operations - Implement HyperLiquid WebSocket client with subscription management - Add connection lifecycle methods (connect, disconnect, ping loop) - Create subscription methods for all supported channels (tickers, order books, trades) - Add authentication support for private WebSocket streams - Implement WsExchange trait with full stream support for all data types - Add donation section to README for project support - Include comprehensive WebSocket integration tests - Add debug derive and skip event callback for WsClient struct * refactor(hyperliquid): update WebSocket implementation and clippy configurations - Remove unused ping interval configuration and related TODO comments - Convert build_subscription_message from instance method to static method - Add clippy allow attributes for disallowed_methods and manual_unwrap_or_default - Simplify return value handling by removing unnecessary Ok() wrapper - Update string conversion to use ToString::to_string instead of closure - Fix WebSocket client access pattern after removing instance method - Optimize ws_clone usage in create_ws function
1 parent d6c8c36 commit dd99a9d

9 files changed

Lines changed: 726 additions & 255 deletions

File tree

‎README.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,16 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
512512
- **Discussions**: [GitHub Discussions](https://github.com/Praying/ccxt-rust/discussions)
513513
- **Documentation**: [Project Docs](docs/)
514514

515+
## ❤️ Donations
516+
517+
If the project is profitable, consider donating as showing gratitude for its development:
518+
519+
**USDT or USDC Binance Smart Chain BEP20**:
520+
521+
`0x8e5d858f92938b028065d39450421d0e080d15f7`
522+
515523
---
516524

517525
**Status**: 🚧 Active Development | **Version**: 0.1.2 | **Updated**: 2026-01
518526

519-
⚠️ **Note**: This library is under active development. APIs may change before v1.0. Not recommended for production use yet.
527+
⚠️ **Note**: This library is under active development. APIs may change before v1.0. Not recommended for production use yet.

‎ccxt-core/Cargo.toml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ zeroize = { workspace = true }
7171
# Test configuration (optional, enabled by test-utils feature)
7272
dotenvy = { workspace = true, optional = true }
7373
envy = { workspace = true, optional = true }
74+
async-stream = "0.3.6"
75+
derive_more = { version = "2.1.1", features = ["debug"] }
7476

7577
[dev-dependencies]
7678
tokio-test = { workspace = true }
@@ -112,4 +114,4 @@ backtrace = []
112114

113115
[[bench]]
114116
name = "timestamp_benchmark"
115-
harness = false
117+
harness = false

‎ccxt-core/src/ws_client/mod.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub use state::{WsConnectionState, WsStats, WsStatsSnapshot};
2424
pub use subscription::{Subscription, SubscriptionManager};
2525

2626
use crate::error::{Error, Result};
27+
use derive_more::Debug;
2728
use futures_util::{SinkExt, StreamExt, stream::SplitSink};
2829
use serde_json::Value;
2930
use std::collections::HashMap;
@@ -53,6 +54,7 @@ type WsWriter = SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>;
5354
/// - `DropOldest`: Removes the oldest message to make room (default)
5455
/// - `DropNewest`: Discards the incoming message
5556
/// - `Block`: Waits until space is available (may stall the read loop)
57+
#[derive(Debug)]
5658
pub struct WsClient {
5759
config: WsConfig,
5860
state: Arc<AtomicU8>,
@@ -64,6 +66,7 @@ pub struct WsClient {
6466
shutdown_tx: Arc<Mutex<Option<mpsc::UnboundedSender<()>>>>,
6567
stats: Arc<WsStats>,
6668
cancel_token: Arc<Mutex<Option<CancellationToken>>>,
69+
#[debug(skip)]
6770
event_callback: Arc<Mutex<Option<WsEventCallback>>>,
6871
/// Counter for dropped messages due to backpressure
6972
dropped_messages: Arc<AtomicU32>,

‎ccxt-core/src/ws_client/reconnect.rs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,4 +259,31 @@ impl AutoReconnectCoordinator {
259259

260260
info!("Auto-reconnect loop terminated");
261261
}
262+
263+
/// Convert into a stream (requires `tokio-stream`).
264+
#[cfg(feature = "websocket")]
265+
pub fn into_stream(self) -> impl futures::Stream<Item = serde_json::Value> {
266+
let client = self.client.clone();
267+
let coordinator = Arc::new(self);
268+
269+
async_stream::stream! {
270+
// Start reconnection in background
271+
coordinator.start().await;
272+
273+
loop {
274+
if let Some(msg) = client.receive().await {
275+
yield msg;
276+
} else {
277+
// Check if we should stop
278+
if !coordinator.is_enabled() {
279+
break;
280+
}
281+
// Wait a bit before retry if channel closed (shouldn't happen with reconnect)
282+
tokio::time::sleep(Duration::from_millis(100)).await;
283+
}
284+
}
285+
286+
coordinator.stop().await;
287+
}
288+
}
262289
}

‎ccxt-exchanges/Cargo.toml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ futures-util = { workspace = true }
5555

5656
# Security
5757
zeroize = { workspace = true }
58+
tokio-stream = "0.1.18"
5859

5960
[dev-dependencies]
6061
tokio-test = { workspace = true }
@@ -98,4 +99,4 @@ full = ["binance", "websocket"]
9899

99100
[[bench]]
100101
name = "binance_benchmark"
101-
harness = false
102+
harness = false

‎ccxt-exchanges/src/hyperliquid/mod.rs‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ pub struct HyperLiquid {
7373
options: HyperLiquidOptions,
7474
/// Authentication instance (optional, for private API).
7575
auth: Option<HyperLiquidAuth>,
76+
/// Persistent WebSocket connection reference.
77+
pub(crate) ws_connection: std::sync::Arc<tokio::sync::RwLock<Option<ws::HyperLiquidWs>>>,
7678
}
7779

7880
/// HyperLiquid-specific options.
@@ -146,6 +148,7 @@ impl HyperLiquid {
146148
base,
147149
options,
148150
auth,
151+
ws_connection: std::sync::Arc::new(tokio::sync::RwLock::new(None)),
149152
})
150153
}
151154

@@ -289,12 +292,11 @@ impl HyperLiquid {
289292
.map(auth::HyperLiquidAuth::wallet_address)
290293
}
291294

292-
// TODO: Implement in task 11 (WebSocket Implementation)
293-
// /// Creates a public WebSocket client.
294-
// pub fn create_ws(&self) -> ws::HyperLiquidWs {
295-
// let urls = self.urls();
296-
// ws::HyperLiquidWs::new(urls.ws)
297-
// }
295+
/// Creates a WebSocket client for public data streams.
296+
pub fn create_ws(&self) -> ws::HyperLiquidWs {
297+
let urls = self.urls();
298+
ws::HyperLiquidWs::new(urls.ws)
299+
}
298300
}
299301

300302
/// HyperLiquid API URLs.

0 commit comments

Comments
 (0)