pmxt-core 2.43.18 → 2.43.20
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/dist/exchanges/gemini-titan/websocket.js +16 -3
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/kalshi/fetcher.js +3 -3
- package/dist/exchanges/kalshi/websocket.d.ts +1 -0
- package/dist/exchanges/kalshi/websocket.js +29 -13
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/client.js +9 -0
- package/dist/exchanges/limitless/normalizer.js +4 -2
- package/dist/exchanges/limitless/utils.js +3 -0
- package/dist/exchanges/limitless/websocket.js +37 -7
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/fetcher.d.ts +1 -1
- package/dist/exchanges/myriad/normalizer.js +1 -1
- package/dist/exchanges/myriad/utils.js +1 -1
- package/dist/exchanges/myriad/websocket.js +16 -4
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/opinion/fetcher.js +1 -1
- package/dist/exchanges/opinion/websocket.js +35 -6
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/normalizer.js +6 -4
- package/dist/exchanges/polymarket/websocket.d.ts +2 -0
- package/dist/exchanges/polymarket/websocket.js +53 -27
- package/dist/exchanges/polymarket_us/websocket.js +6 -0
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/smarkets/fetcher.js +16 -6
- package/dist/feeds/binance/binance-feed.js +9 -0
- package/dist/feeds/chainlink/chainlink-feed.js +9 -0
- package/dist/server/index.js +6 -5
- package/dist/subscriber/watcher.js +6 -8
- package/dist/utils/market-utils.js +4 -4
- package/dist/utils/throttler.d.ts +2 -0
- package/dist/utils/throttler.js +8 -0
- package/package.json +3 -3
|
@@ -50,7 +50,10 @@ class GeminiWebSocket {
|
|
|
50
50
|
const headers = this.auth
|
|
51
51
|
? this.auth.buildWsHeaders()
|
|
52
52
|
: {};
|
|
53
|
-
this.ws = new ws_1.default(this.config.wsUrl, {
|
|
53
|
+
this.ws = new ws_1.default(this.config.wsUrl, {
|
|
54
|
+
headers,
|
|
55
|
+
handshakeTimeout: 30_000,
|
|
56
|
+
});
|
|
54
57
|
this.ws.on('open', () => {
|
|
55
58
|
this.isConnected = true;
|
|
56
59
|
this.isConnecting = false;
|
|
@@ -236,7 +239,12 @@ class GeminiWebSocket {
|
|
|
236
239
|
if (!this.orderBookResolvers.has(symbol)) {
|
|
237
240
|
this.orderBookResolvers.set(symbol, []);
|
|
238
241
|
}
|
|
239
|
-
this.orderBookResolvers.get(symbol)
|
|
242
|
+
const resolvers = this.orderBookResolvers.get(symbol);
|
|
243
|
+
if (!resolvers) {
|
|
244
|
+
reject(new Error(`[gemini-titan] resolver queue missing for ${symbol}`));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
resolvers.push({ resolve, reject });
|
|
240
248
|
});
|
|
241
249
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${symbol}')`);
|
|
242
250
|
}
|
|
@@ -257,7 +265,12 @@ class GeminiWebSocket {
|
|
|
257
265
|
if (!this.tradeResolvers.has(symbol)) {
|
|
258
266
|
this.tradeResolvers.set(symbol, []);
|
|
259
267
|
}
|
|
260
|
-
this.tradeResolvers.get(symbol)
|
|
268
|
+
const resolvers = this.tradeResolvers.get(symbol);
|
|
269
|
+
if (!resolvers) {
|
|
270
|
+
reject(new Error(`[gemini-titan] resolver queue missing for ${symbol}`));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
resolvers.push({ resolve, reject });
|
|
261
274
|
});
|
|
262
275
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${symbol}')`);
|
|
263
276
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.124Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const kalshiApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.kalshiApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.124Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.kalshiApiSpec = {
|
|
@@ -308,7 +308,7 @@ class KalshiFetcher {
|
|
|
308
308
|
const events = data.events || [];
|
|
309
309
|
if (events.length === 0)
|
|
310
310
|
break;
|
|
311
|
-
allEvents
|
|
311
|
+
allEvents.push(...events);
|
|
312
312
|
if (targetMarketCount) {
|
|
313
313
|
for (const event of events) {
|
|
314
314
|
totalMarketCount += (event.markets || []).length;
|
|
@@ -343,7 +343,7 @@ class KalshiFetcher {
|
|
|
343
343
|
const events = data.events || [];
|
|
344
344
|
if (events.length === 0)
|
|
345
345
|
break;
|
|
346
|
-
allEvents
|
|
346
|
+
allEvents.push(...events);
|
|
347
347
|
cursor = data.cursor;
|
|
348
348
|
page++;
|
|
349
349
|
} while (cursor && page < MAX_PAGES);
|
|
@@ -370,7 +370,7 @@ class KalshiFetcher {
|
|
|
370
370
|
page++;
|
|
371
371
|
if (events.length === 0)
|
|
372
372
|
break;
|
|
373
|
-
allEvents
|
|
373
|
+
allEvents.push(...events);
|
|
374
374
|
} while (cursor && allEvents.length < maxEvents && page < MAX_PAGES);
|
|
375
375
|
return {
|
|
376
376
|
events: allEvents.slice(0, maxEvents),
|
|
@@ -28,6 +28,7 @@ export declare class KalshiWebSocket {
|
|
|
28
28
|
private reconnectTimer?;
|
|
29
29
|
private connectionPromise?;
|
|
30
30
|
private isTerminated;
|
|
31
|
+
private static readonly CONNECTION_TIMEOUT_MS;
|
|
31
32
|
constructor(auth: KalshiAuth, config?: KalshiWebSocketConfig);
|
|
32
33
|
private connect;
|
|
33
34
|
private scheduleReconnect;
|
|
@@ -27,10 +27,14 @@ class KalshiWebSocket {
|
|
|
27
27
|
reconnectTimer;
|
|
28
28
|
connectionPromise;
|
|
29
29
|
isTerminated = false;
|
|
30
|
+
static CONNECTION_TIMEOUT_MS = 30_000;
|
|
30
31
|
constructor(auth, config = {}) {
|
|
31
32
|
this.auth = auth;
|
|
32
33
|
this.config = config;
|
|
33
|
-
|
|
34
|
+
if (!config.wsUrl) {
|
|
35
|
+
throw new Error('KalshiWebSocket: wsUrl is required in config');
|
|
36
|
+
}
|
|
37
|
+
this.wsUrl = config.wsUrl;
|
|
34
38
|
}
|
|
35
39
|
async connect() {
|
|
36
40
|
if (this.isConnected) {
|
|
@@ -52,7 +56,20 @@ class KalshiWebSocket {
|
|
|
52
56
|
// Get authentication headers
|
|
53
57
|
const headers = this.auth.getHeaders("GET", path);
|
|
54
58
|
this.ws = new ws_1.default(this.wsUrl, { headers });
|
|
59
|
+
// Connection timeout: close the socket if not connected within 30s
|
|
60
|
+
const connectionTimeout = setTimeout(() => {
|
|
61
|
+
if (!this.isConnected && this.ws) {
|
|
62
|
+
logger_1.logger.error("Kalshi WebSocket connection timed out", {
|
|
63
|
+
timeoutMs: KalshiWebSocket.CONNECTION_TIMEOUT_MS,
|
|
64
|
+
});
|
|
65
|
+
this.ws.close();
|
|
66
|
+
this.isConnecting = false;
|
|
67
|
+
this.connectionPromise = undefined;
|
|
68
|
+
reject(new Error(`Kalshi WebSocket connection timed out after ${KalshiWebSocket.CONNECTION_TIMEOUT_MS}ms`));
|
|
69
|
+
}
|
|
70
|
+
}, KalshiWebSocket.CONNECTION_TIMEOUT_MS);
|
|
55
71
|
this.ws.on("open", () => {
|
|
72
|
+
clearTimeout(connectionTimeout);
|
|
56
73
|
this.isConnected = true;
|
|
57
74
|
this.isConnecting = false;
|
|
58
75
|
this.connectionPromise = undefined;
|
|
@@ -76,12 +93,14 @@ class KalshiWebSocket {
|
|
|
76
93
|
}
|
|
77
94
|
});
|
|
78
95
|
this.ws.on("error", (error) => {
|
|
96
|
+
clearTimeout(connectionTimeout);
|
|
79
97
|
logger_1.logger.error("Kalshi WebSocket error", { error: String(error) });
|
|
80
98
|
this.isConnecting = false;
|
|
81
99
|
this.connectionPromise = undefined;
|
|
82
100
|
reject(error);
|
|
83
101
|
});
|
|
84
102
|
this.ws.on("close", () => {
|
|
103
|
+
clearTimeout(connectionTimeout);
|
|
85
104
|
if (!this.isTerminated) {
|
|
86
105
|
logger_1.logger.info("Kalshi WebSocket closed");
|
|
87
106
|
this.scheduleReconnect();
|
|
@@ -394,10 +413,9 @@ class KalshiWebSocket {
|
|
|
394
413
|
}
|
|
395
414
|
// Return a promise that resolves on the next orderbook update
|
|
396
415
|
const dataPromise = new Promise((resolve, reject) => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this.orderBookResolvers.get(ticker).push({ resolve, reject });
|
|
416
|
+
const resolvers = this.orderBookResolvers.get(ticker) ?? [];
|
|
417
|
+
resolvers.push({ resolve, reject });
|
|
418
|
+
this.orderBookResolvers.set(ticker, resolvers);
|
|
401
419
|
});
|
|
402
420
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${ticker}')`);
|
|
403
421
|
}
|
|
@@ -421,15 +439,14 @@ class KalshiWebSocket {
|
|
|
421
439
|
}
|
|
422
440
|
// Wait for all tickers to receive at least one snapshot/update
|
|
423
441
|
const dataPromise = Promise.all(tickers.map((ticker) => new Promise((resolve, reject) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
this.orderBookResolvers.get(ticker).push({
|
|
442
|
+
const resolvers = this.orderBookResolvers.get(ticker) ?? [];
|
|
443
|
+
resolvers.push({
|
|
428
444
|
resolve: (book) => {
|
|
429
445
|
Promise.resolve(book).then((b) => resolve([ticker, b]));
|
|
430
446
|
},
|
|
431
447
|
reject,
|
|
432
448
|
});
|
|
449
|
+
this.orderBookResolvers.set(ticker, resolvers);
|
|
433
450
|
})));
|
|
434
451
|
const entries = await (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBooks(${JSON.stringify(tickers)})`);
|
|
435
452
|
const result = {};
|
|
@@ -454,10 +471,9 @@ class KalshiWebSocket {
|
|
|
454
471
|
this.subscribeToTrades([ticker]);
|
|
455
472
|
}
|
|
456
473
|
const dataPromise = new Promise((resolve, reject) => {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.tradeResolvers.get(ticker).push({ resolve, reject });
|
|
474
|
+
const resolvers = this.tradeResolvers.get(ticker) ?? [];
|
|
475
|
+
resolvers.push({ resolve, reject });
|
|
476
|
+
this.tradeResolvers.set(ticker, resolvers);
|
|
461
477
|
});
|
|
462
478
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${ticker}')`);
|
|
463
479
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.172Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const limitlessApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.limitlessApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.172Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.limitlessApiSpec = {
|
|
@@ -242,12 +242,18 @@ class LimitlessClient {
|
|
|
242
242
|
* Cancel a specific order by ID.
|
|
243
243
|
*/
|
|
244
244
|
async cancelOrder(orderId) {
|
|
245
|
+
if (!this.orderClient) {
|
|
246
|
+
throw new Error('[limitless] Order client not initialized -- trading credentials required');
|
|
247
|
+
}
|
|
245
248
|
return await this.orderClient.cancel(orderId);
|
|
246
249
|
}
|
|
247
250
|
/**
|
|
248
251
|
* Cancel all orders for a specific market.
|
|
249
252
|
*/
|
|
250
253
|
async cancelAllOrders(marketSlug) {
|
|
254
|
+
if (!this.orderClient) {
|
|
255
|
+
throw new Error('[limitless] Order client not initialized -- trading credentials required');
|
|
256
|
+
}
|
|
251
257
|
return await this.orderClient.cancelAll(marketSlug);
|
|
252
258
|
}
|
|
253
259
|
/**
|
|
@@ -304,6 +310,9 @@ class LimitlessClient {
|
|
|
304
310
|
name: 'base',
|
|
305
311
|
});
|
|
306
312
|
const contract = new ethers_1.Contract(USDC_ADDRESS, ABI, provider);
|
|
313
|
+
if (!this.signer) {
|
|
314
|
+
throw new Error('[limitless] Signer not initialized -- wallet private key required');
|
|
315
|
+
}
|
|
307
316
|
const balance = await contract.balanceOf(this.signer.address);
|
|
308
317
|
const decimals = await contract.decimals(); // Should be 6
|
|
309
318
|
return parseFloat(ethers_1.utils.formatUnits(balance, decimals));
|
|
@@ -56,10 +56,12 @@ class LimitlessNormalizer {
|
|
|
56
56
|
};
|
|
57
57
|
}).sort((a, b) => a.timestamp - b.timestamp);
|
|
58
58
|
if (params.start) {
|
|
59
|
-
|
|
59
|
+
const start = params.start;
|
|
60
|
+
candles = candles.filter((c) => c.timestamp >= start.getTime());
|
|
60
61
|
}
|
|
61
62
|
if (params.end) {
|
|
62
|
-
|
|
63
|
+
const end = params.end;
|
|
64
|
+
candles = candles.filter((c) => c.timestamp <= end.getTime());
|
|
63
65
|
}
|
|
64
66
|
if (params.limit) {
|
|
65
67
|
candles = candles.slice(0, params.limit);
|
|
@@ -16,6 +16,9 @@ function mapMarketToUnified(market) {
|
|
|
16
16
|
// Use explicit key lookup — Object.entries order is not guaranteed to
|
|
17
17
|
// match the prices array.
|
|
18
18
|
if (market.tokens) {
|
|
19
|
+
if (!market.tokens.yes || !market.tokens.no) {
|
|
20
|
+
throw new Error(`[limitless] Market "${market.slug}" is missing token addresses`);
|
|
21
|
+
}
|
|
19
22
|
const prices = Array.isArray(market.prices) ? market.prices : [];
|
|
20
23
|
const yesPrice = prices[0] || 0;
|
|
21
24
|
const noPrice = prices[1] || 0;
|
|
@@ -81,7 +81,9 @@ class LimitlessWebSocket {
|
|
|
81
81
|
// 1. If we have buffered data, return it immediately
|
|
82
82
|
const buffer = this.orderbookBuffers.get(marketSlug);
|
|
83
83
|
if (buffer && buffer.length > 0) {
|
|
84
|
-
|
|
84
|
+
const entry = buffer.shift();
|
|
85
|
+
if (entry)
|
|
86
|
+
return entry;
|
|
85
87
|
}
|
|
86
88
|
// 2. Special case: If this is the FIRST call for this market and we have no data,
|
|
87
89
|
// fetch a snapshot to get things moving.
|
|
@@ -113,14 +115,30 @@ class LimitlessWebSocket {
|
|
|
113
115
|
}
|
|
114
116
|
// Wait for WebSocket update with timeout
|
|
115
117
|
try {
|
|
118
|
+
const resolverEntry = {
|
|
119
|
+
resolve: () => { },
|
|
120
|
+
reject: () => { },
|
|
121
|
+
};
|
|
116
122
|
const wsUpdatePromise = new Promise((resolve, reject) => {
|
|
123
|
+
resolverEntry.resolve = resolve;
|
|
124
|
+
resolverEntry.reject = reject;
|
|
117
125
|
if (!this.orderbookResolvers.has(marketSlug)) {
|
|
118
126
|
this.orderbookResolvers.set(marketSlug, []);
|
|
119
127
|
}
|
|
120
|
-
this.orderbookResolvers.get(marketSlug)
|
|
128
|
+
const resolvers = this.orderbookResolvers.get(marketSlug);
|
|
129
|
+
if (resolvers) {
|
|
130
|
+
resolvers.push(resolverEntry);
|
|
131
|
+
}
|
|
121
132
|
});
|
|
122
133
|
const timeoutPromise = new Promise((resolve) => {
|
|
123
134
|
setTimeout(async () => {
|
|
135
|
+
// Timeout won the race -- remove the stale resolver (#372)
|
|
136
|
+
const resolvers = this.orderbookResolvers.get(marketSlug);
|
|
137
|
+
if (resolvers) {
|
|
138
|
+
const idx = resolvers.indexOf(resolverEntry);
|
|
139
|
+
if (idx !== -1)
|
|
140
|
+
resolvers.splice(idx, 1);
|
|
141
|
+
}
|
|
124
142
|
// Timeout: fetch REST snapshot as fallback
|
|
125
143
|
try {
|
|
126
144
|
this.lastOrderbookTimestamps.set(marketSlug, Date.now());
|
|
@@ -222,6 +240,14 @@ class LimitlessWebSocket {
|
|
|
222
240
|
async close() {
|
|
223
241
|
this.orderbookCallbacks.clear();
|
|
224
242
|
this.priceCallbacks.clear();
|
|
243
|
+
// Reject any pending resolvers before clearing (#372)
|
|
244
|
+
for (const [, resolvers] of this.orderbookResolvers) {
|
|
245
|
+
for (const resolver of resolvers) {
|
|
246
|
+
resolver.reject(new Error('WebSocket closed'));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
this.orderbookResolvers.clear();
|
|
250
|
+
this.orderbookBuffers.clear();
|
|
225
251
|
await this.client.disconnect();
|
|
226
252
|
this.watcher.close();
|
|
227
253
|
}
|
|
@@ -254,7 +280,9 @@ class LimitlessWebSocket {
|
|
|
254
280
|
if (resolvers.length > 0) {
|
|
255
281
|
// If someone is waiting, give it to them immediately
|
|
256
282
|
const resolver = resolvers.shift();
|
|
257
|
-
resolver
|
|
283
|
+
if (resolver) {
|
|
284
|
+
resolver.resolve(pmxtOrderbook);
|
|
285
|
+
}
|
|
258
286
|
}
|
|
259
287
|
else {
|
|
260
288
|
// Otherwise, buffer it for the next call
|
|
@@ -262,10 +290,12 @@ class LimitlessWebSocket {
|
|
|
262
290
|
this.orderbookBuffers.set(marketSlug, []);
|
|
263
291
|
}
|
|
264
292
|
const buffer = this.orderbookBuffers.get(marketSlug);
|
|
265
|
-
buffer
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
buffer.
|
|
293
|
+
if (buffer) {
|
|
294
|
+
buffer.push(pmxtOrderbook);
|
|
295
|
+
// Keep buffer size reasonable
|
|
296
|
+
if (buffer.length > 100)
|
|
297
|
+
buffer.shift();
|
|
298
|
+
}
|
|
269
299
|
}
|
|
270
300
|
});
|
|
271
301
|
// Handle AMM price updates
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.185Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const myriadApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.myriadApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.185Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.myriadApiSpec = {
|
|
@@ -33,7 +33,7 @@ class MyriadNormalizer {
|
|
|
33
33
|
const status = typeof raw.state === 'string' ? (0, utils_1.mapMarketState)(raw.state) : undefined;
|
|
34
34
|
const um = {
|
|
35
35
|
marketId: `${raw.networkId}:${raw.id}`,
|
|
36
|
-
eventId: raw.
|
|
36
|
+
eventId: raw.eventId ? String(raw.eventId) : undefined,
|
|
37
37
|
title: raw.title || '',
|
|
38
38
|
description: raw.description || '',
|
|
39
39
|
outcomes,
|
|
@@ -65,7 +65,7 @@ function mapMarketToUnified(market) {
|
|
|
65
65
|
}));
|
|
66
66
|
const um = {
|
|
67
67
|
marketId: `${market.networkId}:${market.id}`,
|
|
68
|
-
eventId: market.
|
|
68
|
+
eventId: market.eventId ? String(market.eventId) : undefined,
|
|
69
69
|
title: market.title || '',
|
|
70
70
|
description: market.description || '',
|
|
71
71
|
outcomes,
|
|
@@ -34,8 +34,14 @@ class MyriadWebSocket {
|
|
|
34
34
|
this.orderBookResolvers.set(outcomeId, []);
|
|
35
35
|
this.orderBookRejecters.set(outcomeId, []);
|
|
36
36
|
}
|
|
37
|
-
this.orderBookResolvers.get(outcomeId)
|
|
38
|
-
this.orderBookRejecters.get(outcomeId)
|
|
37
|
+
const resolvers = this.orderBookResolvers.get(outcomeId);
|
|
38
|
+
const rejecters = this.orderBookRejecters.get(outcomeId);
|
|
39
|
+
if (!resolvers || !rejecters) {
|
|
40
|
+
reject(new Error(`Failed to initialize orderBook resolvers for outcomeId=${outcomeId}`));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
resolvers.push(resolve);
|
|
44
|
+
rejecters.push(reject);
|
|
39
45
|
if (!this.orderBookTimers.has(outcomeId)) {
|
|
40
46
|
this.startOrderBookPolling(outcomeId);
|
|
41
47
|
}
|
|
@@ -49,8 +55,14 @@ class MyriadWebSocket {
|
|
|
49
55
|
this.tradeResolvers.set(outcomeId, []);
|
|
50
56
|
this.tradeRejecters.set(outcomeId, []);
|
|
51
57
|
}
|
|
52
|
-
this.tradeResolvers.get(outcomeId)
|
|
53
|
-
this.tradeRejecters.get(outcomeId)
|
|
58
|
+
const resolvers = this.tradeResolvers.get(outcomeId);
|
|
59
|
+
const rejecters = this.tradeRejecters.get(outcomeId);
|
|
60
|
+
if (!resolvers || !rejecters) {
|
|
61
|
+
reject(new Error(`Failed to initialize trade resolvers for outcomeId=${outcomeId}`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
resolvers.push(resolve);
|
|
65
|
+
rejecters.push(reject);
|
|
54
66
|
if (!this.tradeTimers.has(outcomeId)) {
|
|
55
67
|
this.startTradePolling(outcomeId);
|
|
56
68
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.190Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const opinionApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.opinionApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.190Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.opinionApiSpec = {
|
|
@@ -264,7 +264,7 @@ class OpinionFetcher {
|
|
|
264
264
|
const list = extractList(result);
|
|
265
265
|
if (!list || list.length === 0)
|
|
266
266
|
break;
|
|
267
|
-
allItems
|
|
267
|
+
allItems.push(...list);
|
|
268
268
|
if (allItems.length >= total)
|
|
269
269
|
break;
|
|
270
270
|
if (targetCount !== undefined && allItems.length >= targetCount)
|
|
@@ -48,8 +48,27 @@ class OpinionWebSocket {
|
|
|
48
48
|
this.isConnecting = true;
|
|
49
49
|
this.connectionPromise = new Promise((resolve, reject) => {
|
|
50
50
|
try {
|
|
51
|
+
const CONNECTION_TIMEOUT_MS = 30_000;
|
|
52
|
+
let settled = false;
|
|
53
|
+
const connectionTimer = setTimeout(() => {
|
|
54
|
+
if (!settled) {
|
|
55
|
+
settled = true;
|
|
56
|
+
this.isConnecting = false;
|
|
57
|
+
this.connectionPromise = undefined;
|
|
58
|
+
logger_1.logger.error("Opinion WebSocket connection timed out", { timeoutMs: CONNECTION_TIMEOUT_MS });
|
|
59
|
+
if (this.ws) {
|
|
60
|
+
this.ws.terminate();
|
|
61
|
+
this.ws = undefined;
|
|
62
|
+
}
|
|
63
|
+
reject(new Error(`Opinion WebSocket connection timed out after ${CONNECTION_TIMEOUT_MS}ms`));
|
|
64
|
+
}
|
|
65
|
+
}, CONNECTION_TIMEOUT_MS);
|
|
51
66
|
this.ws = new ws_1.default(this.wsUrl);
|
|
52
67
|
this.ws.on("open", () => {
|
|
68
|
+
if (settled)
|
|
69
|
+
return;
|
|
70
|
+
settled = true;
|
|
71
|
+
clearTimeout(connectionTimer);
|
|
53
72
|
this.isConnected = true;
|
|
54
73
|
this.isConnecting = false;
|
|
55
74
|
this.connectionPromise = undefined;
|
|
@@ -67,6 +86,10 @@ class OpinionWebSocket {
|
|
|
67
86
|
}
|
|
68
87
|
});
|
|
69
88
|
this.ws.on("error", (error) => {
|
|
89
|
+
if (settled)
|
|
90
|
+
return;
|
|
91
|
+
settled = true;
|
|
92
|
+
clearTimeout(connectionTimer);
|
|
70
93
|
logger_1.logger.error("Opinion WebSocket error", { error: String(error) });
|
|
71
94
|
this.isConnecting = false;
|
|
72
95
|
this.connectionPromise = undefined;
|
|
@@ -265,10 +288,13 @@ class OpinionWebSocket {
|
|
|
265
288
|
this.sendSubscribe("market.depth.diff", marketId);
|
|
266
289
|
}
|
|
267
290
|
const dataPromise = new Promise((resolve, reject) => {
|
|
268
|
-
|
|
269
|
-
|
|
291
|
+
const existing = this.orderBookResolvers.get(marketId);
|
|
292
|
+
if (existing) {
|
|
293
|
+
existing.push({ resolve, reject });
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
this.orderBookResolvers.set(marketId, [{ resolve, reject }]);
|
|
270
297
|
}
|
|
271
|
-
this.orderBookResolvers.get(marketId).push({ resolve, reject });
|
|
272
298
|
});
|
|
273
299
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${marketId}')`);
|
|
274
300
|
}
|
|
@@ -292,10 +318,13 @@ class OpinionWebSocket {
|
|
|
292
318
|
this.sendSubscribe("market.last.trade", marketId);
|
|
293
319
|
}
|
|
294
320
|
const dataPromise = new Promise((resolve, reject) => {
|
|
295
|
-
|
|
296
|
-
|
|
321
|
+
const existing = this.tradeResolvers.get(marketId);
|
|
322
|
+
if (existing) {
|
|
323
|
+
existing.push({ resolve, reject });
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
this.tradeResolvers.set(marketId, [{ resolve, reject }]);
|
|
297
327
|
}
|
|
298
|
-
this.tradeResolvers.get(marketId).push({ resolve, reject });
|
|
299
328
|
});
|
|
300
329
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${marketId}')`);
|
|
301
330
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.132Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketClobSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketClobSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.132Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketClobSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.147Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketDataSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketDataSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.147Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketDataSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.144Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketGammaSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketGammaSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.144Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketGammaSpec = {
|
|
@@ -64,10 +64,12 @@ class PolymarketNormalizer {
|
|
|
64
64
|
}
|
|
65
65
|
else {
|
|
66
66
|
const candle = buckets.get(snappedMs);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
if (candle) {
|
|
68
|
+
candle.high = Math.max(candle.high, price);
|
|
69
|
+
candle.low = Math.min(candle.low, price);
|
|
70
|
+
candle.close = price;
|
|
71
|
+
candle.volume = (candle.volume || 0) + volume;
|
|
72
|
+
}
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
const candles = Array.from(buckets.values()).sort((a, b) => a.timestamp - b.timestamp);
|
|
@@ -47,6 +47,8 @@ export interface PolymarketWebSocketConfig {
|
|
|
47
47
|
watchTimeoutMs?: number;
|
|
48
48
|
/** API credentials for the authenticated user channel (fills/orders). */
|
|
49
49
|
userChannelCreds?: PolymarketUserChannelCreds;
|
|
50
|
+
/** Timeout in ms for WebSocket connections to open (default: 30000). */
|
|
51
|
+
connectionTimeoutMs?: number;
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Native WebSocket implementation for Polymarket market data.
|
|
@@ -45,6 +45,9 @@ const logger_1 = require("../../utils/logger");
|
|
|
45
45
|
const goldsky_1 = require("../../subscriber/external/goldsky");
|
|
46
46
|
const watcher_1 = require("../../subscriber/watcher");
|
|
47
47
|
const watch_timeout_1 = require("../../utils/watch-timeout");
|
|
48
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 30_000;
|
|
49
|
+
const MAX_PENDING_TRADES_PER_ASSET = 1000;
|
|
50
|
+
const MAX_USER_CALLBACKS = 100;
|
|
48
51
|
const POLYMARKET_MARKET_WS_URL = 'wss://ws-subscriptions-clob.polymarket.com/ws/market';
|
|
49
52
|
/**
|
|
50
53
|
* Native WebSocket implementation for Polymarket market data.
|
|
@@ -77,10 +80,8 @@ class PolymarketWebSocket {
|
|
|
77
80
|
await this.subscribe([outcomeId]);
|
|
78
81
|
// Return a promise that resolves on the next orderbook update
|
|
79
82
|
const dataPromise = new Promise((resolve, reject) => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
this.orderBookResolvers.get(outcomeId).push({ resolve, reject });
|
|
83
|
+
const existing = this.orderBookResolvers.get(outcomeId) ?? [];
|
|
84
|
+
this.orderBookResolvers.set(outcomeId, [...existing, { resolve, reject }]);
|
|
84
85
|
});
|
|
85
86
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${outcomeId}')`);
|
|
86
87
|
}
|
|
@@ -111,10 +112,8 @@ class PolymarketWebSocket {
|
|
|
111
112
|
}
|
|
112
113
|
// Otherwise wait for the next trade
|
|
113
114
|
const dataPromise = new Promise((resolve, reject) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
this.tradeResolvers.get(outcomeId).push({ resolve, reject });
|
|
115
|
+
const existing = this.tradeResolvers.get(outcomeId) ?? [];
|
|
116
|
+
this.tradeResolvers.set(outcomeId, [...existing, { resolve, reject }]);
|
|
118
117
|
});
|
|
119
118
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${outcomeId}')`);
|
|
120
119
|
}
|
|
@@ -144,7 +143,12 @@ class PolymarketWebSocket {
|
|
|
144
143
|
if (!creds) {
|
|
145
144
|
throw new Error('User channel requires API credentials. Pass userChannelCreds in PolymarketWebSocketConfig.');
|
|
146
145
|
}
|
|
147
|
-
this.userCallbacks.
|
|
146
|
+
if (!this.userCallbacks.includes(callback)) {
|
|
147
|
+
if (this.userCallbacks.length >= MAX_USER_CALLBACKS) {
|
|
148
|
+
throw new Error(`Maximum user callback limit (${MAX_USER_CALLBACKS}) reached. Call unwatchUserFills() to clear existing callbacks.`);
|
|
149
|
+
}
|
|
150
|
+
this.userCallbacks = [...this.userCallbacks, callback];
|
|
151
|
+
}
|
|
148
152
|
this.userConditionIds = [...new Set([...this.userConditionIds, ...conditionIds])];
|
|
149
153
|
if (this.userWs) {
|
|
150
154
|
// Already connected — just re-subscribe with updated condition IDs.
|
|
@@ -164,18 +168,33 @@ class PolymarketWebSocket {
|
|
|
164
168
|
async connectUserChannel(creds) {
|
|
165
169
|
const WebSocket = (await Promise.resolve().then(() => __importStar(require('ws')))).default;
|
|
166
170
|
const url = 'wss://ws-subscriptions-clob.polymarket.com/ws/user';
|
|
171
|
+
const timeoutMs = this.config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
167
172
|
this.userWs = new WebSocket(url);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
await new Promise((resolve, reject) => {
|
|
174
|
+
const timeout = setTimeout(() => {
|
|
175
|
+
this.userWs?.terminate();
|
|
176
|
+
this.userWs = null;
|
|
177
|
+
reject(new Error(`Polymarket user channel connection timed out after ${timeoutMs}ms`));
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
this.userWs.on('open', () => {
|
|
180
|
+
clearTimeout(timeout);
|
|
181
|
+
logger_1.logger.info('[polymarket-ws] user channel connected');
|
|
182
|
+
this.sendUserSubscription(creds);
|
|
183
|
+
// Ping every 10 seconds to keep the connection alive.
|
|
184
|
+
if (this.userPingInterval)
|
|
185
|
+
clearInterval(this.userPingInterval);
|
|
186
|
+
this.userPingInterval = setInterval(() => {
|
|
187
|
+
if (this.userWs?.readyState === WebSocket.OPEN) {
|
|
188
|
+
this.userWs.ping();
|
|
189
|
+
}
|
|
190
|
+
}, 10_000);
|
|
191
|
+
resolve();
|
|
192
|
+
});
|
|
193
|
+
this.userWs.on('error', (err) => {
|
|
194
|
+
clearTimeout(timeout);
|
|
195
|
+
logger_1.logger.error('[polymarket-ws] user channel error', { error: err.message });
|
|
196
|
+
reject(err);
|
|
197
|
+
});
|
|
179
198
|
});
|
|
180
199
|
this.userWs.on('message', (raw) => {
|
|
181
200
|
try {
|
|
@@ -200,9 +219,6 @@ class PolymarketWebSocket {
|
|
|
200
219
|
logger_1.logger.warn('[polymarket-ws] user channel disconnected, reconnecting in 5s');
|
|
201
220
|
this.scheduleUserReconnect(creds);
|
|
202
221
|
});
|
|
203
|
-
this.userWs.on('error', (err) => {
|
|
204
|
-
logger_1.logger.error('[polymarket-ws] user channel error', { error: err.message });
|
|
205
|
-
});
|
|
206
222
|
}
|
|
207
223
|
sendUserSubscription(creds) {
|
|
208
224
|
if (!this.userWs || this.userWs.readyState !== 1)
|
|
@@ -274,10 +290,18 @@ class PolymarketWebSocket {
|
|
|
274
290
|
async ensureInitialized() {
|
|
275
291
|
if (this.initializationPromise)
|
|
276
292
|
return this.initializationPromise;
|
|
293
|
+
const timeoutMs = this.config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
277
294
|
this.initializationPromise = new Promise((resolve, reject) => {
|
|
278
295
|
const WebSocket = require('ws');
|
|
279
296
|
this.ws = new WebSocket(POLYMARKET_MARKET_WS_URL);
|
|
297
|
+
const timeout = setTimeout(() => {
|
|
298
|
+
this.ws?.terminate();
|
|
299
|
+
this.ws = null;
|
|
300
|
+
this.initializationPromise = undefined;
|
|
301
|
+
reject(new Error(`Polymarket market channel connection timed out after ${timeoutMs}ms`));
|
|
302
|
+
}, timeoutMs);
|
|
280
303
|
this.ws.on('open', () => {
|
|
304
|
+
clearTimeout(timeout);
|
|
281
305
|
resolve();
|
|
282
306
|
});
|
|
283
307
|
this.ws.on('message', (raw) => {
|
|
@@ -301,6 +325,7 @@ class PolymarketWebSocket {
|
|
|
301
325
|
}
|
|
302
326
|
});
|
|
303
327
|
this.ws.on('error', (err) => {
|
|
328
|
+
clearTimeout(timeout);
|
|
304
329
|
logger_1.logger.error('[polymarket-ws] WebSocket error', { error: err.message });
|
|
305
330
|
reject(err);
|
|
306
331
|
});
|
|
@@ -384,10 +409,11 @@ class PolymarketWebSocket {
|
|
|
384
409
|
this.tradeResolvers.set(id, []);
|
|
385
410
|
}
|
|
386
411
|
else {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
412
|
+
const pending = this.pendingTrades.get(id) ?? [];
|
|
413
|
+
const updated = pending.length >= MAX_PENDING_TRADES_PER_ASSET
|
|
414
|
+
? [...pending.slice(1), trade]
|
|
415
|
+
: [...pending, trade];
|
|
416
|
+
this.pendingTrades.set(id, updated);
|
|
391
417
|
}
|
|
392
418
|
}
|
|
393
419
|
resolveOrderBook(id, orderBook) {
|
|
@@ -61,6 +61,9 @@ class PolymarketUSWebSocket {
|
|
|
61
61
|
const slug = slugFromId(outcomeId);
|
|
62
62
|
await this.ensureInitialized();
|
|
63
63
|
if (!this.bookSubscriptions.has(slug)) {
|
|
64
|
+
if (!this.socket) {
|
|
65
|
+
throw new Error('[polymarket_us] Socket not available after connect');
|
|
66
|
+
}
|
|
64
67
|
this.bookSubscriptions.add(slug);
|
|
65
68
|
this.socket.subscribeMarketData(`book:${slug}`, [slug]);
|
|
66
69
|
}
|
|
@@ -75,6 +78,9 @@ class PolymarketUSWebSocket {
|
|
|
75
78
|
const slug = slugFromId(outcomeId);
|
|
76
79
|
await this.ensureInitialized();
|
|
77
80
|
if (!this.tradeSubscriptions.has(slug)) {
|
|
81
|
+
if (!this.socket) {
|
|
82
|
+
throw new Error('[polymarket_us] Socket not available after connect');
|
|
83
|
+
}
|
|
78
84
|
this.tradeSubscriptions.add(slug);
|
|
79
85
|
this.socket.subscribeTrades(`trade:${slug}`, [slug]);
|
|
80
86
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T16:58:16.178Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const probableApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.probableApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T16:58:16.178Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.probableApiSpec = {
|
|
@@ -223,13 +223,23 @@ class SmarketsFetcher {
|
|
|
223
223
|
]);
|
|
224
224
|
const marketsByEvent = new Map();
|
|
225
225
|
for (const market of allMarkets) {
|
|
226
|
-
const existing = marketsByEvent.get(market.event_id)
|
|
227
|
-
|
|
226
|
+
const existing = marketsByEvent.get(market.event_id);
|
|
227
|
+
if (existing) {
|
|
228
|
+
existing.push(market);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
marketsByEvent.set(market.event_id, [market]);
|
|
232
|
+
}
|
|
228
233
|
}
|
|
229
234
|
const contractsByMarket = new Map();
|
|
230
235
|
for (const contract of allContracts) {
|
|
231
|
-
const existing = contractsByMarket.get(contract.market_id)
|
|
232
|
-
|
|
236
|
+
const existing = contractsByMarket.get(contract.market_id);
|
|
237
|
+
if (existing) {
|
|
238
|
+
existing.push(contract);
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
contractsByMarket.set(contract.market_id, [contract]);
|
|
242
|
+
}
|
|
233
243
|
}
|
|
234
244
|
const volumesByMarket = new Map();
|
|
235
245
|
for (const volume of fetchedVolumes) {
|
|
@@ -255,7 +265,7 @@ class SmarketsFetcher {
|
|
|
255
265
|
});
|
|
256
266
|
}
|
|
257
267
|
async fetchPaginatedEvents(queryParams, targetCount) {
|
|
258
|
-
|
|
268
|
+
const allEvents = [];
|
|
259
269
|
let lastId;
|
|
260
270
|
let page = 0;
|
|
261
271
|
do {
|
|
@@ -271,7 +281,7 @@ class SmarketsFetcher {
|
|
|
271
281
|
const events = data.events || [];
|
|
272
282
|
if (events.length === 0)
|
|
273
283
|
break;
|
|
274
|
-
allEvents
|
|
284
|
+
allEvents.push(...events);
|
|
275
285
|
// Check pagination: next_page is null when there are no more results
|
|
276
286
|
const nextPage = data.pagination?.next_page;
|
|
277
287
|
if (!nextPage)
|
|
@@ -129,7 +129,14 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
|
|
|
129
129
|
? `${this.wsUrl}?key=${this.apiKey}`
|
|
130
130
|
: this.wsUrl;
|
|
131
131
|
const ws = new ws_1.default(url);
|
|
132
|
+
const connectionTimeout = setTimeout(() => {
|
|
133
|
+
ws.close();
|
|
134
|
+
this.ws = null;
|
|
135
|
+
this.connectionPromise = null;
|
|
136
|
+
reject(new Error('BinanceFeed: WebSocket connection timed out (30s)'));
|
|
137
|
+
}, 30_000);
|
|
132
138
|
ws.on('open', () => {
|
|
139
|
+
clearTimeout(connectionTimeout);
|
|
133
140
|
this.ws = ws;
|
|
134
141
|
this.connectionPromise = null;
|
|
135
142
|
ws.send(JSON.stringify({ op: 'subscribe_all' }));
|
|
@@ -139,6 +146,7 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
|
|
|
139
146
|
this.handleMessage(data);
|
|
140
147
|
});
|
|
141
148
|
ws.on('close', () => {
|
|
149
|
+
clearTimeout(connectionTimeout);
|
|
142
150
|
this.ws = null;
|
|
143
151
|
this.connectionPromise = null;
|
|
144
152
|
if (!this.isTerminated) {
|
|
@@ -146,6 +154,7 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
|
|
|
146
154
|
}
|
|
147
155
|
});
|
|
148
156
|
ws.on('error', (err) => {
|
|
157
|
+
clearTimeout(connectionTimeout);
|
|
149
158
|
this.ws = null;
|
|
150
159
|
this.connectionPromise = null;
|
|
151
160
|
if (!this.isTerminated) {
|
|
@@ -184,7 +184,14 @@ class ChainlinkFeed extends base_feed_1.BaseDataFeed {
|
|
|
184
184
|
return new Promise((resolve, reject) => {
|
|
185
185
|
const url = `${this.wsUrl}?key=${this.wsApiKey}`;
|
|
186
186
|
const ws = new ws_1.default(url);
|
|
187
|
+
const connectionTimeout = setTimeout(() => {
|
|
188
|
+
ws.close();
|
|
189
|
+
this.ws = null;
|
|
190
|
+
this.connectionPromise = null;
|
|
191
|
+
reject(new Error('ChainlinkFeed: WebSocket connection timed out (30s)'));
|
|
192
|
+
}, 30_000);
|
|
187
193
|
ws.on('open', () => {
|
|
194
|
+
clearTimeout(connectionTimeout);
|
|
188
195
|
this.ws = ws;
|
|
189
196
|
this.connectionPromise = null;
|
|
190
197
|
ws.send(JSON.stringify({ op: 'subscribe_all' }));
|
|
@@ -194,12 +201,14 @@ class ChainlinkFeed extends base_feed_1.BaseDataFeed {
|
|
|
194
201
|
this.handleMessage(data);
|
|
195
202
|
});
|
|
196
203
|
ws.on('close', () => {
|
|
204
|
+
clearTimeout(connectionTimeout);
|
|
197
205
|
this.ws = null;
|
|
198
206
|
this.connectionPromise = null;
|
|
199
207
|
if (!this.isTerminated)
|
|
200
208
|
this.scheduleReconnect();
|
|
201
209
|
});
|
|
202
210
|
ws.on('error', (err) => {
|
|
211
|
+
clearTimeout(connectionTimeout);
|
|
203
212
|
this.ws = null;
|
|
204
213
|
this.connectionPromise = null;
|
|
205
214
|
if (!this.isTerminated)
|
package/dist/server/index.js
CHANGED
|
@@ -5,6 +5,7 @@ require("dotenv/config");
|
|
|
5
5
|
const app_1 = require("./app");
|
|
6
6
|
const port_manager_1 = require("./utils/port-manager");
|
|
7
7
|
const lock_file_1 = require("./utils/lock-file");
|
|
8
|
+
const logger_1 = require("../utils/logger");
|
|
8
9
|
const crypto_1 = require("crypto");
|
|
9
10
|
const crypto_2 = require("crypto");
|
|
10
11
|
const fs_1 = require("fs");
|
|
@@ -42,14 +43,14 @@ async function main() {
|
|
|
42
43
|
const lockFile = new lock_file_1.LockFile();
|
|
43
44
|
await lockFile.create(port, process.pid, accessToken, version);
|
|
44
45
|
const server = await (0, app_1.startServer)(port, accessToken);
|
|
45
|
-
|
|
46
|
+
logger_1.logger.info(`PMXT Sidecar Server v${version} running on http://localhost:${port}`);
|
|
46
47
|
if (version.includes('-dev.')) {
|
|
47
|
-
|
|
48
|
+
logger_1.logger.info('Running in Development Mode (auto-restart enabled)');
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
logger_1.logger.info(`Lock file created at ${lockFile.lockPath}`);
|
|
50
51
|
// Graceful shutdown
|
|
51
52
|
const shutdown = async () => {
|
|
52
|
-
|
|
53
|
+
logger_1.logger.info('Shutting down gracefully...');
|
|
53
54
|
server.close();
|
|
54
55
|
await lockFile.remove();
|
|
55
56
|
process.exit(0);
|
|
@@ -58,6 +59,6 @@ async function main() {
|
|
|
58
59
|
process.on('SIGINT', shutdown);
|
|
59
60
|
}
|
|
60
61
|
main().catch((error) => {
|
|
61
|
-
|
|
62
|
+
logger_1.logger.error('Failed to start server:', error);
|
|
62
63
|
process.exit(1);
|
|
63
64
|
});
|
|
@@ -43,17 +43,15 @@ class AddressWatcher {
|
|
|
43
43
|
if (assetId) {
|
|
44
44
|
const assetKey = `${key} ${assetId}`;
|
|
45
45
|
return new Promise((resolve, reject) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.assetIdResolvers.get(assetKey).push({ resolve, reject });
|
|
46
|
+
const list = this.assetIdResolvers.get(assetKey) ?? [];
|
|
47
|
+
list.push({ resolve, reject });
|
|
48
|
+
this.assetIdResolvers.set(assetKey, list);
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
51
|
return new Promise((resolve, reject) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.resolvers.get(key).push({ resolve, reject });
|
|
52
|
+
const list = this.resolvers.get(key) ?? [];
|
|
53
|
+
list.push({ resolve, reject });
|
|
54
|
+
this.resolvers.set(key, list);
|
|
57
55
|
});
|
|
58
56
|
}
|
|
59
57
|
/**
|
|
@@ -46,11 +46,11 @@ function addBinaryOutcomes(market) {
|
|
|
46
46
|
// those are meaningful labels for financial markets.
|
|
47
47
|
const yesLabel = market.yes?.label.toLowerCase();
|
|
48
48
|
const noLabel = market.no?.label.toLowerCase();
|
|
49
|
-
if (market.title && yesLabel === 'yes') {
|
|
50
|
-
market.yes
|
|
49
|
+
if (market.title && market.yes && yesLabel === 'yes') {
|
|
50
|
+
market.yes = { ...market.yes, label: market.title };
|
|
51
51
|
}
|
|
52
|
-
if (market.title && noLabel === 'no') {
|
|
53
|
-
market.no.label
|
|
52
|
+
if (market.title && market.no && noLabel === 'no') {
|
|
53
|
+
market.no = { ...market.no, label: `Not ${market.title}` };
|
|
54
54
|
}
|
|
55
55
|
market.up = market.yes;
|
|
56
56
|
market.down = market.no;
|
|
@@ -6,10 +6,12 @@ export declare class Throttler {
|
|
|
6
6
|
private refillRate;
|
|
7
7
|
private capacity;
|
|
8
8
|
private delay;
|
|
9
|
+
private maxQueueDepth;
|
|
9
10
|
constructor(config: {
|
|
10
11
|
refillRate: number;
|
|
11
12
|
capacity: number;
|
|
12
13
|
delay: number;
|
|
14
|
+
maxQueueDepth?: number;
|
|
13
15
|
});
|
|
14
16
|
throttle(cost?: number): Promise<void>;
|
|
15
17
|
private loop;
|
package/dist/utils/throttler.js
CHANGED
|
@@ -9,13 +9,21 @@ class Throttler {
|
|
|
9
9
|
refillRate;
|
|
10
10
|
capacity;
|
|
11
11
|
delay;
|
|
12
|
+
maxQueueDepth;
|
|
12
13
|
constructor(config) {
|
|
13
14
|
this.refillRate = config.refillRate;
|
|
14
15
|
this.capacity = config.capacity;
|
|
15
16
|
this.delay = config.delay;
|
|
17
|
+
this.maxQueueDepth = config.maxQueueDepth ?? 1000;
|
|
16
18
|
}
|
|
17
19
|
async throttle(cost = 1) {
|
|
18
20
|
return new Promise((resolve) => {
|
|
21
|
+
if (this.queue.length >= this.maxQueueDepth) {
|
|
22
|
+
const dropped = this.queue.shift();
|
|
23
|
+
if (dropped) {
|
|
24
|
+
dropped.resolve();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
this.queue.push({ resolve, cost });
|
|
20
28
|
if (!this.running) {
|
|
21
29
|
this.running = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "2.43.
|
|
3
|
+
"version": "2.43.20",
|
|
4
4
|
"description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"test": "jest -c jest.config.js",
|
|
30
30
|
"server": "tsx watch src/server/index.ts",
|
|
31
31
|
"server:prod": "node dist/server/index.js",
|
|
32
|
-
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.43.
|
|
33
|
-
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.43.
|
|
32
|
+
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.43.20,library=urllib3",
|
|
33
|
+
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.43.20,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
|
|
34
34
|
"fetch:openapi": "node scripts/fetch-openapi-specs.js",
|
|
35
35
|
"extract:jsdoc": "node ../scripts/extract-jsdoc.js",
|
|
36
36
|
"generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",
|