@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/LICENSE +661 -0
- package/README.md +212 -0
- package/dist/WSSubscriptionManager.d.ts +160 -0
- package/dist/WSSubscriptionManager.js +1020 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +21 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +34 -0
- package/dist/modules/OrderBookCache.d.ts +54 -0
- package/dist/modules/OrderBookCache.js +194 -0
- package/dist/types/PolymarketWebSocket.d.ts +460 -0
- package/dist/types/PolymarketWebSocket.js +86 -0
- package/dist/types/WebSocketSubscriptions.d.ts +32 -0
- package/dist/types/WebSocketSubscriptions.js +12 -0
- package/package.json +54 -0
- package/src/WSSubscriptionManager.ts +1126 -0
- package/src/index.ts +3 -0
- package/src/logger.ts +36 -0
- package/src/modules/OrderBookCache.ts +227 -0
- package/src/types/PolymarketWebSocket.ts +538 -0
- package/src/types/WebSocketSubscriptions.ts +35 -0
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 };
|