pmxt-core 2.43.19 → 2.43.24

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 (88) hide show
  1. package/dist/exchanges/baozi/errors.d.ts +1 -1
  2. package/dist/exchanges/baozi/errors.js +1 -1
  3. package/dist/exchanges/gemini-titan/errors.d.ts +3 -3
  4. package/dist/exchanges/gemini-titan/errors.js +1 -1
  5. package/dist/exchanges/gemini-titan/index.js +1 -1
  6. package/dist/exchanges/gemini-titan/normalizer.js +2 -2
  7. package/dist/exchanges/gemini-titan/websocket.js +16 -3
  8. package/dist/exchanges/hyperliquid/errors.d.ts +3 -3
  9. package/dist/exchanges/hyperliquid/errors.js +1 -1
  10. package/dist/exchanges/hyperliquid/index.js +1 -1
  11. package/dist/exchanges/kalshi/api.d.ts +1 -1
  12. package/dist/exchanges/kalshi/api.js +1 -1
  13. package/dist/exchanges/kalshi/auth.js +3 -0
  14. package/dist/exchanges/kalshi/errors.d.ts +2 -2
  15. package/dist/exchanges/kalshi/fetcher.d.ts +1 -0
  16. package/dist/exchanges/kalshi/fetcher.js +3 -3
  17. package/dist/exchanges/kalshi/index.js +1 -1
  18. package/dist/exchanges/kalshi/normalizer.d.ts +1 -1
  19. package/dist/exchanges/kalshi/normalizer.js +4 -4
  20. package/dist/exchanges/kalshi/websocket.d.ts +1 -0
  21. package/dist/exchanges/kalshi/websocket.js +29 -13
  22. package/dist/exchanges/limitless/api.d.ts +1 -1
  23. package/dist/exchanges/limitless/api.js +1 -1
  24. package/dist/exchanges/limitless/auth.js +3 -0
  25. package/dist/exchanges/limitless/client.js +9 -0
  26. package/dist/exchanges/limitless/errors.d.ts +2 -2
  27. package/dist/exchanges/limitless/index.js +1 -1
  28. package/dist/exchanges/limitless/normalizer.js +4 -2
  29. package/dist/exchanges/limitless/utils.js +3 -0
  30. package/dist/exchanges/limitless/websocket.js +37 -7
  31. package/dist/exchanges/metaculus/cancelOrder.d.ts +1 -1
  32. package/dist/exchanges/metaculus/cancelOrder.js +3 -3
  33. package/dist/exchanges/metaculus/errors.d.ts +3 -3
  34. package/dist/exchanges/mock/index.js +32 -15
  35. package/dist/exchanges/myriad/api.d.ts +1 -1
  36. package/dist/exchanges/myriad/api.js +1 -1
  37. package/dist/exchanges/myriad/errors.d.ts +2 -2
  38. package/dist/exchanges/myriad/websocket.js +16 -4
  39. package/dist/exchanges/opinion/api.d.ts +1 -1
  40. package/dist/exchanges/opinion/api.js +1 -1
  41. package/dist/exchanges/opinion/errors.d.ts +2 -2
  42. package/dist/exchanges/opinion/errors.js +4 -3
  43. package/dist/exchanges/opinion/fetcher.js +1 -1
  44. package/dist/exchanges/opinion/index.js +1 -1
  45. package/dist/exchanges/opinion/utils.d.ts +1 -1
  46. package/dist/exchanges/opinion/utils.js +2 -2
  47. package/dist/exchanges/opinion/websocket.js +35 -6
  48. package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
  49. package/dist/exchanges/polymarket/api-clob.js +1 -1
  50. package/dist/exchanges/polymarket/api-data.d.ts +1 -1
  51. package/dist/exchanges/polymarket/api-data.js +1 -1
  52. package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
  53. package/dist/exchanges/polymarket/api-gamma.js +1 -1
  54. package/dist/exchanges/polymarket/auth.js +22 -3
  55. package/dist/exchanges/polymarket/errors.d.ts +3 -3
  56. package/dist/exchanges/polymarket/fetcher.js +3 -0
  57. package/dist/exchanges/polymarket/index.js +1 -1
  58. package/dist/exchanges/polymarket/normalizer.js +6 -4
  59. package/dist/exchanges/polymarket/websocket.d.ts +2 -0
  60. package/dist/exchanges/polymarket/websocket.js +53 -27
  61. package/dist/exchanges/polymarket_us/normalizer.js +3 -3
  62. package/dist/exchanges/polymarket_us/websocket.js +6 -0
  63. package/dist/exchanges/probable/api.d.ts +1 -1
  64. package/dist/exchanges/probable/api.js +1 -1
  65. package/dist/exchanges/probable/errors.d.ts +2 -2
  66. package/dist/exchanges/probable/errors.js +1 -1
  67. package/dist/exchanges/probable/index.js +2 -2
  68. package/dist/exchanges/smarkets/auth.js +6 -0
  69. package/dist/exchanges/smarkets/errors.d.ts +3 -3
  70. package/dist/exchanges/smarkets/errors.js +7 -2
  71. package/dist/exchanges/smarkets/fetcher.js +16 -6
  72. package/dist/feeds/binance/binance-feed.js +20 -2
  73. package/dist/feeds/chainlink/chainlink-feed.js +18 -3
  74. package/dist/router/Router.js +1 -3
  75. package/dist/router/client.d.ts +16 -8
  76. package/dist/router/client.js +7 -3
  77. package/dist/server/index.js +6 -5
  78. package/dist/server/openapi.yaml +1 -1
  79. package/dist/subscriber/external/goldsky.d.ts +2 -1
  80. package/dist/subscriber/external/goldsky.js +33 -14
  81. package/dist/subscriber/watcher.js +6 -8
  82. package/dist/types.d.ts +1 -1
  83. package/dist/utils/error-mapper.d.ts +7 -7
  84. package/dist/utils/error-mapper.js +54 -47
  85. package/dist/utils/market-utils.js +4 -4
  86. package/dist/utils/throttler.d.ts +2 -0
  87. package/dist/utils/throttler.js +8 -0
  88. package/package.json +3 -3
