@tentou-tech/poly-websockets 1.0.0

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 ADDED
@@ -0,0 +1,212 @@
1
+ # Poly-WebSockets
2
+
3
+ A TypeScript library for **real-time Polymarket market data** over **WebSocket** with **automatic reconnections** and **easy subscription management**.
4
+
5
+ Powering [Nevua Markets](https://nevua.markets)
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @tentou-tech/poly-websockets
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - 📊 **Real-time Market Updates**: Get `book`, `price_change`, `tick_size_change`, `last_trade_price`, `best_bid_ask`, `new_market` and `market_resolved` 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
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
+
22
+ ## Quick Start
23
+
24
+ ```typescript
25
+ import { WSSubscriptionManager } from '@nevuamarkets/poly-websockets';
26
+
27
+ const manager = new WSSubscriptionManager({
28
+ onBook: async (events) => {
29
+ console.log('Book events:', events);
30
+ },
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);
40
+ }
41
+ });
42
+
43
+ // Subscribe to assets
44
+ await manager.addSubscriptions(['asset-id-1', 'asset-id-2']);
45
+
46
+ // Get monitored assets
47
+ console.log('Monitored:', manager.getAssetIds());
48
+
49
+ // Remove subscriptions
50
+ await manager.removeSubscriptions(['asset-id-1']);
51
+
52
+ // Clear all subscriptions and close connection
53
+ await manager.clearState();
54
+ ```
55
+
56
+ ## Market-Level Events
57
+
58
+ To receive `new_market` and `market_resolved` events, enable custom features:
59
+
60
+ ```typescript
61
+ import { WSSubscriptionManager, NewMarketEvent, MarketResolvedEvent } from '@nevuamarkets/poly-websockets';
62
+
63
+ const manager = new WSSubscriptionManager({
64
+ onNewMarket: async (events: NewMarketEvent[]) => {
65
+ for (const event of events) {
66
+ console.log('New market created:', event.question);
67
+ console.log('Market ID:', event.id);
68
+ console.log('Asset IDs:', event.assets_ids);
69
+ }
70
+ },
71
+ onMarketResolved: async (events: MarketResolvedEvent[]) => {
72
+ for (const event of events) {
73
+ console.log('Market resolved:', event.question);
74
+ console.log('Winning outcome:', event.winning_outcome);
75
+ }
76
+ },
77
+ onBestBidAsk: async (events) => {
78
+ for (const event of events) {
79
+ console.log(`Best bid: ${event.best_bid}, Best ask: ${event.best_ask}`);
80
+ }
81
+ }
82
+ }, {
83
+ enableCustomFeatures: true // Required for new_market and market_resolved
84
+ });
85
+
86
+ // Market-level events work even without asset subscriptions
87
+ await manager.addSubscriptions([]);
88
+ ```
89
+
90
+ **Note:** Market-level events (`new_market`, `market_resolved`) are broadcast to all handlers regardless of asset subscriptions. Asset-level events (`book`, `price_change`, `best_bid_ask`, etc.) are only received for subscribed assets.
91
+
92
+ ## API Reference
93
+
94
+ ### WSSubscriptionManager
95
+
96
+ #### Constructor
97
+
98
+ ```typescript
99
+ new WSSubscriptionManager(handlers: WebSocketHandlers, options?: SubscriptionManagerOptions)
100
+ ```
101
+
102
+ **Parameters:**
103
+ - `handlers` - Event handlers for WebSocket events
104
+ - `options` - Optional configuration:
105
+ - `reconnectAndCleanupIntervalMs?: number` - Reconnection check interval (default: 5000ms)
106
+ - `pendingFlushIntervalMs?: number` - How often to flush pending subscriptions (default: 100ms)
107
+ - `enableCustomFeatures?: boolean` - Enable custom features like `new_market` and `market_resolved` events (default: false)
108
+
109
+ #### Methods
110
+
111
+ | Method | Description |
112
+ |--------|-------------|
113
+ | `addSubscriptions(assetIds: string[])` | Add assets to monitor |
114
+ | `removeSubscriptions(assetIds: string[])` | Stop monitoring assets |
115
+ | `getAssetIds(): string[]` | Get all monitored asset IDs (subscribed + pending) |
116
+ | `getStatistics()` | Get connection and subscription statistics |
117
+ | `clearState()` | Clear all subscriptions and close connection |
118
+
119
+ #### Statistics Object
120
+
121
+ ```typescript
122
+ manager.getStatistics() // Returns:
123
+ {
124
+ openWebSockets: number; // 1 if connected, 0 otherwise
125
+ assetIds: number; // Total monitored assets
126
+ pendingSubscribeCount: number; // Assets waiting to be subscribed
127
+ pendingUnsubscribeCount: number; // Assets waiting to be unsubscribed
128
+ }
129
+ ```
130
+
131
+ ### WebSocketHandlers
132
+
133
+ ```typescript
134
+ interface WebSocketHandlers {
135
+ // Polymarket WebSocket events (asset-level)
136
+ onBook?: (events: BookEvent[]) => Promise<void>;
137
+ onLastTradePrice?: (events: LastTradePriceEvent[]) => Promise<void>;
138
+ onPriceChange?: (events: PriceChangeEvent[]) => Promise<void>;
139
+ onTickSizeChange?: (events: TickSizeChangeEvent[]) => Promise<void>;
140
+ onBestBidAsk?: (events: BestBidAskEvent[]) => Promise<void>;
141
+
142
+ // Polymarket WebSocket events (market-level, requires enableCustomFeatures: true)
143
+ onNewMarket?: (events: NewMarketEvent[]) => Promise<void>;
144
+ onMarketResolved?: (events: MarketResolvedEvent[]) => Promise<void>;
145
+
146
+ // Derived price update (implements Polymarket's display logic)
147
+ onPolymarketPriceUpdate?: (events: PolymarketPriceUpdateEvent[]) => Promise<void>;
148
+
149
+ // Connection events
150
+ onWSOpen?: (managerId: string, pendingAssetIds: string[]) => Promise<void>;
151
+ onWSClose?: (managerId: string, code: number, reason: string) => Promise<void>;
152
+ onError?: (error: Error) => Promise<void>;
153
+ }
154
+ ```
155
+
156
+ ### Event Types
157
+
158
+ #### Asset-Level Events
159
+ These events are filtered by subscribed asset IDs:
160
+
161
+ | Event | Description |
162
+ |-------|-------------|
163
+ | `BookEvent` | Full order book snapshot ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#book-message)) |
164
+ | `PriceChangeEvent` | Order book price level changes ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#price-change-message)) |
165
+ | `TickSizeChangeEvent` | Tick size changes ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#tick-size-change-message)) |
166
+ | `LastTradePriceEvent` | Trade executions |
167
+ | `BestBidAskEvent` | Best bid/ask price updates ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#best_bid_ask-message)) |
168
+
169
+ #### Market-Level Events
170
+ These events are broadcast to all handlers (not filtered by subscriptions). Requires `enableCustomFeatures: true`:
171
+
172
+ | Event | Description |
173
+ |-------|-------------|
174
+ | `NewMarketEvent` | New market creation ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#new_market-message)) |
175
+ | `MarketResolvedEvent` | Market resolution ([docs](https://docs.polymarket.com/developers/CLOB/websocket/market-channel#market_resolved-message)) |
176
+
177
+ #### Derived Events
178
+
179
+ | Event | Description |
180
+ |-------|-------------|
181
+ | `PolymarketPriceUpdateEvent` | Derived price using Polymarket's display logic |
182
+
183
+ ## Multiple Independent Connections
184
+
185
+ Each `WSSubscriptionManager` instance maintains its own WebSocket connection:
186
+
187
+ ```typescript
188
+ // Two separate connections for different asset groups
189
+ const manager1 = new WSSubscriptionManager(handlers1);
190
+ const manager2 = new WSSubscriptionManager(handlers2);
191
+
192
+ await manager1.addSubscriptions(['asset-1', 'asset-2']);
193
+ await manager2.addSubscriptions(['asset-3', 'asset-4']);
194
+ ```
195
+
196
+ ## Examples
197
+
198
+ See the [examples](./examples) folder for complete working examples.
199
+
200
+ ## Testing
201
+
202
+ ```bash
203
+ npm test
204
+ ```
205
+
206
+ ## License
207
+
208
+ AGPL-3.0
209
+
210
+ ## Disclaimer
211
+
212
+ 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.
@@ -0,0 +1,160 @@
1
+ import { WebSocketHandlers } from './types/PolymarketWebSocket';
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
+ */
12
+ declare class WSSubscriptionManager {
13
+ private readonly managerId;
14
+ private handlers;
15
+ private bookCache;
16
+ private wsClient;
17
+ private status;
18
+ private connecting;
19
+ private subscribedAssetIds;
20
+ private pendingSubscribeAssetIds;
21
+ private pendingUnsubscribeAssetIds;
22
+ private enableCustomFeatures;
23
+ private reconnectIntervalMs;
24
+ private pendingFlushIntervalMs;
25
+ private reconnectInterval?;
26
+ private pendingFlushInterval?;
27
+ private pingInterval?;
28
+ private connectionTimeout?;
29
+ constructor(userHandlers: WebSocketHandlers, options?: SubscriptionManagerOptions);
30
+ /**
31
+ * Clears all WebSocket subscriptions and state.
32
+ *
33
+ * This will:
34
+ * 1. Stop all timers
35
+ * 2. Close the WebSocket connection
36
+ * 3. Clear all asset tracking
37
+ * 4. Clear the order book cache
38
+ */
39
+ clearState(): Promise<void>;
40
+ /**
41
+ * Filters events to only include those for subscribed assets.
42
+ * Wraps user handler calls in try-catch to prevent user errors from breaking internal logic.
43
+ * Does not call the handler if all events are filtered out.
44
+ */
45
+ private actOnSubscribedEvents;
46
+ /**
47
+ * Adds new subscriptions.
48
+ *
49
+ * Assets are added to a pending queue and will be subscribed when:
50
+ * - The WebSocket connects (initial subscription)
51
+ * - The pending flush timer fires (for new assets on an existing connection)
52
+ *
53
+ * @param assetIdsToAdd - The asset IDs to add subscriptions for.
54
+ */
55
+ addSubscriptions(assetIdsToAdd: string[]): Promise<void>;
56
+ /**
57
+ * Ensures the periodic intervals are running.
58
+ * Called after clearState() when new subscriptions are added.
59
+ */
60
+ private ensureIntervalsRunning;
61
+ /**
62
+ * Schedules the next reconnection check.
63
+ * Uses a fixed interval (default 5 seconds) between checks.
64
+ */
65
+ private scheduleReconnectionCheck;
66
+ /**
67
+ * Safely calls the error handler, catching any exceptions thrown by it.
68
+ * Prevents user handler exceptions from breaking internal logic.
69
+ */
70
+ private safeCallErrorHandler;
71
+ /**
72
+ * Removes subscriptions.
73
+ *
74
+ * @param assetIdsToRemove - The asset IDs to remove subscriptions for.
75
+ */
76
+ removeSubscriptions(assetIdsToRemove: string[]): Promise<void>;
77
+ /**
78
+ * Get all currently monitored asset IDs.
79
+ * This includes both successfully subscribed assets and pending subscriptions.
80
+ *
81
+ * @returns Array of asset IDs being monitored.
82
+ */
83
+ getAssetIds(): string[];
84
+ /**
85
+ * Returns statistics about the current state of the subscription manager.
86
+ */
87
+ getStatistics(): {
88
+ openWebSockets: number;
89
+ assetIds: number;
90
+ pendingSubscribeCount: number;
91
+ pendingUnsubscribeCount: number;
92
+ /** @deprecated Use pendingSubscribeCount + pendingUnsubscribeCount instead */
93
+ pendingAssetIds: number;
94
+ };
95
+ /**
96
+ * Flush pending subscriptions and unsubscriptions to the WebSocket.
97
+ *
98
+ * SUBSCRIPTION PROTOCOL NOTE:
99
+ * The Polymarket WebSocket protocol does NOT send any confirmation or acknowledgment
100
+ * messages for subscribe/unsubscribe operations. The server silently processes these
101
+ * requests. We optimistically assume success after sending. If the server rejects
102
+ * a request (e.g., invalid asset ID), events for those assets simply won't arrive -
103
+ * there is no error response to handle.
104
+ *
105
+ * This means:
106
+ * - We cannot definitively know if a subscription succeeded
107
+ * - We cannot definitively know if an unsubscription succeeded
108
+ * - The only indication of failure is the absence of expected events
109
+ */
110
+ private flushPendingSubscriptions;
111
+ /**
112
+ * Closes the WebSocket connection and cleans up related resources.
113
+ */
114
+ private closeWebSocket;
115
+ /**
116
+ * Check if we need to reconnect.
117
+ * Note: Assets are moved to pending in handleClose/handleError handlers,
118
+ * so this method only needs to check if reconnection is needed.
119
+ */
120
+ private checkReconnection;
121
+ /**
122
+ * Establish the WebSocket connection.
123
+ */
124
+ private connect;
125
+ /**
126
+ * Sets up event handlers for the WebSocket connection.
127
+ */
128
+ private setupEventHandlers;
129
+ /**
130
+ * Handles book events by updating the cache and notifying listeners.
131
+ */
132
+ private handleBookEvents;
133
+ /**
134
+ * Handles tick size change events by notifying listeners.
135
+ */
136
+ private handleTickEvents;
137
+ /**
138
+ * Handles price change events.
139
+ */
140
+ private handlePriceChangeEvents;
141
+ /**
142
+ * Handles last trade price events.
143
+ */
144
+ private handleLastTradeEvents;
145
+ /**
146
+ * Handles best bid/ask events.
147
+ */
148
+ private handleBestBidAskEvents;
149
+ /**
150
+ * Handles new market events.
151
+ * These events bypass asset-level filtering as they are market-level events.
152
+ */
153
+ private handleNewMarketEvents;
154
+ /**
155
+ * Handles market resolved events.
156
+ * These events bypass asset-level filtering as they are market-level events.
157
+ */
158
+ private handleMarketResolvedEvents;
159
+ }
160
+ export { WSSubscriptionManager, WebSocketHandlers };