diff --git a/README.md b/README.md index afdb06c..c697367 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ use polyfill_rs::{OrderBookImpl, WebSocketStream}; // Real-time order book with fixed-point optimizations let mut book = OrderBookImpl::new("token_id".to_string(), 100); -let mut stream = WebSocketStream::new("wss://ws-subscriptions-clob.polymarket.com").await?; +let mut stream = WebSocketStream::new("wss://ws-subscriptions-clob.polymarket.com/ws/market").await?; // Process thousands of updates per second while let Some(update) = stream.next().await { @@ -386,7 +386,7 @@ Here's how you connect to live market data. The library handles all the annoying ```rust use polyfill_rs::{WebSocketStream, StreamManager}; -let mut stream = WebSocketStream::new("wss://clob.polymarket.com/ws"); +let mut stream = WebSocketStream::new("wss://ws-subscriptions-clob.polymarket.com/ws/market"); // Set up authentication (you'll need API credentials) let auth = WssAuth { @@ -505,7 +505,7 @@ let reconnect_config = ReconnectConfig { backoff_multiplier: 2.0, // Double delay each time }; -let stream = WebSocketStream::new("wss://clob.polymarket.com/ws") +let stream = WebSocketStream::new("wss://ws-subscriptions-clob.polymarket.com/ws/market") .with_reconnect_config(reconnect_config); ``` diff --git a/examples/comprehensive_demo.rs b/examples/comprehensive_demo.rs index 9ff84d4..5d2ab78 100644 --- a/examples/comprehensive_demo.rs +++ b/examples/comprehensive_demo.rs @@ -579,7 +579,7 @@ impl PolyfillDemo { info!("=== Demo 8: Streaming Capabilities ==="); // Create a mock WebSocket stream - let _stream = WebSocketStream::new("wss://stream.polymarket.com"); + let _stream = WebSocketStream::new("wss://ws-subscriptions-clob.polymarket.com/ws/market"); info!("Created WebSocket stream"); diff --git a/src/stream.rs b/src/stream.rs index 67fd2d3..f5344f6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -160,12 +160,10 @@ impl WebSocketStream { } // Send subscription message in the format expected by Polymarket - let message = serde_json::json!({ - "auth": subscription.auth, - "markets": subscription.markets, - "asset_ids": subscription.asset_ids, - "type": subscription.channel_type, - }); + // The subscription struct will serialize correctly with proper field names + let message = serde_json::to_value(&subscription).map_err(|e| { + PolyfillError::parse(format!("Failed to serialize subscription: {}", e), None) + })?; self.send_message(message).await?; self.subscriptions.push(subscription.clone()); @@ -183,17 +181,67 @@ impl WebSocketStream { .clone(); let subscription = WssSubscription { - auth, - markets: Some(markets), - asset_ids: None, - channel_type: "USER".to_string(), + channel_type: "user".to_string(), + operation: Some("subscribe".to_string()), + markets, + asset_ids: Vec::new(), + initial_dump: Some(true), + custom_feature_enabled: None, + auth: Some(auth), }; self.subscribe_async(subscription).await } /// Subscribe to market channel (order book and trades) + /// Market subscriptions do not require authentication pub async fn subscribe_market_channel(&mut self, asset_ids: Vec) -> Result<()> { + let subscription = WssSubscription { + channel_type: "market".to_string(), + operation: Some("subscribe".to_string()), + markets: Vec::new(), + asset_ids, + initial_dump: Some(true), + custom_feature_enabled: None, + auth: None, + }; + + self.subscribe_async(subscription).await + } + + /// Subscribe to market channel with custom features enabled + /// Custom features include: best_bid_ask, new_market, market_resolved events + pub async fn subscribe_market_channel_with_features(&mut self, asset_ids: Vec) -> Result<()> { + let subscription = WssSubscription { + channel_type: "market".to_string(), + operation: Some("subscribe".to_string()), + markets: Vec::new(), + asset_ids, + initial_dump: Some(true), + custom_feature_enabled: Some(true), + auth: None, + }; + + self.subscribe_async(subscription).await + } + + /// Unsubscribe from market channel + pub async fn unsubscribe_market_channel(&mut self, asset_ids: Vec) -> Result<()> { + let subscription = WssSubscription { + channel_type: "market".to_string(), + operation: Some("unsubscribe".to_string()), + markets: Vec::new(), + asset_ids, + initial_dump: None, + custom_feature_enabled: None, + auth: None, + }; + + self.subscribe_async(subscription).await + } + + /// Unsubscribe from user channel + pub async fn unsubscribe_user_channel(&mut self, markets: Vec) -> Result<()> { let auth = self .auth .as_ref() @@ -201,42 +249,18 @@ impl WebSocketStream { .clone(); let subscription = WssSubscription { - auth, - markets: None, - asset_ids: Some(asset_ids), - channel_type: "MARKET".to_string(), + channel_type: "user".to_string(), + operation: Some("unsubscribe".to_string()), + markets, + asset_ids: Vec::new(), + initial_dump: None, + custom_feature_enabled: None, + auth: Some(auth), }; self.subscribe_async(subscription).await } - /// Unsubscribe from market data - pub async fn unsubscribe_async(&mut self, token_ids: &[String]) -> Result<()> { - // Note: Polymarket WebSocket API doesn't seem to have explicit unsubscribe - // We'll just remove from our local subscriptions - self.subscriptions - .retain(|sub| match sub.channel_type.as_str() { - "USER" => { - if let Some(markets) = &sub.markets { - !token_ids.iter().any(|id| markets.contains(id)) - } else { - true - } - }, - "MARKET" => { - if let Some(asset_ids) = &sub.asset_ids { - !token_ids.iter().any(|id| asset_ids.contains(id)) - } else { - true - } - }, - _ => true, - }); - - info!("Unsubscribed from {} tokens", token_ids.len()); - Ok(()) - } - /// Handle incoming WebSocket messages #[allow(dead_code)] async fn handle_message( diff --git a/src/types.rs b/src/types.rs index 7143ac0..79d3fa9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -675,15 +675,28 @@ pub struct WssAuth { /// WebSocket subscription request #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WssSubscription { - /// Authentication information - pub auth: WssAuth, - /// Array of markets (condition IDs) for USER channel - pub markets: Option>, - /// Array of asset IDs (token IDs) for MARKET channel - pub asset_ids: Option>, - /// Channel type: "USER" or "MARKET" + /// Channel type: "market" or "user" #[serde(rename = "type")] pub channel_type: String, + /// Operation type: "subscribe" or "unsubscribe" + #[serde(skip_serializing_if = "Option::is_none")] + pub operation: Option, + /// Array of markets (condition IDs) for USER channel + #[serde(default)] + pub markets: Vec, + /// Array of asset IDs (token IDs) for MARKET channel + /// Note: Field name is "assets_ids" (with 's') per Polymarket API spec + #[serde(rename = "assets_ids", default)] + pub asset_ids: Vec, + /// Request initial state dump + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_dump: Option, + /// Enable custom features (best_bid_ask, new_market, market_resolved) + #[serde(skip_serializing_if = "Option::is_none")] + pub custom_feature_enabled: Option, + /// Authentication information (only for USER channel) + #[serde(skip_serializing_if = "Option::is_none")] + pub auth: Option, } /// WebSocket message types for streaming