@nevuamarkets/poly-websockets 0.3.1 → 1.0.0-beta.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Poly-WebSockets
2
2
 
3
- A TypeScript library for **real-time Polymarket market price alerts** over **Websocket** with **automatic reconnections** and **easy subscription management**.
3
+ A TypeScript library for **real-time Polymarket market data** over **WebSocket** with **automatic reconnections** and **easy subscription management**.
4
4
 
5
5
  Powering [Nevua Markets](https://nevua.markets)
6
6
 
@@ -12,42 +12,44 @@ npm install @nevuamarkets/poly-websockets
12
12
 
13
13
  ## Features
14
14
 
15
- - 📊 **Real-time Market Updates**: Get `book` , `price_change`, `tick_size_change` and `last_trade_price` real-time market events from Polymarket WSS
16
- - 🎯 **Derived Future Price Event**: Implements Polymarket's [price calculation logic](https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated#future-price) (midpoint vs last trade price based on spread)
17
- - 🔗 **Group Management**: Efficiently manages multiple asset subscriptions across connection groups **without losing events** when subscribing / unsubscribing assets.
18
- - 🔄 **Automatic Connection Management**: Handles WebSocket connections, reconnections, and cleanup for grouped assetId (i.e. clobTokenId) subscriptions
19
- - 🚦 **Rate Limiting**: Built-in rate limiting to respect Polymarket API limits
15
+ - 📊 **Real-time Market Updates**: Get `book`, `price_change`, `tick_size_change` and `last_trade_price` events from Polymarket WebSocket
16
+ - 🎯 **Derived Price Event**: Implements Polymarket's [price calculation logic](https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated#future-price) (midpoint vs last trade price based on spread)
17
+ - 🔗 **Dynamic Subscriptions**: Subscribe and unsubscribe to assets without reconnecting
18
+ - 🔄 **Automatic Reconnection**: Handles connection drops with automatic reconnection
20
19
  - 💪 **TypeScript Support**: Full TypeScript definitions for all events and handlers
20
+ - 🔒 **Independent Instances**: Each manager instance is fully isolated with its own WebSocket connection
21
21
 
22
22
  ## Quick Start
23
23
 
24
24
  ```typescript
25
- import {
26
- WSSubscriptionManager,
27
- WebSocketHandlers
28
- } from '@nevuamarkets/poly-websockets';
25
+ import { WSSubscriptionManager } from '@nevuamarkets/poly-websockets';
29
26
 
30
- // Create the subscription manager with your own handlers
31
27
  const manager = new WSSubscriptionManager({
32
- onBook: async (events: BookEvent[]) => {
33
- for (const event of events) {
34
- console.log('book event', JSON.stringify(event, null, 2))
35
- }
28
+ onBook: async (events) => {
29
+ console.log('Book events:', events);
36
30
  },
37
- onPriceChange: async (events: PriceChangeEvent[]) => {
38
- for (const event of events) {
39
- console.log('price change event', JSON.stringify(event, null, 2))
40
- }
31
+ onPriceChange: async (events) => {
32
+ console.log('Price change events:', events);
33
+ },
34
+ onPolymarketPriceUpdate: async (events) => {
35
+ // Derived price following Polymarket's display logic
36
+ console.log('Price updates:', events);
37
+ },
38
+ onError: async (error) => {
39
+ console.error('Error:', error.message);
41
40
  }
42
41
  });
43
42
 
44
43
  // Subscribe to assets
45
44
  await manager.addSubscriptions(['asset-id-1', 'asset-id-2']);
46
45
 
46
+ // Get monitored assets
47
+ console.log('Monitored:', manager.getAssetIds());
48
+
47
49
  // Remove subscriptions
48
50
  await manager.removeSubscriptions(['asset-id-1']);
49
51
 
50
- // Clear all subscriptions and connections
52
+ // Clear all subscriptions and close connection
51
53
  await manager.clearState();
52
54
  ```
53
55
 
@@ -55,8 +57,6 @@ await manager.clearState();
55
57
 
56
58
  ### WSSubscriptionManager
57
59
 
58
- The main class that manages WebSocket connections and subscriptions.
59
-
60
60
  #### Constructor
61
61
 
62
62
  ```typescript
@@ -64,115 +64,81 @@ new WSSubscriptionManager(handlers: WebSocketHandlers, options?: SubscriptionMan
64
64
  ```
65
65
 
66
66
  **Parameters:**
67
- - `handlers` - Event handlers for different WebSocket events
68
- - `options` - Optional configuration object:
69
- - `maxMarketsPerWS?: number` - Maximum assets per WebSocket connection (default: 100)
70
- - `reconnectAndCleanupIntervalMs?: number` - Interval for reconnection attempts (default: 10s)
71
- - `burstLimiter?: Bottleneck` - Custom rate limiter instance. If none is provided, one will be created and used internally in the component.
67
+ - `handlers` - Event handlers for WebSocket events
68
+ - `options` - Optional configuration:
69
+ - `reconnectAndCleanupIntervalMs?: number` - Reconnection check interval (default: 5000ms)
70
+ - `pendingFlushIntervalMs?: number` - How often to flush pending subscriptions (default: 100ms)
71
+ - ~~`burstLimiter`~~ - **@deprecated** No longer used
72
+ - ~~`maxMarketsPerWS`~~ - **@deprecated** No longer used. Create multiple manager instances instead
72
73
 
73
74
  #### Methods
74
75
 
75
- ##### `addSubscriptions(assetIds: string[]): Promise<void>`
76
-
77
- Adds new asset subscriptions. The manager will:
78
- - Filter out already subscribed assets
79
- - Find available connection groups or create new ones
80
- - Establish WebSocket connections as needed
81
-
82
- ##### `removeSubscriptions(assetIds: string[]): Promise<void>`
83
-
84
- Removes asset subscriptions. **Connections are kept alive to avoid missing events**, and unused groups are cleaned up during the next reconnection cycle.
85
-
86
- ##### `clearState(): Promise<void>`
76
+ | Method | Description |
77
+ |--------|-------------|
78
+ | `addSubscriptions(assetIds: string[])` | Add assets to monitor |
79
+ | `removeSubscriptions(assetIds: string[])` | Stop monitoring assets |
80
+ | `getAssetIds(): string[]` | Get all monitored asset IDs (subscribed + pending) |
81
+ | `getStatistics()` | Get connection and subscription statistics |
82
+ | `clearState()` | Clear all subscriptions and close connection |
87
83
 
88
- Clears all subscriptions and state:
89
- - Removes all asset subscriptions
90
- - Closes all WebSocket connections
91
- - Clears the internal order book cache
84
+ #### Statistics Object
92
85
 
93
- ##### `getStatistics(): { openWebSockets: number; subscribedAssetIds: number }`
94
-
95
- Returns statistics about the current state of the subscription manager:
96
- - `openWebSockets`: The number of websockets that are currently in OPEN state
97
- - `subscribedAssetIds`: The number of unique asset IDs that are currently subscribed
86
+ ```typescript
87
+ manager.getStatistics() // Returns:
88
+ {
89
+ openWebSockets: number; // 1 if connected, 0 otherwise
90
+ assetIds: number; // Total monitored assets
91
+ pendingSubscribeCount: number; // Assets waiting to be subscribed
92
+ pendingUnsubscribeCount: number; // Assets waiting to be unsubscribed
93
+ }
94
+ ```
98
95
 
99
96
  ### WebSocketHandlers
100
97
 
101
- Interface defining event handlers for different WebSocket events.
102
-
103
98
  ```typescript
104
99
  interface WebSocketHandlers {
105
- // Core Polymarket WebSocket events
100
+ // Polymarket WebSocket events
106
101
  onBook?: (events: BookEvent[]) => Promise<void>;
107
102
  onLastTradePrice?: (events: LastTradePriceEvent[]) => Promise<void>;
108
103
  onPriceChange?: (events: PriceChangeEvent[]) => Promise<void>;
109
104
  onTickSizeChange?: (events: TickSizeChangeEvent[]) => Promise<void>;
110
105
 
111
- // Derived polymarket price update event
106
+ // Derived price update (implements Polymarket's display logic)
112
107
  onPolymarketPriceUpdate?: (events: PolymarketPriceUpdateEvent[]) => Promise<void>;
113
108
 
114
- // Connection lifecycle events
115
- onWSOpen?: (groupId: string, assetIds: string[]) => Promise<void>;
116
- onWSClose?: (groupId: string, code: number, reason: string) => Promise<void>;
109
+ // Connection events
110
+ onWSOpen?: (managerId: string, pendingAssetIds: string[]) => Promise<void>;
111
+ onWSClose?: (managerId: string, code: number, reason: string) => Promise<void>;
117
112
  onError?: (error: Error) => Promise<void>;
118
113
  }
119
114
  ```
120
115
 
121
- #### Key Event Types
122
-
123
- **BookEvent**
124
- - See // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#book-message
125
-
126
- **PriceChangeEvent**
127
- - See https://docs.polymarket.com/developers/CLOB/websocket/market-channel#price-change-message
116
+ ### Event Types
128
117
 
129
- **onTickSizeChange**
130
- - See https://docs.polymarket.com/developers/CLOB/websocket/market-channel#tick-size-change-message
118
+ | Event | Description |
119
+ |-------|-------------|
120
+ | `BookEvent` | Full order book snapshot ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#book-message)) |
121
+ | `PriceChangeEvent` | Order book price level changes ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#price-change-message)) |
122
+ | `TickSizeChangeEvent` | Tick size changes ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#tick-size-change-message)) |
123
+ | `LastTradePriceEvent` | Trade executions |
124
+ | `PolymarketPriceUpdateEvent` | Derived price using Polymarket's display logic |
131
125
 
132
- **LastTradePriceEvent**
133
- - Currently undocumented, but is emitted when a trade occurs
126
+ ## Multiple Independent Connections
134
127
 
135
- **PolymarketPriceUpdateEvent**
136
- - Derived price update following Polymarket's display logic
137
- - Uses midpoint when spread <= $0.10, otherwise uses last trade price
138
- - Includes full order book context
139
-
140
- ### Custom Rate Limiting
128
+ Each `WSSubscriptionManager` instance maintains its own WebSocket connection:
141
129
 
142
130
  ```typescript
143
- import Bottleneck from 'bottleneck';
144
-
145
- const customLimiter = new Bottleneck({
146
- reservoir: 10,
147
- reservoirRefreshAmount: 10,
148
- reservoirRefreshInterval: 1000,
149
- maxConcurrent: 10
150
- });
131
+ // Two separate connections for different asset groups
132
+ const manager1 = new WSSubscriptionManager(handlers1);
133
+ const manager2 = new WSSubscriptionManager(handlers2);
151
134
 
152
- const manager = new WSSubscriptionManager(handlers, {
153
- burstLimiter: customLimiter
154
- });
135
+ await manager1.addSubscriptions(['asset-1', 'asset-2']);
136
+ await manager2.addSubscriptions(['asset-3', 'asset-4']);
155
137
  ```
156
138
 
157
139
  ## Examples
158
140
 
159
- Check the [examples](./examples) folder for complete working examples
160
-
161
- ## Error Handling
162
-
163
- The library includes error handling:
164
- - Automatic reconnection on connection drops
165
- - User-defined error callbacks for custom handling
166
-
167
- ## Rate Limits
168
-
169
- Respects Polymarket's API rate limits:
170
- - Default: 5 requests per second burst limit
171
- - Configurable through custom Bottleneck instances
172
-
173
- ## License
174
-
175
- AGPL-3
141
+ See the [examples](./examples) folder for complete working examples.
176
142
 
177
143
  ## Testing
178
144
 
@@ -180,18 +146,10 @@ AGPL-3
180
146
  npm test
181
147
  ```
182
148
 
183
- ## TypeScript Support
149
+ ## License
184
150
 
185
- Full TypeScript definitions included.
151
+ AGPL-3.0
186
152
 
187
153
  ## Disclaimer
188
154
 
189
- This software is provided "as is", without warranty of any kind, express or implied. The author(s) are not responsible for:
190
-
191
- - Any financial losses incurred from using this software
192
- - Trading decisions made based on the data provided
193
- - Bugs, errors, or inaccuracies in the data
194
- - System failures or downtime
195
- - Any other damages arising from the use of this software
196
-
197
- Use at your own risk. Always verify data independently and never rely solely on automated systems for trading decisions.
155
+ This software is provided "as is", without warranty of any kind. The author(s) are not responsible for any financial losses, trading decisions, bugs, or system failures. Use at your own risk.
@@ -1,64 +1,145 @@
1
1
  import { WebSocketHandlers } from './types/PolymarketWebSocket';
2
2
  import { SubscriptionManagerOptions } from './types/WebSocketSubscriptions';
3
+ /**
4
+ * WebSocket Subscription Manager for Polymarket CLOB WebSocket.
5
+ *
6
+ * Each instance manages a single WebSocket connection and tracks:
7
+ * - Subscribed assets: Successfully subscribed to the WebSocket
8
+ * - Pending assets: Waiting to be subscribed (batched and flushed periodically)
9
+ *
10
+ * Instances are fully independent - no shared state between managers.
11
+ */
3
12
  declare class WSSubscriptionManager {
13
+ private readonly managerId;
4
14
  private handlers;
5
- private burstLimiter;
6
- private groupRegistry;
7
15
  private bookCache;
8
- private reconnectAndCleanupIntervalMs;
9
- private maxMarketsPerWS;
16
+ private wsClient;
17
+ private status;
18
+ private connecting;
19
+ private subscribedAssetIds;
20
+ private pendingSubscribeAssetIds;
21
+ private pendingUnsubscribeAssetIds;
22
+ private reconnectIntervalMs;
23
+ private pendingFlushIntervalMs;
24
+ private reconnectInterval?;
25
+ private pendingFlushInterval?;
26
+ private pingInterval?;
27
+ private connectionTimeout?;
10
28
  constructor(userHandlers: WebSocketHandlers, options?: SubscriptionManagerOptions);
11
29
  /**
12
30
  * Clears all WebSocket subscriptions and state.
13
31
  *
14
32
  * This will:
15
- *
16
- * 1. Remove all subscriptions and groups
17
- * 2. Close all WebSocket connections
18
- * 3. Clear the order book cache
33
+ * 1. Stop all timers
34
+ * 2. Close the WebSocket connection
35
+ * 3. Clear all asset tracking
36
+ * 4. Clear the order book cache
19
37
  */
20
38
  clearState(): Promise<void>;
21
39
  /**
22
- * This function is called when:
23
- * - a websocket event is received from the Polymarket WS
24
- * - a price update event detected, either by after a 'last_trade_price' event or a 'price_change' event
25
- * depending on the current bid-ask spread (see https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated)
26
- *
27
- * The user handlers will be called **ONLY** for assets that are actively subscribed to by any groups.
28
- *
29
- * @param events - The events to process.
30
- * @param action - The action to perform on the filtered events.
40
+ * Filters events to only include those for subscribed assets.
41
+ * Wraps user handler calls in try-catch to prevent user errors from breaking internal logic.
42
+ * Does not call the handler if all events are filtered out.
31
43
  */
32
44
  private actOnSubscribedEvents;
33
45
  /**
34
- * Edits wsGroups: Adds new subscriptions.
46
+ * Adds new subscriptions.
35
47
  *
36
- * - Filters out assets that are already subscribed
37
- * - Finds a group with capacity or creates a new one
38
- * - Creates a new WebSocket client and adds it to the group
48
+ * Assets are added to a pending queue and will be subscribed when:
49
+ * - The WebSocket connects (initial subscription)
50
+ * - The pending flush timer fires (for new assets on an existing connection)
39
51
  *
40
52
  * @param assetIdsToAdd - The asset IDs to add subscriptions for.
41
53
  */
42
54
  addSubscriptions(assetIdsToAdd: string[]): Promise<void>;
43
55
  /**
44
- * Edits wsGroups: Removes subscriptions.
45
- * The group will use the updated subscriptions when it reconnects.
46
- * We do that because we don't want to miss events by reconnecting.
56
+ * Ensures the periodic intervals are running.
57
+ * Called after clearState() when new subscriptions are added.
58
+ */
59
+ private ensureIntervalsRunning;
60
+ /**
61
+ * Schedules the next reconnection check.
62
+ * Uses a fixed interval (default 5 seconds) between checks.
63
+ */
64
+ private scheduleReconnectionCheck;
65
+ /**
66
+ * Safely calls the error handler, catching any exceptions thrown by it.
67
+ * Prevents user handler exceptions from breaking internal logic.
68
+ */
69
+ private safeCallErrorHandler;
70
+ /**
71
+ * Removes subscriptions.
47
72
  *
48
73
  * @param assetIdsToRemove - The asset IDs to remove subscriptions for.
49
74
  */
50
75
  removeSubscriptions(assetIdsToRemove: string[]): Promise<void>;
51
76
  /**
52
- * This function runs periodically and:
77
+ * Get all currently monitored asset IDs.
78
+ * This includes both successfully subscribed assets and pending subscriptions.
53
79
  *
54
- * - Tries to reconnect groups that have assets and are disconnected
55
- * - Cleans up groups that have no assets
80
+ * @returns Array of asset IDs being monitored.
81
+ */
82
+ getAssetIds(): string[];
83
+ /**
84
+ * Returns statistics about the current state of the subscription manager.
56
85
  */
57
- private reconnectAndCleanupGroups;
58
86
  getStatistics(): {
59
87
  openWebSockets: number;
60
- subscribedAssetIds: number;
88
+ assetIds: number;
89
+ pendingSubscribeCount: number;
90
+ pendingUnsubscribeCount: number;
91
+ /** @deprecated Use pendingSubscribeCount + pendingUnsubscribeCount instead */
92
+ pendingAssetIds: number;
61
93
  };
62
- private createWebSocketClient;
94
+ /**
95
+ * Flush pending subscriptions and unsubscriptions to the WebSocket.
96
+ *
97
+ * SUBSCRIPTION PROTOCOL NOTE:
98
+ * The Polymarket WebSocket protocol does NOT send any confirmation or acknowledgment
99
+ * messages for subscribe/unsubscribe operations. The server silently processes these
100
+ * requests. We optimistically assume success after sending. If the server rejects
101
+ * a request (e.g., invalid asset ID), events for those assets simply won't arrive -
102
+ * there is no error response to handle.
103
+ *
104
+ * This means:
105
+ * - We cannot definitively know if a subscription succeeded
106
+ * - We cannot definitively know if an unsubscription succeeded
107
+ * - The only indication of failure is the absence of expected events
108
+ */
109
+ private flushPendingSubscriptions;
110
+ /**
111
+ * Closes the WebSocket connection and cleans up related resources.
112
+ */
113
+ private closeWebSocket;
114
+ /**
115
+ * Check if we need to reconnect.
116
+ * Note: Assets are moved to pending in handleClose/handleError handlers,
117
+ * so this method only needs to check if reconnection is needed.
118
+ */
119
+ private checkReconnection;
120
+ /**
121
+ * Establish the WebSocket connection.
122
+ */
123
+ private connect;
124
+ /**
125
+ * Sets up event handlers for the WebSocket connection.
126
+ */
127
+ private setupEventHandlers;
128
+ /**
129
+ * Handles book events by updating the cache and notifying listeners.
130
+ */
131
+ private handleBookEvents;
132
+ /**
133
+ * Handles tick size change events by notifying listeners.
134
+ */
135
+ private handleTickEvents;
136
+ /**
137
+ * Handles price change events.
138
+ */
139
+ private handlePriceChangeEvents;
140
+ /**
141
+ * Handles last trade price events.
142
+ */
143
+ private handleLastTradeEvents;
63
144
  }
64
145
  export { WSSubscriptionManager, WebSocketHandlers };