@@ -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) {
@@ -154,15 +154,15 @@ function intentToOutcomeId(intent, slug) {
154
154
  function mapOrderType(type) {
155
155
  return type === 'ORDER_TYPE_MARKET' ? 'market' : 'limit';
156
156
  }
157
- // PMXT Order.status values: 'pending' | 'open' | 'filled' | 'cancelled' | 'rejected'
158
- // Note: PMXT has no 'expired' status; expired orders are mapped to 'cancelled'.
157
+ // PMXT Order.status values: 'pending' | 'open' | 'filled' | 'canceled' | 'rejected'
158
+ // Note: PMXT has no 'expired' status; expired orders are mapped to 'canceled'.
159
159
  function mapOrderStatus(state) {
160
160
  switch (state) {
161
161
  case 'ORDER_STATE_FILLED':
162
162
  return 'filled';
163
163
  case 'ORDER_STATE_CANCELED':
164
164
  case 'ORDER_STATE_EXPIRED':
165
- return 'cancelled';
165
+ return 'canceled';
166
166
  case 'ORDER_STATE_REJECTED':
167
167
  return 'rejected';
168
168
  case 'ORDER_STATE_NEW':
@@ -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-24T16:33:23.414Z
3
+ * Generated at: 2026-05-24T18:05:59.975Z
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-24T16:33:23.414Z
6
+ * Generated at: 2026-05-24T18:05:59.975Z
7
7
  * Do not edit manually -- run "npm run fetch:openapi" to regenerate.
8
8
  */
9
9
  exports.probableApiSpec = {
@@ -2,7 +2,7 @@ import { ErrorMapper } from '../../utils/error-mapper';
2
2
  import { BadRequest } from '../../errors';
3
3
  export declare class ProbableErrorMapper extends ErrorMapper {
4
4
  constructor();
5
- protected extractErrorMessage(error: any): string;
6
- protected mapBadRequestError(message: string, data: any): BadRequest;
5
+ protected extractErrorMessage(error: unknown): string;
6
+ protected mapBadRequestError(message: string, data: unknown): BadRequest;
7
7
  }
8
8
  export declare const probableErrorMapper: ProbableErrorMapper;
@@ -25,7 +25,7 @@ class ProbableErrorMapper extends error_mapper_1.ErrorMapper {
25
25
  }
26
26
  }
27
27
  // Handle @prob/clob SDK error objects
28
- if (error && typeof error === 'object' && error.msg) {
28
+ if (typeof error === 'object' && error !== null && 'msg' in error) {
29
29
  return String(error.msg);
30
30
  }
31
31
  return super.extractErrorMessage(error);
@@ -229,7 +229,7 @@ class ProbableExchange extends BaseExchange_1.PredictionMarketExchange {
229
229
  side: 'buy',
230
230
  type: 'limit',
231
231
  amount: 0,
232
- status: 'cancelled',
232
+ status: 'canceled',
233
233
  filled: 0,
234
234
  remaining: 0,
235
235
  timestamp: Date.now(),
@@ -402,7 +402,7 @@ function mapOrderStatus(status) {
402
402
  if (lower === 'filled' || lower === 'trade')
403
403
  return 'filled';
404
404
  if (lower === 'canceled' || lower === 'cancelled' || lower === 'expired')
405
- return 'cancelled';
405
+ return 'canceled';
406
406
  if (lower === 'rejected')
407
407
  return 'rejected';
408
408
  return 'open';
@@ -37,12 +37,18 @@ class SmarketsAuth {
37
37
  * Returns the username (email) used for session creation.
38
38
  */
39
39
  getUsername() {
40
+ if (!this.credentials.apiKey) {
41
+ throw new Error('[smarkets] apiKey (username) is required');
42
+ }
40
43
  return this.credentials.apiKey;
41
44
  }
42
45
  /**
43
46
  * Returns the password used for session creation.
44
47
  */
45
48
  getPassword() {
49
+ if (!this.credentials.privateKey) {
50
+ throw new Error('[smarkets] privateKey (password) is required');
51
+ }
46
52
  return this.credentials.privateKey;
47
53
  }
48
54
  /**
@@ -11,16 +11,16 @@ export declare class SmarketsErrorMapper extends ErrorMapper {
11
11
  /**
12
12
  * Override to handle the Smarkets { error_type, data } format
13
13
  */
14
- protected extractErrorMessage(error: any): string;
14
+ protected extractErrorMessage(error: unknown): string;
15
15
  /**
16
16
  * Override to map Smarkets error_type values before falling back
17
17
  * to the default status-code-based mapping
18
18
  */
19
- mapError(error: any): ReturnType<ErrorMapper['mapError']>;
19
+ mapError(error: unknown): ReturnType<ErrorMapper['mapError']>;
20
20
  /**
21
21
  * Override to detect order-specific errors within 400 responses
22
22
  */
23
- protected mapBadRequestError(message: string, data: any): BadRequest;
23
+ protected mapBadRequestError(message: string, data: unknown): BadRequest;
24
24
  /**
25
25
  * Maps a Smarkets error_type string to a PMXT error class.
26
26
  * Returns undefined if the error_type is not recognized, allowing
@@ -123,7 +123,9 @@ class SmarketsErrorMapper extends error_mapper_1.ErrorMapper {
123
123
  * Override to detect order-specific errors within 400 responses
124
124
  */
125
125
  mapBadRequestError(message, data) {
126
- const errorType = data?.error_type;
126
+ const errorType = typeof data === 'object' && data !== null && 'error_type' in data
127
+ ? String(data.error_type)
128
+ : undefined;
127
129
  if (errorType) {
128
130
  if (INSUFFICIENT_FUNDS_ERRORS.has(errorType)) {
129
131
  return new errors_1.InsufficientFunds(message, this.exchangeName);
@@ -150,7 +152,10 @@ class SmarketsErrorMapper extends error_mapper_1.ErrorMapper {
150
152
  return new errors_1.AuthenticationError(message, this.exchangeName);
151
153
  }
152
154
  if (RATE_LIMIT_ERRORS.has(errorType)) {
153
- const retryAfter = response?.headers?.['retry-after'];
155
+ const headers = (typeof response === 'object' && response !== null && 'headers' in response
156
+ ? response.headers
157
+ : undefined);
158
+ const retryAfter = headers?.['retry-after'];
154
159
  const retryAfterSeconds = retryAfter
155
160
  ? parseInt(retryAfter, 10)
156
161
  : undefined;
@@ -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)
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.BinanceFeed = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
+ const logger_1 = require("../../utils/logger");
8
9
  const base_feed_1 = require("../base-feed");
9
10
  const types_1 = require("./types");
10
11
  const normalizer_1 = require("./normalizer");
@@ -106,7 +107,11 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
106
107
  watchTickerImpl(symbol, callback) {
107
108
  const sub = { symbol, callback };
108
109
  this.subscriptions = [...this.subscriptions, sub];
109
- this.ensureConnected();
110
+ this.ensureConnected().catch((err) => {
111
+ logger_1.logger.error('[BinanceFeed] initial connect failed in watchTickerImpl', {
112
+ error: err instanceof Error ? err.message : String(err),
113
+ });
114
+ });
110
115
  return () => {
111
116
  this.subscriptions = this.subscriptions.filter((s) => s !== sub);
112
117
  };
@@ -129,7 +134,14 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
129
134
  ? `${this.wsUrl}?key=${this.apiKey}`
130
135
  : this.wsUrl;
131
136
  const ws = new ws_1.default(url);
137
+ const connectionTimeout = setTimeout(() => {
138
+ ws.close();
139
+ this.ws = null;
140
+ this.connectionPromise = null;
141
+ reject(new Error('BinanceFeed: WebSocket connection timed out (30s)'));
142
+ }, 30_000);
132
143
  ws.on('open', () => {
144
+ clearTimeout(connectionTimeout);
133
145
  this.ws = ws;
134
146
  this.connectionPromise = null;
135
147
  ws.send(JSON.stringify({ op: 'subscribe_all' }));
@@ -139,6 +151,7 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
139
151
  this.handleMessage(data);
140
152
  });
141
153
  ws.on('close', () => {
154
+ clearTimeout(connectionTimeout);
142
155
  this.ws = null;
143
156
  this.connectionPromise = null;
144
157
  if (!this.isTerminated) {
@@ -146,6 +159,7 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
146
159
  }
147
160
  });
148
161
  ws.on('error', (err) => {
162
+ clearTimeout(connectionTimeout);
149
163
  this.ws = null;
150
164
  this.connectionPromise = null;
151
165
  if (!this.isTerminated) {
@@ -181,7 +195,11 @@ class BinanceFeed extends base_feed_1.BaseDataFeed {
181
195
  this.reconnectTimer = setTimeout(() => {
182
196
  this.reconnectTimer = null;
183
197
  if (!this.isTerminated) {
184
- this.connect();
198
+ this.connect().catch((err) => {
199
+ logger_1.logger.error('[BinanceFeed] reconnect failed', {
200
+ error: err instanceof Error ? err.message : String(err),
201
+ });
202
+ });
185
203
  }
186
204
  }, this.reconnectIntervalMs);
187
205
  }
@@ -7,6 +7,7 @@ exports.ChainlinkFeed = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
8
  const axios_1 = __importDefault(require("axios"));
9
9
  const base_feed_1 = require("../base-feed");
10
+ const logger_1 = require("../../utils/logger");
10
11
  const types_1 = require("./types");
11
12
  const normalizer_1 = require("./normalizer");
12
13
  class ChainlinkFeed extends base_feed_1.BaseDataFeed {
@@ -123,7 +124,9 @@ class ChainlinkFeed extends base_feed_1.BaseDataFeed {
123
124
  watchTickerImpl(symbol, callback) {
124
125
  const sub = { symbol: symbol.toUpperCase(), callback };
125
126
  this.subscriptions = [...this.subscriptions, sub];
126
- this.ensureConnected();
127
+ this.ensureConnected().catch((err) => {
128
+ logger_1.logger.error('[ChainlinkFeed] initial connect failed in watchTickerImpl', { error: err instanceof Error ? err.message : String(err) });
129
+ });
127
130
  return () => {
128
131
  this.subscriptions = this.subscriptions.filter((s) => s !== sub);
129
132
  };
@@ -184,7 +187,14 @@ class ChainlinkFeed extends base_feed_1.BaseDataFeed {
184
187
  return new Promise((resolve, reject) => {
185
188
  const url = `${this.wsUrl}?key=${this.wsApiKey}`;
186
189
  const ws = new ws_1.default(url);
190
+ const connectionTimeout = setTimeout(() => {
191
+ ws.close();
192
+ this.ws = null;
193
+ this.connectionPromise = null;
194
+ reject(new Error('ChainlinkFeed: WebSocket connection timed out (30s)'));
195
+ }, 30_000);
187
196
  ws.on('open', () => {
197
+ clearTimeout(connectionTimeout);
188
198
  this.ws = ws;
189
199
  this.connectionPromise = null;
190
200
  ws.send(JSON.stringify({ op: 'subscribe_all' }));
@@ -194,12 +204,14 @@ class ChainlinkFeed extends base_feed_1.BaseDataFeed {
194
204
  this.handleMessage(data);
195
205
  });
196
206
  ws.on('close', () => {
207
+ clearTimeout(connectionTimeout);
197
208
  this.ws = null;
198
209
  this.connectionPromise = null;
199
210
  if (!this.isTerminated)
200
211
  this.scheduleReconnect();
201
212
  });
202
213
  ws.on('error', (err) => {
214
+ clearTimeout(connectionTimeout);
203
215
  this.ws = null;
204
216
  this.connectionPromise = null;
205
217
  if (!this.isTerminated)
@@ -233,8 +245,11 @@ class ChainlinkFeed extends base_feed_1.BaseDataFeed {
233
245
  return;
234
246
  this.reconnectTimer = setTimeout(() => {
235
247
  this.reconnectTimer = null;
236
- if (!this.isTerminated)
237
- this.connect();
248
+ if (!this.isTerminated) {
249
+ this.connect().catch((err) => {
250
+ logger_1.logger.error('[ChainlinkFeed] reconnect failed', { error: err instanceof Error ? err.message : String(err) });
251
+ });
252
+ }
238
253
  }, this.reconnectIntervalMs);
239
254
  }
240
255
  }
@@ -308,9 +308,7 @@ class Router extends BaseExchange_1.PredictionMarketExchange {
308
308
  query.category = params.category;
309
309
  if (params?.limit !== undefined)
310
310
  query.limit = String(params.limit);
311
- const res = await this.client.getArbitrage(query);
312
- // getArbitrage already unwraps .data — res is the opportunities array.
313
- const items = Array.isArray(res) ? res : (res?.data ?? []);
311
+ const items = await this.client.getArbitrage(query);
314
312
  return items.map((r) => {
315
313
  if (r.spread == null || r.buyPrice == null || r.sellPrice == null) {
316
314
  throw new Error(`fetchArbitrageBulk: arbitrage record is missing required price fields ` +
@@ -1,14 +1,22 @@
1
- import type { FetchMatchesParams, FetchMarketMatchesParams, FetchEventMatchesParams, RouterMarketSearchParams, RouterEventSearchParams } from './types';
1
+ import type { UnifiedMarket, UnifiedEvent } from '../types';
2
+ import type { FetchMatchesParams, FetchMarketMatchesParams, FetchEventMatchesParams, MatchResult, EventMatchResult, ArbitrageOpportunity, RouterMarketSearchParams, RouterEventSearchParams } from './types';
3
+ interface MarketMatchesResponse {
4
+ matches: MatchResult[];
5
+ }
6
+ interface EventMatchesResponse {
7
+ matches: EventMatchResult[];
8
+ }
2
9
  export declare class PmxtApiClient {
3
10
  private readonly http;
4
11
  constructor(apiKey: string, baseUrl?: string);
5
- getMarketMatches(params: FetchMatchesParams): Promise<any>;
6
- getEventMatches(params: FetchEventMatchesParams): Promise<any>;
7
- browseMarketMatches(params: FetchMarketMatchesParams): Promise<any>;
8
- browseEventMatches(params: FetchEventMatchesParams): Promise<any>;
9
- searchMarkets(params?: RouterMarketSearchParams): Promise<any>;
10
- searchEvents(params?: RouterEventSearchParams): Promise<any>;
11
- getArbitrage(query?: Record<string, string>): Promise<any>;
12
+ getMarketMatches(params: FetchMatchesParams): Promise<MarketMatchesResponse>;
13
+ getEventMatches(params: FetchEventMatchesParams): Promise<EventMatchesResponse>;
14
+ browseMarketMatches(params: FetchMarketMatchesParams): Promise<MatchResult[]>;
15
+ browseEventMatches(params: FetchEventMatchesParams): Promise<EventMatchResult[]>;
16
+ searchMarkets(params?: RouterMarketSearchParams): Promise<UnifiedMarket[]>;
17
+ searchEvents(params?: RouterEventSearchParams): Promise<UnifiedEvent[]>;
18
+ getArbitrage(query?: Record<string, string>): Promise<ArbitrageOpportunity[]>;
12
19
  private request;
13
20
  private mapError;
14
21
  }
22
+ export {};
@@ -169,10 +169,14 @@ class PmxtApiClient {
169
169
  return new errors_1.BadRequest(message, 'Router');
170
170
  }
171
171
  }
172
- if (error?.code === 'ECONNREFUSED' || error?.code === 'ENOTFOUND' || error?.code === 'ETIMEDOUT') {
173
- return new errors_1.NetworkError(`Network error: ${error.message}`, 'Router');
172
+ if (error instanceof Error) {
173
+ const code = error.code;
174
+ if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'ETIMEDOUT') {
175
+ return new errors_1.NetworkError(`Network error: ${error.message}`, 'Router');
176
+ }
177
+ return error;
174
178
  }
175
- return error instanceof Error ? error : new Error(String(error));
179
+ return new Error(String(error));
176
180
  }
177
181
  }
178
182
  exports.PmxtApiClient = PmxtApiClient;
@@ -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
  });
@@ -2920,7 +2920,7 @@ components:
2920
2920
  - pending
2921
2921
  - open
2922
2922
  - filled
2923
- - cancelled
2923
+ - canceled
2924
2924
  - rejected
2925
2925
  description: Lifecycle status of the order.
2926
2926
  filled:
@@ -5,7 +5,7 @@ import { BaseSubscriber, SubscribedActivityBuilder, SubscriberConfig, Subscripti
5
5
  export interface GoldSkyGraphQlQuery {
6
6
  url: string;
7
7
  query: string;
8
- variables?: Record<string, any>;
8
+ variables?: Record<string, string | number | boolean>;
9
9
  }
10
10
  /**
11
11
  * Executes a single GraphQL query and returns the `data` object, or `null` on
@@ -76,6 +76,7 @@ export declare class GoldSkySubscriber implements BaseSubscriber {
76
76
  private pollTimers;
77
77
  private callbacks;
78
78
  private addressQueryTypes;
79
+ private querying;
79
80
  private closed;
80
81
  constructor(config: GoldSkyConfig);
81
82
  subscribe(address: string, types: SubscriptionOption[], onEvent: (data: unknown) => void): Promise<void>;