fix: fix to wss subscription sigs

This commit is contained in:
floor-licker
2026-01-03 23:36:11 -05:00
parent 9dffa85410
commit 6da792b2ae
4 changed files with 89 additions and 52 deletions
+3 -3
View File
@@ -149,7 +149,7 @@ use polyfill_rs::{OrderBookImpl, WebSocketStream};
// Real-time order book with fixed-point optimizations // Real-time order book with fixed-point optimizations
let mut book = OrderBookImpl::new("token_id".to_string(), 100); 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 // Process thousands of updates per second
while let Some(update) = stream.next().await { 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 ```rust
use polyfill_rs::{WebSocketStream, StreamManager}; 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) // Set up authentication (you'll need API credentials)
let auth = WssAuth { let auth = WssAuth {
@@ -505,7 +505,7 @@ let reconnect_config = ReconnectConfig {
backoff_multiplier: 2.0, // Double delay each time 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); .with_reconnect_config(reconnect_config);
``` ```
+1 -1
View File
@@ -579,7 +579,7 @@ impl PolyfillDemo {
info!("=== Demo 8: Streaming Capabilities ==="); info!("=== Demo 8: Streaming Capabilities ===");
// Create a mock WebSocket stream // 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"); info!("Created WebSocket stream");
+65 -41
View File
@@ -160,12 +160,10 @@ impl WebSocketStream {
} }
// Send subscription message in the format expected by Polymarket // Send subscription message in the format expected by Polymarket
let message = serde_json::json!({ // The subscription struct will serialize correctly with proper field names
"auth": subscription.auth, let message = serde_json::to_value(&subscription).map_err(|e| {
"markets": subscription.markets, PolyfillError::parse(format!("Failed to serialize subscription: {}", e), None)
"asset_ids": subscription.asset_ids, })?;
"type": subscription.channel_type,
});
self.send_message(message).await?; self.send_message(message).await?;
self.subscriptions.push(subscription.clone()); self.subscriptions.push(subscription.clone());
@@ -183,17 +181,67 @@ impl WebSocketStream {
.clone(); .clone();
let subscription = WssSubscription { let subscription = WssSubscription {
auth, channel_type: "user".to_string(),
markets: Some(markets), operation: Some("subscribe".to_string()),
asset_ids: None, markets,
channel_type: "USER".to_string(), asset_ids: Vec::new(),
initial_dump: Some(true),
custom_feature_enabled: None,
auth: Some(auth),
}; };
self.subscribe_async(subscription).await self.subscribe_async(subscription).await
} }
/// Subscribe to market channel (order book and trades) /// 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<String>) -> Result<()> { pub async fn subscribe_market_channel(&mut self, asset_ids: Vec<String>) -> 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<String>) -> 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<String>) -> 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<String>) -> Result<()> {
let auth = self let auth = self
.auth .auth
.as_ref() .as_ref()
@@ -201,42 +249,18 @@ impl WebSocketStream {
.clone(); .clone();
let subscription = WssSubscription { let subscription = WssSubscription {
auth, channel_type: "user".to_string(),
markets: None, operation: Some("unsubscribe".to_string()),
asset_ids: Some(asset_ids), markets,
channel_type: "MARKET".to_string(), asset_ids: Vec::new(),
initial_dump: None,
custom_feature_enabled: None,
auth: Some(auth),
}; };
self.subscribe_async(subscription).await 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 /// Handle incoming WebSocket messages
#[allow(dead_code)] #[allow(dead_code)]
async fn handle_message( async fn handle_message(
+20 -7
View File
@@ -675,15 +675,28 @@ pub struct WssAuth {
/// WebSocket subscription request /// WebSocket subscription request
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WssSubscription { pub struct WssSubscription {
/// Authentication information /// Channel type: "market" or "user"
pub auth: WssAuth,
/// Array of markets (condition IDs) for USER channel
pub markets: Option<Vec<String>>,
/// Array of asset IDs (token IDs) for MARKET channel
pub asset_ids: Option<Vec<String>>,
/// Channel type: "USER" or "MARKET"
#[serde(rename = "type")] #[serde(rename = "type")]
pub channel_type: String, pub channel_type: String,
/// Operation type: "subscribe" or "unsubscribe"
#[serde(skip_serializing_if = "Option::is_none")]
pub operation: Option<String>,
/// Array of markets (condition IDs) for USER channel
#[serde(default)]
pub markets: Vec<String>,
/// 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<String>,
/// Request initial state dump
#[serde(skip_serializing_if = "Option::is_none")]
pub initial_dump: Option<bool>,
/// Enable custom features (best_bid_ask, new_market, market_resolved)
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_feature_enabled: Option<bool>,
/// Authentication information (only for USER channel)
#[serde(skip_serializing_if = "Option::is_none")]
pub auth: Option<WssAuth>,
} }
/// WebSocket message types for streaming /// WebSocket message types for streaming