pmxt-core 1.5.3 → 1.5.5

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.
@@ -289,14 +289,18 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
289
289
  if (!this.ws) {
290
290
  this.ws = new websocket_1.KalshiWebSocket(auth, this.wsConfig);
291
291
  }
292
- return this.ws.watchOrderBook(id);
292
+ // Normalize ticker (strip -NO suffix if present)
293
+ const marketTicker = id.replace(/-NO$/, '');
294
+ return this.ws.watchOrderBook(marketTicker);
293
295
  }
294
296
  async watchTrades(id, since, limit) {
295
297
  const auth = this.ensureAuth();
296
298
  if (!this.ws) {
297
299
  this.ws = new websocket_1.KalshiWebSocket(auth, this.wsConfig);
298
300
  }
299
- return this.ws.watchTrades(id);
301
+ // Normalize ticker (strip -NO suffix if present)
302
+ const marketTicker = id.replace(/-NO$/, '');
303
+ return this.ws.watchTrades(marketTicker);
300
304
  }
301
305
  async close() {
302
306
  if (this.ws) {
@@ -90,7 +90,7 @@ describe('KalshiExchange', () => {
90
90
  };
91
91
  mockedAxios.post.mockResolvedValue(mockResponse);
92
92
  const order = await exchange.createOrder(orderParams);
93
- expect(mockedAxios.post).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
93
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
94
94
  ticker: 'TEST-MARKET',
95
95
  side: 'yes',
96
96
  action: 'buy',
@@ -125,7 +125,7 @@ describe('KalshiExchange', () => {
125
125
  };
126
126
  mockedAxios.post.mockResolvedValue(mockResponse);
127
127
  await exchange.createOrder(orderParams);
128
- expect(mockedAxios.post).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
128
+ expect(mockedAxios.post).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
129
129
  ticker: 'TEST-MARKET',
130
130
  side: 'no',
131
131
  action: 'sell',
@@ -156,7 +156,7 @@ describe('KalshiExchange', () => {
156
156
  mockedAxios.get.mockResolvedValue(mockResponse);
157
157
  await exchange.fetchOpenOrders();
158
158
  // Verify the request URL includes query params
159
- expect(mockedAxios.get).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders?status=resting', expect.any(Object));
159
+ expect(mockedAxios.get).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders?status=resting', expect.any(Object));
160
160
  // Verify getHeaders was called with base path only (no query params)
161
161
  expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith('GET', '/trade-api/v2/portfolio/orders');
162
162
  });
@@ -164,7 +164,7 @@ describe('KalshiExchange', () => {
164
164
  const mockResponse = { data: { orders: [] } };
165
165
  mockedAxios.get.mockResolvedValue(mockResponse);
166
166
  await exchange.fetchOpenOrders('TEST-MARKET');
167
- expect(mockedAxios.get).toHaveBeenCalledWith('https://trading-api.kalshi.com/trade-api/v2/portfolio/orders?status=resting&ticker=TEST-MARKET', expect.any(Object));
167
+ expect(mockedAxios.get).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders?status=resting&ticker=TEST-MARKET', expect.any(Object));
168
168
  });
169
169
  });
170
170
  describe('fetchPositions', () => {
@@ -17,11 +17,14 @@ export declare class KalshiWebSocket {
17
17
  private orderBookResolvers;
18
18
  private tradeResolvers;
19
19
  private orderBooks;
20
- private subscribedTickers;
20
+ private subscribedOrderBookTickers;
21
+ private subscribedTradeTickers;
21
22
  private messageIdCounter;
22
23
  private isConnecting;
23
24
  private isConnected;
24
25
  private reconnectTimer?;
26
+ private connectionPromise?;
27
+ private isTerminated;
25
28
  constructor(auth: KalshiAuth, config?: KalshiWebSocketConfig);
26
29
  private connect;
27
30
  private scheduleReconnect;
@@ -14,10 +14,12 @@ class KalshiWebSocket {
14
14
  this.orderBookResolvers = new Map();
15
15
  this.tradeResolvers = new Map();
16
16
  this.orderBooks = new Map();
17
- this.subscribedTickers = new Set();
17
+ this.subscribedOrderBookTickers = new Set();
18
+ this.subscribedTradeTickers = new Set();
18
19
  this.messageIdCounter = 1;
19
20
  this.isConnecting = false;
20
21
  this.isConnected = false;
22
+ this.isTerminated = false;
21
23
  this.auth = auth;
22
24
  this.config = {
23
25
  wsUrl: config.wsUrl || 'wss://api.elections.kalshi.com/trade-api/ws/v2',
@@ -25,11 +27,17 @@ class KalshiWebSocket {
25
27
  };
26
28
  }
27
29
  async connect() {
28
- if (this.isConnected || this.isConnecting) {
30
+ if (this.isConnected) {
29
31
  return;
30
32
  }
33
+ if (this.isTerminated) {
34
+ return;
35
+ }
36
+ if (this.connectionPromise) {
37
+ return this.connectionPromise;
38
+ }
31
39
  this.isConnecting = true;
32
- return new Promise((resolve, reject) => {
40
+ this.connectionPromise = new Promise((resolve, reject) => {
33
41
  try {
34
42
  // Extract path from URL for signature
35
43
  const url = new URL(this.config.wsUrl);
@@ -41,10 +49,14 @@ class KalshiWebSocket {
41
49
  this.ws.on('open', () => {
42
50
  this.isConnected = true;
43
51
  this.isConnecting = false;
52
+ this.connectionPromise = undefined;
44
53
  console.log('Kalshi WebSocket connected');
45
54
  // Resubscribe to all tickers if reconnecting
46
- if (this.subscribedTickers.size > 0) {
47
- this.subscribeToOrderbook(Array.from(this.subscribedTickers));
55
+ if (this.subscribedOrderBookTickers.size > 0) {
56
+ this.subscribeToOrderbook(Array.from(this.subscribedOrderBookTickers));
57
+ }
58
+ if (this.subscribedTradeTickers.size > 0) {
59
+ this.subscribeToTrades(Array.from(this.subscribedTradeTickers));
48
60
  }
49
61
  resolve();
50
62
  });
@@ -60,22 +72,31 @@ class KalshiWebSocket {
60
72
  this.ws.on('error', (error) => {
61
73
  console.error('Kalshi WebSocket error:', error);
62
74
  this.isConnecting = false;
75
+ this.connectionPromise = undefined;
63
76
  reject(error);
64
77
  });
65
78
  this.ws.on('close', () => {
66
- console.log('Kalshi WebSocket closed');
79
+ if (!this.isTerminated) {
80
+ console.log('Kalshi WebSocket closed');
81
+ this.scheduleReconnect();
82
+ }
67
83
  this.isConnected = false;
68
84
  this.isConnecting = false;
69
- this.scheduleReconnect();
85
+ this.connectionPromise = undefined;
70
86
  });
71
87
  }
72
88
  catch (error) {
73
89
  this.isConnecting = false;
90
+ this.connectionPromise = undefined;
74
91
  reject(error);
75
92
  }
76
93
  });
94
+ return this.connectionPromise;
77
95
  }
78
96
  scheduleReconnect() {
97
+ if (this.isTerminated) {
98
+ return;
99
+ }
79
100
  if (this.reconnectTimer) {
80
101
  clearTimeout(this.reconnectTimer);
81
102
  }
@@ -271,9 +292,9 @@ class KalshiWebSocket {
271
292
  await this.connect();
272
293
  }
273
294
  // Subscribe if not already subscribed
274
- if (!this.subscribedTickers.has(ticker)) {
275
- this.subscribedTickers.add(ticker);
276
- this.subscribeToOrderbook([ticker]);
295
+ if (!this.subscribedOrderBookTickers.has(ticker)) {
296
+ this.subscribedOrderBookTickers.add(ticker);
297
+ this.subscribeToOrderbook(Array.from(this.subscribedOrderBookTickers));
277
298
  }
278
299
  // Return a promise that resolves on the next orderbook update
279
300
  return new Promise((resolve, reject) => {
@@ -289,9 +310,9 @@ class KalshiWebSocket {
289
310
  await this.connect();
290
311
  }
291
312
  // Subscribe if not already subscribed
292
- if (!this.subscribedTickers.has(ticker)) {
293
- this.subscribedTickers.add(ticker);
294
- this.subscribeToTrades([ticker]);
313
+ if (!this.subscribedTradeTickers.has(ticker)) {
314
+ this.subscribedTradeTickers.add(ticker);
315
+ this.subscribeToTrades(Array.from(this.subscribedTradeTickers));
295
316
  }
296
317
  // Return a promise that resolves on the next trade
297
318
  return new Promise((resolve, reject) => {
@@ -302,12 +323,33 @@ class KalshiWebSocket {
302
323
  });
303
324
  }
304
325
  async close() {
326
+ this.isTerminated = true;
305
327
  if (this.reconnectTimer) {
306
328
  clearTimeout(this.reconnectTimer);
329
+ this.reconnectTimer = undefined;
307
330
  }
331
+ // Reject all pending resolvers
332
+ this.orderBookResolvers.forEach((resolvers, ticker) => {
333
+ resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${ticker}`)));
334
+ });
335
+ this.orderBookResolvers.clear();
336
+ this.tradeResolvers.forEach((resolvers, ticker) => {
337
+ resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${ticker}`)));
338
+ });
339
+ this.tradeResolvers.clear();
308
340
  if (this.ws) {
309
- this.ws.close();
341
+ const ws = this.ws;
310
342
  this.ws = undefined;
343
+ if (ws.readyState !== ws_1.default.CLOSED && ws.readyState !== ws_1.default.CLOSING) {
344
+ return new Promise((resolve) => {
345
+ ws.once('close', () => {
346
+ this.isConnected = false;
347
+ this.isConnecting = false;
348
+ resolve();
349
+ });
350
+ ws.close();
351
+ });
352
+ }
311
353
  }
312
354
  this.isConnected = false;
313
355
  this.isConnecting = false;
@@ -191,11 +191,15 @@ class PolymarketExchange extends BaseExchange_1.PredictionMarketExchange {
191
191
  const client = await auth.getClobClient();
192
192
  try {
193
193
  const order = await client.getOrder(orderId);
194
+ if (!order || !order.id) {
195
+ const errorMsg = order?.error || 'Order not found (Invalid ID)';
196
+ throw new Error(errorMsg);
197
+ }
194
198
  return {
195
199
  id: order.id,
196
200
  marketId: order.market || 'unknown',
197
201
  outcomeId: order.asset_id,
198
- side: order.side.toLowerCase(),
202
+ side: (order.side || '').toLowerCase(),
199
203
  type: order.order_type === 'GTC' ? 'limit' : 'market',
200
204
  price: parseFloat(order.price),
201
205
  amount: parseFloat(order.original_size),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmxt-core",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
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",
@@ -25,12 +25,12 @@
25
25
  "clean": "rm -rf dist",
26
26
  "prebuild": "npm run clean",
27
27
  "build": "tsc",
28
- "bundle:server": "esbuild dist/server/index.js --bundle --platform=node --target=node18 --outfile=dist/server/bundled.js --external:fsevents",
28
+ "bundle:server": "npm run build && esbuild dist/server/index.js --bundle --platform=node --target=node18 --outfile=dist/server/bundled.js --external:fsevents",
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=1.5.3,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=1.5.3,supportsES6=true,typescriptThreePlus=true",
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=1.5.5,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=1.5.5,supportsES6=true,typescriptThreePlus=true",
34
34
  "generate:docs": "node ../scripts/generate-api-docs.js",
35
35
  "generate:sdk:all": "npm run generate:sdk:python && npm run generate:sdk:typescript && npm run generate:docs"
36
36
  },