@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,3 @@
1
+ export { WSSubscriptionManager, WebSocketHandlers } from './WSSubscriptionManager';
2
+ export * from './types/PolymarketWebSocket';
3
+ export * from './types/WebSocketSubscriptions';
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.WSSubscriptionManager = void 0;
18
+ var WSSubscriptionManager_1 = require("./WSSubscriptionManager");
19
+ Object.defineProperty(exports, "WSSubscriptionManager", { enumerable: true, get: function () { return WSSubscriptionManager_1.WSSubscriptionManager; } });
20
+ __exportStar(require("./types/PolymarketWebSocket"), exports);
21
+ __exportStar(require("./types/WebSocketSubscriptions"), exports);
@@ -0,0 +1,2 @@
1
+ import winston from 'winston';
2
+ export declare const logger: winston.Logger;
package/dist/logger.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = void 0;
7
+ const winston_1 = __importDefault(require("winston"));
8
+ // Override with LOG_LEVEL environment variable (e.g., LOG_LEVEL=info npm start)
9
+ exports.logger = winston_1.default.createLogger({
10
+ level: process.env.LOG_LEVEL || 'warn',
11
+ format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.errors({ stack: true }), winston_1.default.format.colorize(), winston_1.default.format.printf(({ level, message, timestamp, ...rest }) => {
12
+ // Ensure consistent order: timestamp, level, message, then rest of fields
13
+ const restString = Object.keys(rest)
14
+ .filter(key => key !== 'service') // Exclude service since we add it in defaultMeta
15
+ .sort()
16
+ .map(key => `${key}: ${JSON.stringify(rest[key])}`)
17
+ .join(', ');
18
+ return `${timestamp} ${level}: ${message}${restString ? ` (${restString})` : ''}`;
19
+ })),
20
+ defaultMeta: { service: 'poly-websockets' },
21
+ transports: [
22
+ new winston_1.default.transports.Console({
23
+ format: winston_1.default.format.combine(winston_1.default.format.colorize({
24
+ all: true,
25
+ colors: {
26
+ error: 'red',
27
+ warn: 'yellow',
28
+ info: 'cyan',
29
+ debug: 'green'
30
+ }
31
+ }))
32
+ })
33
+ ]
34
+ });
@@ -0,0 +1,54 @@
1
+ import { BookEvent, PriceChangeEvent, PriceLevel } from '../types/PolymarketWebSocket';
2
+ export interface BookEntry {
3
+ bids: PriceLevel[];
4
+ asks: PriceLevel[];
5
+ price: string | null;
6
+ midpoint: string | null;
7
+ spread: string | null;
8
+ }
9
+ export declare class OrderBookCache {
10
+ private bookCache;
11
+ constructor();
12
+ /**
13
+ * Replace full book (after a `book` event)
14
+ * @param event new orderbook event
15
+ */
16
+ replaceBook(event: BookEvent): void;
17
+ /**
18
+ * Update a cached book from a `price_change` event.
19
+ *
20
+ * @param event PriceChangeEvent
21
+ * @returns true if the book was updated.
22
+ * @throws if the book is not found.
23
+ */
24
+ upsertPriceChange(event: PriceChangeEvent): void;
25
+ /**
26
+ * Side effect: updates the book's spread
27
+ *
28
+ * @returns `true` if best-bid/best-ask spread exceeds `cents`.
29
+ * @throws if either side of the book is empty.
30
+ */
31
+ spreadOver(assetId: string, cents?: number): boolean;
32
+ /**
33
+ * Calculate the midpoint of the book, rounded to 3dp, no trailing zeros
34
+ *
35
+ * Side effect: updates the book's midpoint
36
+ *
37
+ * Throws if
38
+ * - the book is not found or missing either bid or ask
39
+ * - the midpoint is NaN.
40
+ */
41
+ midpoint(assetId: string): string;
42
+ /**
43
+ * Removes a specific market from the orderbook if assetId is provided
44
+ * otherwise clears all orderbook
45
+ * @param assetId tokenId of a market
46
+ */
47
+ clear(assetId?: string): void;
48
+ /**
49
+ * Get a book entry by asset id.
50
+ *
51
+ * @returns book entry if found, otherwise null
52
+ */
53
+ getBookEntry(assetId: string): BookEntry | null;
54
+ }
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OrderBookCache = void 0;
4
+ function sortDescendingInPlace(bookSide) {
5
+ bookSide.sort((a, b) => parseFloat(b.price) - parseFloat(a.price));
6
+ }
7
+ function sortAscendingInPlace(bookSide) {
8
+ bookSide.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));
9
+ }
10
+ class OrderBookCache {
11
+ constructor() {
12
+ this.bookCache = {};
13
+ }
14
+ /**
15
+ * Replace full book (after a `book` event)
16
+ * @param event new orderbook event
17
+ */
18
+ replaceBook(event) {
19
+ let lastPrice = null;
20
+ let lastMidpoint = null;
21
+ let lastSpread = null;
22
+ if (this.bookCache[event.asset_id]) {
23
+ lastPrice = this.bookCache[event.asset_id].price;
24
+ lastMidpoint = this.bookCache[event.asset_id].midpoint;
25
+ lastSpread = this.bookCache[event.asset_id].spread;
26
+ }
27
+ this.bookCache[event.asset_id] = {
28
+ bids: [...event.bids],
29
+ asks: [...event.asks],
30
+ price: lastPrice,
31
+ midpoint: lastMidpoint,
32
+ spread: lastSpread,
33
+ };
34
+ /* Polymarket book events are currently sorted as such:
35
+ * - bids (buys) ascending
36
+ * - asks (sells) descending
37
+ *
38
+ * So we maintain this order in the cache.
39
+ */
40
+ sortAscendingInPlace(this.bookCache[event.asset_id].bids);
41
+ sortDescendingInPlace(this.bookCache[event.asset_id].asks);
42
+ }
43
+ /**
44
+ * Update a cached book from a `price_change` event.
45
+ *
46
+ * @param event PriceChangeEvent
47
+ * @returns true if the book was updated.
48
+ * @throws if the book is not found.
49
+ */
50
+ upsertPriceChange(event) {
51
+ // Iterate through price_changes array
52
+ for (const priceChange of event.price_changes) {
53
+ const book = this.bookCache[priceChange.asset_id];
54
+ if (!book) {
55
+ throw new Error(`Book not found for asset ${priceChange.asset_id}`);
56
+ }
57
+ const { price, size, side } = priceChange;
58
+ const sizeNum = parseFloat(size);
59
+ if (side === 'BUY') {
60
+ const i = book.bids.findIndex(bid => bid.price === price);
61
+ if (i !== -1) {
62
+ // Remove entry if size is zero or effectively zero
63
+ if (sizeNum === 0 || size === '0') {
64
+ book.bids.splice(i, 1);
65
+ }
66
+ else {
67
+ book.bids[i].size = size;
68
+ }
69
+ }
70
+ else if (sizeNum > 0) {
71
+ // Only add if size is non-zero
72
+ book.bids.push({ price, size });
73
+ // Ensure the bids are sorted ascending
74
+ sortAscendingInPlace(book.bids);
75
+ }
76
+ }
77
+ else {
78
+ const i = book.asks.findIndex(ask => ask.price === price);
79
+ if (i !== -1) {
80
+ // Remove entry if size is zero or effectively zero
81
+ if (sizeNum === 0 || size === '0') {
82
+ book.asks.splice(i, 1);
83
+ }
84
+ else {
85
+ book.asks[i].size = size;
86
+ }
87
+ }
88
+ else if (sizeNum > 0) {
89
+ // Only add if size is non-zero
90
+ book.asks.push({ price, size });
91
+ // Ensure the asks are sorted descending
92
+ sortDescendingInPlace(book.asks);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Side effect: updates the book's spread
99
+ *
100
+ * @returns `true` if best-bid/best-ask spread exceeds `cents`.
101
+ * @throws if either side of the book is empty.
102
+ */
103
+ spreadOver(assetId, cents = 0.1) {
104
+ const book = this.bookCache[assetId];
105
+ if (!book)
106
+ throw new Error(`Book for ${assetId} not cached`);
107
+ if (book.asks.length === 0)
108
+ throw new Error(`No asks in book for ${assetId}`);
109
+ if (book.bids.length === 0)
110
+ throw new Error(`No bids in book for ${assetId}`);
111
+ /*
112
+ * Polymarket book events are currently sorted as such:
113
+ * - bids ascending
114
+ * - asks descending
115
+ */
116
+ const highestBid = book.bids[book.bids.length - 1].price;
117
+ const lowestAsk = book.asks[book.asks.length - 1].price;
118
+ const highestBidNum = parseFloat(highestBid);
119
+ const lowestAskNum = parseFloat(lowestAsk);
120
+ const spread = lowestAskNum - highestBidNum;
121
+ if (isNaN(spread)) {
122
+ throw new Error(`Spread is NaN: lowestAsk '${lowestAsk}' highestBid '${highestBid}'`);
123
+ }
124
+ /*
125
+ * Update spead, 3 precision decimal places, trim trailing zeros
126
+ */
127
+ book.spread = parseFloat(spread.toFixed(3)).toString();
128
+ // Should be safe for 0.### - precision values
129
+ return spread > cents;
130
+ }
131
+ /**
132
+ * Calculate the midpoint of the book, rounded to 3dp, no trailing zeros
133
+ *
134
+ * Side effect: updates the book's midpoint
135
+ *
136
+ * Throws if
137
+ * - the book is not found or missing either bid or ask
138
+ * - the midpoint is NaN.
139
+ */
140
+ midpoint(assetId) {
141
+ const book = this.bookCache[assetId];
142
+ if (!book)
143
+ throw new Error(`Book for ${assetId} not cached`);
144
+ if (book.asks.length === 0)
145
+ throw new Error(`No asks in book for ${assetId}`);
146
+ if (book.bids.length === 0)
147
+ throw new Error(`No bids in book for ${assetId}`);
148
+ /*
149
+ * Polymarket book events are currently sorted as such:
150
+ * - bids ascending
151
+ * - asks descending
152
+ */
153
+ const highestBid = book.bids[book.bids.length - 1].price;
154
+ const lowestAsk = book.asks[book.asks.length - 1].price;
155
+ const highestBidNum = parseFloat(highestBid);
156
+ const lowestAskNum = parseFloat(lowestAsk);
157
+ const midpoint = (highestBidNum + lowestAskNum) / 2;
158
+ if (isNaN(midpoint)) {
159
+ throw new Error(`Midpoint is NaN: lowestAsk '${lowestAsk}' highestBid '${highestBid}'`);
160
+ }
161
+ /*
162
+ * Update midpoint, 3 precision decimal places, trim trailing zeros
163
+ */
164
+ book.midpoint = parseFloat(midpoint.toFixed(3)).toString();
165
+ return parseFloat(midpoint.toFixed(3)).toString();
166
+ }
167
+ /**
168
+ * Removes a specific market from the orderbook if assetId is provided
169
+ * otherwise clears all orderbook
170
+ * @param assetId tokenId of a market
171
+ */
172
+ clear(assetId) {
173
+ if (assetId) {
174
+ delete this.bookCache[assetId];
175
+ }
176
+ else {
177
+ for (const k of Object.keys(this.bookCache)) {
178
+ delete this.bookCache[k];
179
+ }
180
+ }
181
+ }
182
+ /**
183
+ * Get a book entry by asset id.
184
+ *
185
+ * @returns book entry if found, otherwise null
186
+ */
187
+ getBookEntry(assetId) {
188
+ if (!this.bookCache[assetId]) {
189
+ return null;
190
+ }
191
+ return this.bookCache[assetId];
192
+ }
193
+ }
194
+ exports.OrderBookCache = OrderBookCache;