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.
Files changed (43) hide show
  1. package/dist/exchanges/gemini-titan/websocket.js +16 -3
  2. package/dist/exchanges/kalshi/api.d.ts +1 -1
  3. package/dist/exchanges/kalshi/api.js +1 -1
  4. package/dist/exchanges/kalshi/fetcher.js +3 -3
  5. package/dist/exchanges/kalshi/websocket.d.ts +1 -0
  6. package/dist/exchanges/kalshi/websocket.js +29 -13
  7. package/dist/exchanges/limitless/api.d.ts +1 -1
  8. package/dist/exchanges/limitless/api.js +1 -1
  9. package/dist/exchanges/limitless/client.js +9 -0
  10. package/dist/exchanges/limitless/normalizer.js +4 -2
  11. package/dist/exchanges/limitless/utils.js +3 -0
  12. package/dist/exchanges/limitless/websocket.js +37 -7
  13. package/dist/exchanges/myriad/api.d.ts +1 -1
  14. package/dist/exchanges/myriad/api.js +1 -1
  15. package/dist/exchanges/myriad/fetcher.d.ts +1 -1
  16. package/dist/exchanges/myriad/normalizer.js +1 -1
  17. package/dist/exchanges/myriad/utils.js +1 -1
  18. package/dist/exchanges/myriad/websocket.js +16 -4
  19. package/dist/exchanges/opinion/api.d.ts +1 -1
  20. package/dist/exchanges/opinion/api.js +1 -1
  21. package/dist/exchanges/opinion/fetcher.js +1 -1
  22. package/dist/exchanges/opinion/websocket.js +35 -6
  23. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  24. package/dist/exchanges/polymarket/api-clob.js +1 -1
  25. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  26. package/dist/exchanges/polymarket/api-data.js +1 -1
  27. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  28. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  29. package/dist/exchanges/polymarket/normalizer.js +6 -4
  30. package/dist/exchanges/polymarket/websocket.d.ts +2 -0
  31. package/dist/exchanges/polymarket/websocket.js +53 -27
  32. package/dist/exchanges/polymarket_us/websocket.js +6 -0
  33. package/dist/exchanges/probable/api.d.ts +1 -1
  34. package/dist/exchanges/probable/api.js +1 -1
  35. package/dist/exchanges/smarkets/fetcher.js +16 -6
  36. package/dist/feeds/binance/binance-feed.js +9 -0
  37. package/dist/feeds/chainlink/chainlink-feed.js +9 -0
  38. package/dist/server/index.js +6 -5
  39. package/dist/subscriber/watcher.js +6 -8
  40. package/dist/utils/market-utils.js +4 -4
  41. package/dist/utils/throttler.d.ts +2 -0
  42. package/dist/utils/throttler.js +8 -0
  43. 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, { headers });
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).push({ resolve, reject });
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).push({ resolve, reject });
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-24T15:43:12.265Z
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-24T15:43:12.265Z
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 = allEvents.concat(events);
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 = allEvents.concat(events);
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 = allEvents.concat(events);
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
- this.wsUrl = config.wsUrl; // wsUrl must be provided by caller (from KalshiExchange)
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
- if (!this.orderBookResolvers.has(ticker)) {
398
- this.orderBookResolvers.set(ticker, []);
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
- if (!this.orderBookResolvers.has(ticker)) {
425
- this.orderBookResolvers.set(ticker, []);
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
- if (!this.tradeResolvers.has(ticker)) {
458
- this.tradeResolvers.set(ticker, []);
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-24T15:43:12.309Z
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-24T15:43:12.309Z
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
- candles = candles.filter((c) => c.timestamp >= params.start.getTime());
59
+ const start = params.start;
60
+ candles = candles.filter((c) => c.timestamp >= start.getTime());
60
61
  }
61
62
  if (params.end) {
62
- candles = candles.filter((c) => c.timestamp <= params.end.getTime());
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
- return buffer.shift();
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).push({ resolve, reject });
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.resolve(pmxtOrderbook);
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.push(pmxtOrderbook);
266
- // Keep buffer size reasonable
267
- if (buffer.length > 100)
268
- buffer.shift();
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-24T15:43:12.320Z
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-24T15:43:12.320Z
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 = {
@@ -11,7 +11,7 @@ export interface MyriadRawMarket {
11
11
  volume24h?: number;
12
12
  volume?: number;
13
13
  liquidity?: number;
14
- questionId?: number;
14
+ eventId?: number;
15
15
  topics?: string[];
16
16
  outcomes?: MyriadRawOutcome[];
17
17
  [key: string]: unknown;
@@ -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.questionId ? String(raw.questionId) : undefined,
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.questionId ? String(market.questionId) : undefined,
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).push(resolve);
38
- this.orderBookRejecters.get(outcomeId).push(reject);
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).push(resolve);
53
- this.tradeRejecters.get(outcomeId).push(reject);
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-24T15:43:12.325Z
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-24T15:43:12.325Z
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 = [...allItems, ...list];
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
- if (!this.orderBookResolvers.has(marketId)) {
269
- this.orderBookResolvers.set(marketId, []);
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
- if (!this.tradeResolvers.has(marketId)) {
296
- this.tradeResolvers.set(marketId, []);
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-24T15:43:12.271Z
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-24T15:43:12.271Z
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-24T15:43:12.287Z
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-24T15:43:12.287Z
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-24T15:43:12.284Z
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-24T15:43:12.284Z
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
- candle.high = Math.max(candle.high, price);
68
- candle.low = Math.min(candle.low, price);
69
- candle.close = price;
70
- candle.volume = (candle.volume || 0) + volume;
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
- if (!this.orderBookResolvers.has(outcomeId)) {
81
- this.orderBookResolvers.set(outcomeId, []);
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
- if (!this.tradeResolvers.has(outcomeId)) {
115
- this.tradeResolvers.set(outcomeId, []);
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.push(callback);
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
- this.userWs.on('open', () => {
169
- logger_1.logger.debug('[polymarket-ws] user channel connected');
170
- this.sendUserSubscription(creds);
171
- // Ping every 10 seconds to keep the connection alive.
172
- if (this.userPingInterval)
173
- clearInterval(this.userPingInterval);
174
- this.userPingInterval = setInterval(() => {
175
- if (this.userWs?.readyState === WebSocket.OPEN) {
176
- this.userWs.ping();
177
- }
178
- }, 10_000);
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
- if (!this.pendingTrades.has(id)) {
388
- this.pendingTrades.set(id, []);
389
- }
390
- this.pendingTrades.get(id).push(trade);
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-24T15:43:12.313Z
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-24T15:43:12.313Z
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
- marketsByEvent.set(market.event_id, [...existing, market]);
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
- contractsByMarket.set(contract.market_id, [...existing, contract]);
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
- let allEvents = [];
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 = [...allEvents, ...events];
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)
@@ -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
- console.log(`PMXT Sidecar Server v${version} running on http://localhost:${port}`);
46
+ logger_1.logger.info(`PMXT Sidecar Server v${version} running on http://localhost:${port}`);
46
47
  if (version.includes('-dev.')) {
47
- console.log('Running in Development Mode (auto-restart enabled)');
48
+ logger_1.logger.info('Running in Development Mode (auto-restart enabled)');
48
49
  }
49
- console.log(`Lock file created at ${lockFile.lockPath}`);
50
+ logger_1.logger.info(`Lock file created at ${lockFile.lockPath}`);
50
51
  // Graceful shutdown
51
52
  const shutdown = async () => {
52
- console.log('\nShutting down gracefully...');
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
- console.error('Failed to start server:', error);
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
- if (!this.assetIdResolvers.has(assetKey)) {
47
- this.assetIdResolvers.set(assetKey, []);
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
- if (!this.resolvers.has(key)) {
54
- this.resolvers.set(key, []);
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.label = market.title;
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 = `Not ${market.title}`;
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;
@@ -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.18",
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.18,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.18,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.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.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",