@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.
@@ -0,0 +1,538 @@
1
+ /**
2
+ * Represents a single price level in the order book
3
+ * @example
4
+ * { price: "0.01", size: "510000" }
5
+ */
6
+ export type PriceLevel = {
7
+ price: string;
8
+ size: string;
9
+ };
10
+
11
+ // ============================================================================
12
+ // WebSocket Messages (Client -> Server)
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Initial subscription message sent when connecting to the market WebSocket.
17
+ *
18
+ * @example
19
+ * {
20
+ * assets_ids: ["71321045679252212594626385532706912750332728571942532289631379312455583992563"],
21
+ * type: "market"
22
+ * }
23
+ */
24
+ export type MarketSubscriptionMessage = {
25
+ assets_ids: string[];
26
+ type: 'market';
27
+ custom_feature_enabled?: boolean; // Custom feature flag for Polymarket
28
+ };
29
+
30
+ /**
31
+ * Subscribe to additional assets on an existing connection.
32
+ *
33
+ * IMPORTANT: The Polymarket WebSocket protocol does NOT send any confirmation or
34
+ * acknowledgment message for subscribe operations. The server silently accepts the
35
+ * request, and the client will start receiving events for the subscribed assets.
36
+ * There is no way to know if a subscription was rejected (events simply won't arrive).
37
+ *
38
+ * @example
39
+ * {
40
+ * operation: "subscribe",
41
+ * assets_ids: ["71321045679252212594626385532706912750332728571942532289631379312455583992563"]
42
+ * }
43
+ */
44
+ export type SubscribeMessage = {
45
+ operation: 'subscribe';
46
+ assets_ids: string[];
47
+ custom_feature_enabled?: boolean; // Custom feature flag for Polymarket
48
+ };
49
+
50
+ /**
51
+ * Unsubscribe from assets on an existing connection.
52
+ *
53
+ * IMPORTANT: The Polymarket WebSocket protocol does NOT send any confirmation or
54
+ * acknowledgment message for unsubscribe operations. The server silently accepts the
55
+ * request, and the client will stop receiving events for the unsubscribed assets.
56
+ * There is no way to know if an unsubscription was rejected.
57
+ *
58
+ * @example
59
+ * {
60
+ * operation: "unsubscribe",
61
+ * assets_ids: ["71321045679252212594626385532706912750332728571942532289631379312455583992563"]
62
+ * }
63
+ */
64
+ export type UnsubscribeMessage = {
65
+ operation: 'unsubscribe';
66
+ assets_ids: string[];
67
+ custom_feature_enabled?: boolean; // Custom feature flag for Polymarket
68
+ };
69
+
70
+ /**
71
+ * Union type for all client-to-server messages.
72
+ */
73
+ export type PolymarketWSMessage = MarketSubscriptionMessage | SubscribeMessage | UnsubscribeMessage;
74
+
75
+ // ============================================================================
76
+ // WebSocket Events (Server -> Client)
77
+ // ============================================================================
78
+
79
+ /**
80
+ * Represents a single price change item
81
+ */
82
+ export type PriceChangeItem = {
83
+ asset_id: string;
84
+ price: string;
85
+ size: string;
86
+ side: 'BUY' | 'SELL';
87
+ hash: string;
88
+ best_bid: string;
89
+ best_ask: string;
90
+ };
91
+
92
+ /**
93
+ * Represents a price_change event from Polymarket WebSocket
94
+ *
95
+ * @example
96
+ * {
97
+ * market: "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
98
+ * price_changes: [
99
+ * {
100
+ * asset_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
101
+ * price: "0.5",
102
+ * size: "200",
103
+ * side: "BUY",
104
+ * hash: "56621a121a47ed9333273e21c83b660cff37ae50",
105
+ * best_bid: "0.5",
106
+ * best_ask: "1"
107
+ * }
108
+ * ],
109
+ * timestamp: "1757908892351",
110
+ * event_type: "price_change"
111
+ * }
112
+ */
113
+ export type PriceChangeEvent = {
114
+ event_type: 'price_change';
115
+ market: string;
116
+ timestamp: string;
117
+ price_changes: PriceChangeItem[];
118
+ };
119
+
120
+ /**
121
+ * Represents a Polymarket book
122
+ * @example
123
+ * {
124
+ * bids: [
125
+ * { price: "0.01", size: "510000" },
126
+ * { price: "0.02", size: "3100" }
127
+ * ],
128
+ * asks: [
129
+ * { price: "0.99", size: "58.07" },
130
+ * { price: "0.97", size: "178.73" }
131
+ * }
132
+ */
133
+ export type Book = {
134
+ bids: PriceLevel[];
135
+ asks: PriceLevel[];
136
+ };
137
+
138
+ /**
139
+ * Represents a book event from Polymarket WebSocket
140
+ * @example
141
+ * {
142
+ * market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
143
+ * asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
144
+ * timestamp: "1740759191594",
145
+ * hash: "c0e51b1cfdbcb1b2aec58feaf7b01004019a89c6",
146
+ * bids: [
147
+ * { price: "0.01", size: "510000" },
148
+ * { price: "0.02", size: "3100" }
149
+ * ],
150
+ * asks: [
151
+ * { price: "0.99", size: "58.07" },
152
+ * { price: "0.97", size: "178.73" }
153
+ * ],
154
+ * event_type: "book"
155
+ * }
156
+ */
157
+ export type BookEvent = {
158
+ market: string;
159
+ asset_id: string;
160
+ timestamp: string;
161
+ hash: string;
162
+ bids: PriceLevel[];
163
+ asks: PriceLevel[];
164
+ event_type: 'book';
165
+ };
166
+
167
+ /**
168
+ * Represents a last trade price event from Polymarket WebSocket
169
+ * @example
170
+ * {
171
+ * asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
172
+ * event_type: "last_trade_price",
173
+ * fee_rate_bps: "0",
174
+ * market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
175
+ * price: "0.12",
176
+ * side: "BUY",
177
+ * size: "8.333332",
178
+ * timestamp: "1740760245471",
179
+ * transaction_hash: "0xd449923990fce41c5fcd1fef8079df5b1dc55fa00c2df62831d0bd3a7cdcc2aa"
180
+ * }
181
+ */
182
+ export type LastTradePriceEvent = {
183
+ asset_id: string;
184
+ event_type: 'last_trade_price';
185
+ fee_rate_bps: string;
186
+ market: string;
187
+ price: string;
188
+ side: 'BUY' | 'SELL';
189
+ size: string;
190
+ timestamp: string;
191
+ transaction_hash: string;
192
+ };
193
+
194
+ /**
195
+ * Represents a tick size change event from Polymarket WebSocket
196
+ * @example
197
+ * {
198
+ * event_type: "tick_size_change",
199
+ * asset_id: "65818619657568813474341868652308942079804919287380422192892211131408793125422",
200
+ * market: "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
201
+ * old_tick_size: "0.01",
202
+ * new_tick_size: "0.001",
203
+ * timestamp: "100000000"
204
+ * }
205
+ */
206
+ export type TickSizeChangeEvent = {
207
+ asset_id: string;
208
+ event_type: 'tick_size_change';
209
+ market: string;
210
+ old_tick_size: string;
211
+ new_tick_size: string;
212
+ timestamp: string;
213
+ };
214
+
215
+ /**
216
+ * Represents a best bid/ask event from Polymarket WebSocket
217
+ *
218
+ * Emitted when the best bid or ask price changes
219
+ *
220
+ * @example
221
+ * {
222
+ * event_type: "best_bid_ask",
223
+ * asset_id: "65818619657568813474341868652308942079804919287380422192892211131408793125422",
224
+ * market: "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
225
+ * best_bid: "0.48",
226
+ * best_ask: "0.52",
227
+ * timestamp: "123456789000"
228
+ * }
229
+ */
230
+ export type BestBidAskEvent = {
231
+ asset_id: string;
232
+ event_type: 'best_bid_ask';
233
+ market: string;
234
+ best_bid: string;
235
+ best_ask: string;
236
+ timestamp: string;
237
+ };
238
+
239
+ /**
240
+ * Represents the event message object nested in new_market and market_resolved events
241
+ */
242
+ export type EventMessage = {
243
+ id: string;
244
+ ticker: string;
245
+ slug: string;
246
+ title: string;
247
+ description: string;
248
+ };
249
+
250
+ /**
251
+ * Represents a new_market event from Polymarket WebSocket
252
+ *
253
+ * Note: This event requires custom_feature_enabled flag to be set to true
254
+ *
255
+ * @example
256
+ * {
257
+ * id: "1031769",
258
+ * question: "Will NVIDIA (NVDA) close above $240 end of January?",
259
+ * market: "0x311d0c4b6671ab54af4970c06fcf58662516f5168997bdda209ec3db5aa6b0c1",
260
+ * slug: "nvda-above-240-on-january-30-2026",
261
+ * description: "This market will resolve to...",
262
+ * assets_ids: ["76043073756653678226373981964075571318267289248134717369284518995922789326425"],
263
+ * outcomes: ["Yes", "No"],
264
+ * event_message: {
265
+ * id: "125819",
266
+ * ticker: "nvda-above-in-january-2026",
267
+ * slug: "nvda-above-in-january-2026",
268
+ * title: "Will NVIDIA (NVDA) close above ___ end of January?",
269
+ * description: "..."
270
+ * },
271
+ * timestamp: "1766790415550",
272
+ * event_type: "new_market"
273
+ * }
274
+ */
275
+ export type NewMarketEvent = {
276
+ event_type: 'new_market';
277
+ id: string;
278
+ question: string;
279
+ market: string;
280
+ slug: string;
281
+ description: string;
282
+ assets_ids: string[];
283
+ outcomes: string[];
284
+ event_message: EventMessage;
285
+ timestamp: string;
286
+ };
287
+
288
+ /**
289
+ * Represents a market_resolved event from Polymarket WebSocket
290
+ *
291
+ * Note: This event requires custom_feature_enabled flag to be set to true
292
+ *
293
+ * @example
294
+ * {
295
+ * id: "1031769",
296
+ * question: "Will NVIDIA (NVDA) close above $240 end of January?",
297
+ * market: "0x311d0c4b6671ab54af4970c06fcf58662516f5168997bdda209ec3db5aa6b0c1",
298
+ * slug: "nvda-above-240-on-january-30-2026",
299
+ * description: "This market will resolve to...",
300
+ * assets_ids: ["76043073756653678226373981964075571318267289248134717369284518995922789326425"],
301
+ * outcomes: ["Yes", "No"],
302
+ * winning_asset_id: "76043073756653678226373981964075571318267289248134717369284518995922789326425",
303
+ * winning_outcome: "Yes",
304
+ * event_message: {
305
+ * id: "125819",
306
+ * ticker: "nvda-above-in-january-2026",
307
+ * slug: "nvda-above-in-january-2026",
308
+ * title: "Will NVIDIA (NVDA) close above ___ end of January?",
309
+ * description: "..."
310
+ * },
311
+ * timestamp: "1766790415550",
312
+ * event_type: "market_resolved"
313
+ * }
314
+ */
315
+ export type MarketResolvedEvent = {
316
+ event_type: 'market_resolved';
317
+ id: string;
318
+ question: string;
319
+ market: string;
320
+ slug: string;
321
+ description: string;
322
+ assets_ids: string[];
323
+ outcomes: string[];
324
+ winning_asset_id: string;
325
+ winning_outcome: string;
326
+ event_message: EventMessage;
327
+ timestamp: string;
328
+ };
329
+
330
+ /**
331
+ * Union type representing all possible event types from Polymarket WebSocket
332
+ * @example BookEvent
333
+ * {
334
+ * market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
335
+ * asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
336
+ * timestamp: "1740759191594",
337
+ * hash: "c0e51b1cfdbcb1b2aec58feaf7b01004019a89c6",
338
+ * bids: [{ price: "0.01", size: "510000" }],
339
+ * asks: [{ price: "0.99", size: "58.07" }],
340
+ * event_type: "book"
341
+ * }
342
+ *
343
+ * @example LastTradePriceEvent
344
+ * {
345
+ * asset_id: "101007741586870489619361069512452187353898396425142157315847015703471254508752",
346
+ * event_type: "last_trade_price",
347
+ * fee_rate_bps: "0",
348
+ * market: "0xf83fb46dd70a4459fcc441a8511701c463374c5c3c250f585d74fda85ddfb7c9",
349
+ * price: "0.12",
350
+ * side: "BUY",
351
+ * size: "8.333332",
352
+ * timestamp: "1740760245471"
353
+ * }
354
+ *
355
+ * @example PriceChangeEvent
356
+ * {
357
+ * market: "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
358
+ * price_changes: [
359
+ * {
360
+ * asset_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563",
361
+ * price: "0.5",
362
+ * size: "200",
363
+ * side: "BUY",
364
+ * hash: "56621a121a47ed9333273e21c83b660cff37ae50",
365
+ * best_bid: "0.5",
366
+ * best_ask: "1"
367
+ * }
368
+ * ],
369
+ * timestamp: "1757908892351",
370
+ * event_type: "price_change"
371
+ * }
372
+ *
373
+ * @example TickSizeChangeEvent
374
+ * {
375
+ * event_type: "tick_size_change",
376
+ * asset_id: "65818619657568813474341868652308942079804919287380422192892211131408793125422",
377
+ * market: "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
378
+ * old_tick_size: "0.01",
379
+ * new_tick_size: "0.001",
380
+ * timestamp: "100000000"
381
+ * }
382
+ */
383
+ export type PolymarketWSEvent = BookEvent | LastTradePriceEvent | PriceChangeEvent | TickSizeChangeEvent | BestBidAskEvent | NewMarketEvent | MarketResolvedEvent;
384
+
385
+ /**
386
+ * Represents a price update event
387
+ *
388
+ * This is an event that is emitted to faciliate price update events. It is
389
+ * not emitted by the Polymarket WebSocket directly.
390
+ *
391
+ * See https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated
392
+ *
393
+ * TLDR: The prices displayed on Polymarket are the midpoint of the bid-ask spread in the orderbook,
394
+ * UNLESS that spread is over $0.10, in which case the **last traded price** is used.
395
+ */
396
+ export interface PolymarketPriceUpdateEvent {
397
+ event_type: 'price_update';
398
+ asset_id: string;
399
+ timestamp: string;
400
+ triggeringEvent: LastTradePriceEvent | PriceChangeEvent;
401
+ book: Book;
402
+ price: string;
403
+ midpoint: string;
404
+ spread: string;
405
+ }
406
+
407
+ /**
408
+ * Represents the handlers for the Polymarket WebSocket
409
+ */
410
+ export type WebSocketHandlers = {
411
+
412
+ /*
413
+ Polymarket WebSocket event handlers
414
+ */
415
+
416
+ // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#book-message
417
+ onBook?: (events: BookEvent[]) => Promise<void>;
418
+
419
+ // Currently undocumented, but is emitted when a trade occurs
420
+ onLastTradePrice?: (events: LastTradePriceEvent[]) => Promise<void>;
421
+
422
+ // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#tick-size-change-message
423
+ onTickSizeChange?: (events: TickSizeChangeEvent[]) => Promise<void>;
424
+
425
+ // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#price-change-message
426
+ onPriceChange?: (events: PriceChangeEvent[]) => Promise<void>;
427
+
428
+ // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#best_bid_ask-message
429
+ onBestBidAsk?: (events: BestBidAskEvent[]) => Promise<void>;
430
+
431
+ // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#new_market-message
432
+ // Note: Requires enableCustomFeatures option to be set to true
433
+ onNewMarket?: (events: NewMarketEvent[]) => Promise<void>;
434
+
435
+ // https://docs.polymarket.com/developers/CLOB/websocket/market-channel#market_resolved-message
436
+ // Note: Requires enableCustomFeatures option to be set to true
437
+ onMarketResolved?: (events: MarketResolvedEvent[]) => Promise<void>;
438
+
439
+ /*
440
+ Also mentioned as 'Future Price', this is the price that is displayed on the Polymarket UI
441
+ and denotes the probability of an event happening. Read more about it here:
442
+ https://docs.polymarket.com/polymarket-learn/trading/how-are-prices-calculated#future-price
443
+
444
+ This is a derived event that is not emmited by the Polymarket WebSocket directly.
445
+ */
446
+ onPolymarketPriceUpdate?: (events: PolymarketPriceUpdateEvent[]) => Promise<void>;
447
+
448
+ // Error handling
449
+ onError?: (error: Error) => Promise<void>;
450
+
451
+ // Connection lifecycle events
452
+ onWSClose?: (managerId: string, code: number, reason: string) => Promise<void>;
453
+ onWSOpen?: (managerId: string, pendingAssetIds: string[]) => Promise<void>;
454
+ }
455
+
456
+ /**
457
+ * Type guard to check if an event is a BookEvent
458
+ * @example
459
+ * if (isBookEvent(event)) {
460
+ * // event is now typed as BookEvent
461
+ * console.log(event.bids);
462
+ * }
463
+ */
464
+ export function isBookEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is BookEvent {
465
+ return event?.event_type === 'book';
466
+ }
467
+
468
+ /**
469
+ * Type guard to check if an event is a LastTradePriceEvent
470
+ * @example
471
+ * if (isLastTradePriceEvent(event)) {
472
+ * // event is now typed as LastTradePriceEvent
473
+ * console.log(event.side);
474
+ * }
475
+ */
476
+ export function isLastTradePriceEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is LastTradePriceEvent {
477
+ return event?.event_type === 'last_trade_price';
478
+ }
479
+
480
+ /**
481
+ * Type guard to check if an event is a PriceChangeEvent
482
+ * @example
483
+ * if (isPriceChangeEvent(event)) {
484
+ * // event is now typed as PriceChangeEvent
485
+ * console.log(event.changes);
486
+ * }
487
+ */
488
+ export function isPriceChangeEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is PriceChangeEvent {
489
+ return event?.event_type === 'price_change';
490
+ }
491
+
492
+ /**
493
+ * Type guard to check if an event is a TickSizeChangeEvent
494
+ * @example
495
+ * if (isTickSizeChangeEvent(event)) {
496
+ * // event is now typed as TickSizeChangeEvent
497
+ * console.log(event.old_tick_size);
498
+ * }
499
+ */
500
+ export function isTickSizeChangeEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is TickSizeChangeEvent {
501
+ return event?.event_type === 'tick_size_change';
502
+ }
503
+
504
+ /**
505
+ * Type guard to check if an event is a BestBidAskEvent
506
+ * @example
507
+ * if (isBestBidAskEvent(event)) {
508
+ * // event is now typed as BestBidAskEvent
509
+ * console.log(event.best_bid, event.best_ask);
510
+ * }
511
+ */
512
+ export function isBestBidAskEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is BestBidAskEvent {
513
+ return event?.event_type === 'best_bid_ask';
514
+ }
515
+
516
+ /**
517
+ * Type guard to check if an event is a NewMarketEvent
518
+ * @example
519
+ * if (isNewMarketEvent(event)) {
520
+ * // event is now typed as NewMarketEvent
521
+ * console.log(event.question);
522
+ * }
523
+ */
524
+ export function isNewMarketEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is NewMarketEvent {
525
+ return event?.event_type === 'new_market';
526
+ }
527
+
528
+ /**
529
+ * Type guard to check if an event is a MarketResolvedEvent
530
+ * @example
531
+ * if (isMarketResolvedEvent(event)) {
532
+ * // event is now typed as MarketResolvedEvent
533
+ * console.log(event.winning_outcome);
534
+ * }
535
+ */
536
+ export function isMarketResolvedEvent(event: PolymarketWSEvent | PolymarketPriceUpdateEvent): event is MarketResolvedEvent {
537
+ return event?.event_type === 'market_resolved';
538
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Connection status for the WebSocket.
3
+ */
4
+ export enum WebSocketConnectionStatus {
5
+ DISCONNECTED = 'disconnected',
6
+ CONNECTING = 'connecting',
7
+ CONNECTED = 'connected',
8
+ }
9
+
10
+ /**
11
+ * Options for configuring the WSSubscriptionManager.
12
+ */
13
+ export type SubscriptionManagerOptions = {
14
+ /**
15
+ * How often to check for reconnection (in milliseconds).
16
+ * Default: 5000ms (5 seconds)
17
+ *
18
+ * Note: We intentionally use a static interval rather than exponential backoff.
19
+ * Perhaps change this to exponential backoff in the future.
20
+ */
21
+ reconnectAndCleanupIntervalMs?: number;
22
+
23
+ /**
24
+ * How often to flush pending subscriptions to the WebSocket (in milliseconds).
25
+ * Default: 100ms
26
+ */
27
+ pendingFlushIntervalMs?: number;
28
+
29
+ /**
30
+ * Enable custom features such as new_market and market_resolved events.
31
+ * When enabled, sets the custom_feature_enabled flag in WebSocket messages.
32
+ * Default: false
33
+ */
34
+ enableCustomFeatures?: boolean;
35
+ }