pmxt-core 2.8.0 → 2.9.1

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 (83) hide show
  1. package/dist/BaseExchange.d.ts +48 -0
  2. package/dist/BaseExchange.js +116 -0
  3. package/dist/exchanges/kalshi/api.d.ts +1763 -0
  4. package/dist/exchanges/kalshi/api.js +2554 -0
  5. package/dist/exchanges/kalshi/fetchEvents.d.ts +3 -2
  6. package/dist/exchanges/kalshi/fetchEvents.js +9 -16
  7. package/dist/exchanges/kalshi/fetchMarkets.d.ts +3 -2
  8. package/dist/exchanges/kalshi/fetchMarkets.js +24 -32
  9. package/dist/exchanges/kalshi/fetchOHLCV.d.ts +1 -2
  10. package/dist/exchanges/kalshi/fetchOHLCV.js +9 -11
  11. package/dist/exchanges/kalshi/index.d.ts +2 -1
  12. package/dist/exchanges/kalshi/index.js +161 -183
  13. package/dist/exchanges/kalshi/kalshi.test.js +51 -31
  14. package/dist/exchanges/limitless/api.d.ts +555 -0
  15. package/dist/exchanges/limitless/api.js +863 -0
  16. package/dist/exchanges/limitless/fetchEvents.d.ts +1 -2
  17. package/dist/exchanges/limitless/fetchEvents.js +9 -15
  18. package/dist/exchanges/limitless/fetchMarkets.d.ts +1 -2
  19. package/dist/exchanges/limitless/fetchMarkets.js +9 -16
  20. package/dist/exchanges/limitless/fetchOHLCV.d.ts +1 -2
  21. package/dist/exchanges/limitless/fetchOHLCV.js +2 -11
  22. package/dist/exchanges/limitless/fetchOrderBook.d.ts +1 -2
  23. package/dist/exchanges/limitless/fetchOrderBook.js +2 -11
  24. package/dist/exchanges/limitless/index.d.ts +1 -0
  25. package/dist/exchanges/limitless/index.js +28 -7
  26. package/dist/exchanges/limitless/websocket.d.ts +2 -1
  27. package/dist/exchanges/limitless/websocket.js +6 -4
  28. package/dist/exchanges/myriad/api.d.ts +294 -0
  29. package/dist/exchanges/myriad/api.js +690 -0
  30. package/dist/exchanges/myriad/fetchOHLCV.d.ts +1 -2
  31. package/dist/exchanges/myriad/fetchOHLCV.js +3 -11
  32. package/dist/exchanges/myriad/fetchOrderBook.d.ts +1 -2
  33. package/dist/exchanges/myriad/fetchOrderBook.js +3 -11
  34. package/dist/exchanges/myriad/index.d.ts +2 -0
  35. package/dist/exchanges/myriad/index.js +116 -103
  36. package/dist/exchanges/myriad/websocket.d.ts +2 -2
  37. package/dist/exchanges/myriad/websocket.js +28 -6
  38. package/dist/exchanges/polymarket/api-clob.d.ts +346 -0
  39. package/dist/exchanges/polymarket/api-clob.js +517 -0
  40. package/dist/exchanges/polymarket/api-data.d.ts +789 -0
  41. package/dist/exchanges/polymarket/api-data.js +860 -0
  42. package/dist/exchanges/polymarket/api-gamma.d.ts +556 -0
  43. package/dist/exchanges/polymarket/api-gamma.js +1161 -0
  44. package/dist/exchanges/polymarket/fetchEvents.js +0 -68
  45. package/dist/exchanges/polymarket/fetchOHLCV.d.ts +1 -2
  46. package/dist/exchanges/polymarket/fetchOHLCV.js +4 -10
  47. package/dist/exchanges/polymarket/fetchOrderBook.d.ts +1 -2
  48. package/dist/exchanges/polymarket/fetchOrderBook.js +2 -10
  49. package/dist/exchanges/polymarket/fetchTrades.d.ts +1 -2
  50. package/dist/exchanges/polymarket/fetchTrades.js +2 -11
  51. package/dist/exchanges/polymarket/index.d.ts +10 -0
  52. package/dist/exchanges/polymarket/index.js +110 -5
  53. package/dist/exchanges/probable/api.d.ts +605 -0
  54. package/dist/exchanges/probable/api.js +887 -0
  55. package/dist/exchanges/probable/fetchEvents.d.ts +3 -3
  56. package/dist/exchanges/probable/fetchEvents.js +28 -25
  57. package/dist/exchanges/probable/fetchMarkets.d.ts +1 -1
  58. package/dist/exchanges/probable/fetchMarkets.js +25 -21
  59. package/dist/exchanges/probable/index.d.ts +1 -0
  60. package/dist/exchanges/probable/index.js +92 -10
  61. package/dist/exchanges/probable/utils.d.ts +1 -2
  62. package/dist/exchanges/probable/utils.js +4 -11
  63. package/dist/index.d.ts +1 -0
  64. package/dist/index.js +3 -1
  65. package/dist/utils/openapi.d.ts +9 -0
  66. package/dist/utils/openapi.js +59 -0
  67. package/package.json +4 -3
  68. package/dist/exchanges/kalshi/fetchOrderBook.d.ts +0 -3
  69. package/dist/exchanges/kalshi/fetchOrderBook.js +0 -59
  70. package/dist/exchanges/kalshi/fetchTrades.d.ts +0 -4
  71. package/dist/exchanges/kalshi/fetchTrades.js +0 -31
  72. package/dist/exchanges/limitless/fetchPositions.d.ts +0 -2
  73. package/dist/exchanges/limitless/fetchPositions.js +0 -34
  74. package/dist/exchanges/myriad/fetchTrades.d.ts +0 -4
  75. package/dist/exchanges/myriad/fetchTrades.js +0 -62
  76. package/dist/exchanges/polymarket/fetchPositions.d.ts +0 -2
  77. package/dist/exchanges/polymarket/fetchPositions.js +0 -34
  78. package/dist/exchanges/probable/fetchOHLCV.d.ts +0 -4
  79. package/dist/exchanges/probable/fetchOHLCV.js +0 -83
  80. package/dist/exchanges/probable/fetchOrderBook.d.ts +0 -3
  81. package/dist/exchanges/probable/fetchOrderBook.js +0 -37
  82. package/dist/exchanges/probable/fetchPositions.d.ts +0 -2
  83. package/dist/exchanges/probable/fetchPositions.js +0 -33
@@ -5,12 +5,13 @@ const BaseExchange_1 = require("../../BaseExchange");
5
5
  const fetchMarkets_1 = require("./fetchMarkets");
6
6
  const fetchEvents_1 = require("./fetchEvents");
7
7
  const fetchOHLCV_1 = require("./fetchOHLCV");
8
- const fetchOrderBook_1 = require("./fetchOrderBook");
9
- const fetchTrades_1 = require("./fetchTrades");
10
8
  const auth_1 = require("./auth");
9
+ const validation_1 = require("../../utils/validation");
11
10
  const websocket_1 = require("./websocket");
12
11
  const errors_1 = require("./errors");
13
12
  const errors_2 = require("../../errors");
13
+ const openapi_1 = require("../../utils/openapi");
14
+ const api_1 = require("./api");
14
15
  class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
15
16
  has = {
16
17
  fetchMarkets: true,
@@ -47,12 +48,23 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
47
48
  if (credentials?.apiKey && credentials?.privateKey) {
48
49
  this.auth = new auth_1.KalshiAuth(credentials);
49
50
  }
51
+ const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.kalshiApiSpec);
52
+ this.defineImplicitApi(descriptor);
50
53
  }
51
54
  get name() {
52
55
  return "Kalshi";
53
56
  }
54
- getBaseUrl() {
55
- return 'https://api.elections.kalshi.com';
57
+ // ----------------------------------------------------------------------------
58
+ // Implicit API Auth & Error Mapping
59
+ // ----------------------------------------------------------------------------
60
+ sign(method, path, _params) {
61
+ const auth = this.ensureAuth();
62
+ // The implicit API passes just the spec path (e.g. /portfolio/balance),
63
+ // but Kalshi's signature requires the full path including /trade-api/v2.
64
+ return auth.getHeaders(method, '/trade-api/v2' + path);
65
+ }
66
+ mapImplicitApiError(error) {
67
+ throw errors_1.kalshiErrorMapper.mapError(error);
56
68
  }
57
69
  // ----------------------------------------------------------------------------
58
70
  // Helpers
@@ -68,218 +80,184 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
68
80
  // Market Data Methods - Implementation for CCXT-style API
69
81
  // ----------------------------------------------------------------------------
70
82
  async fetchMarketsImpl(params) {
71
- return (0, fetchMarkets_1.fetchMarkets)(params, this.http);
83
+ return (0, fetchMarkets_1.fetchMarkets)(params, this.callApi.bind(this));
72
84
  }
73
85
  async fetchEventsImpl(params) {
74
- return (0, fetchEvents_1.fetchEvents)(params, this.http);
86
+ return (0, fetchEvents_1.fetchEvents)(params, this.callApi.bind(this));
75
87
  }
76
88
  async fetchOHLCV(id, params) {
77
- return (0, fetchOHLCV_1.fetchOHLCV)(id, params, this.http);
89
+ return (0, fetchOHLCV_1.fetchOHLCV)(id, params, this.callApi.bind(this));
78
90
  }
79
91
  async fetchOrderBook(id) {
80
- return (0, fetchOrderBook_1.fetchOrderBook)(id, this.http);
92
+ (0, validation_1.validateIdFormat)(id, 'OrderBook');
93
+ const isNoOutcome = id.endsWith('-NO');
94
+ const ticker = id.replace(/-NO$/, '');
95
+ const data = (await this.callApi('GetMarketOrderbook', { ticker })).orderbook;
96
+ let bids;
97
+ let asks;
98
+ if (isNoOutcome) {
99
+ bids = (data.no || []).map((level) => ({
100
+ price: level[0] / 100,
101
+ size: level[1],
102
+ }));
103
+ asks = (data.yes || []).map((level) => ({
104
+ price: 1 - (level[0] / 100),
105
+ size: level[1],
106
+ }));
107
+ }
108
+ else {
109
+ bids = (data.yes || []).map((level) => ({
110
+ price: level[0] / 100,
111
+ size: level[1],
112
+ }));
113
+ asks = (data.no || []).map((level) => ({
114
+ price: 1 - (level[0] / 100),
115
+ size: level[1],
116
+ }));
117
+ }
118
+ bids.sort((a, b) => b.price - a.price);
119
+ asks.sort((a, b) => a.price - b.price);
120
+ return { bids, asks, timestamp: Date.now() };
81
121
  }
82
122
  async fetchTrades(id, params) {
83
- // Deprecation warning
84
123
  if ('resolution' in params && params.resolution !== undefined) {
85
124
  console.warn('[pmxt] Warning: The "resolution" parameter is deprecated for fetchTrades() and will be ignored. ' +
86
125
  'It will be removed in v3.0.0. Please remove it from your code.');
87
126
  }
88
- return (0, fetchTrades_1.fetchTrades)(id, params, this.http);
127
+ const ticker = id.replace(/-NO$/, '');
128
+ const data = await this.callApi('GetTrades', { ticker, limit: params.limit || 100 });
129
+ const trades = data.trades || [];
130
+ return trades.map((t) => ({
131
+ id: t.trade_id,
132
+ timestamp: new Date(t.created_time).getTime(),
133
+ price: t.yes_price / 100,
134
+ amount: t.count,
135
+ side: t.taker_side === 'yes' ? 'buy' : 'sell',
136
+ }));
89
137
  }
90
138
  // ----------------------------------------------------------------------------
91
139
  // User Data Methods
92
140
  // ----------------------------------------------------------------------------
93
141
  async fetchBalance() {
94
- try {
95
- const auth = this.ensureAuth();
96
- const path = '/trade-api/v2/portfolio/balance';
97
- // Use demo-api if it's a sandbox key (usually indicated by config, but defaulting to prod for now)
98
- // Or we could detect it. For now, let's assume Production unless specified.
99
- // TODO: Make base URL configurable in credentials
100
- const baseUrl = this.getBaseUrl();
101
- const headers = auth.getHeaders('GET', path);
102
- const response = await this.http.get(`${baseUrl}${path}`, { headers });
103
- // Kalshi response structure:
104
- // - balance: Available balance in cents (for trading)
105
- // - portfolio_value: Total portfolio value in cents (includes positions)
106
- // - updated_ts: Unix timestamp of last update
107
- const balanceCents = response.data.balance;
108
- const portfolioValueCents = response.data.portfolio_value;
109
- const available = balanceCents / 100;
110
- const total = portfolioValueCents / 100;
111
- const locked = total - available;
112
- return [{
113
- currency: 'USD',
114
- total: total, // Total portfolio value (cash + positions)
115
- available: available, // Available for trading
116
- locked: locked // Value locked in positions
117
- }];
118
- }
119
- catch (error) {
120
- throw errors_1.kalshiErrorMapper.mapError(error);
121
- }
142
+ const data = await this.callApi('GetBalance');
143
+ const available = data.balance / 100;
144
+ const total = data.portfolio_value / 100;
145
+ return [{
146
+ currency: 'USD',
147
+ total,
148
+ available,
149
+ locked: total - available,
150
+ }];
122
151
  }
123
152
  // ----------------------------------------------------------------------------
124
153
  // Trading Methods
125
154
  // ----------------------------------------------------------------------------
126
155
  async createOrder(params) {
127
- try {
128
- const auth = this.ensureAuth();
129
- const path = '/trade-api/v2/portfolio/orders';
130
- const baseUrl = this.getBaseUrl();
131
- const headers = auth.getHeaders('POST', path);
132
- // Map unified params to Kalshi format
133
- // Kalshi uses 'yes'/'no' for side and 'buy'/'sell' for action
134
- const isYesSide = params.side === 'buy';
135
- const kalshiOrder = {
136
- ticker: params.marketId, // Kalshi uses ticker for market identification
137
- client_order_id: `pmxt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
138
- side: isYesSide ? 'yes' : 'no',
139
- action: params.side === 'buy' ? 'buy' : 'sell',
140
- count: params.amount, // Number of contracts
141
- type: params.type === 'limit' ? 'limit' : 'market'
142
- };
143
- // Add price field based on side (yes_price for yes side, no_price for no side)
144
- if (params.price) {
145
- const priceInCents = Math.round(params.price * 100);
146
- if (isYesSide) {
147
- kalshiOrder.yes_price = priceInCents;
148
- }
149
- else {
150
- kalshiOrder.no_price = priceInCents;
151
- }
156
+ const isYesSide = params.side === 'buy';
157
+ const kalshiOrder = {
158
+ ticker: params.marketId,
159
+ client_order_id: `pmxt-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
160
+ side: isYesSide ? 'yes' : 'no',
161
+ action: params.side === 'buy' ? 'buy' : 'sell',
162
+ count: params.amount,
163
+ type: params.type === 'limit' ? 'limit' : 'market',
164
+ };
165
+ if (params.price) {
166
+ const priceInCents = Math.round(params.price * 100);
167
+ if (isYesSide) {
168
+ kalshiOrder.yes_price = priceInCents;
169
+ }
170
+ else {
171
+ kalshiOrder.no_price = priceInCents;
152
172
  }
153
- const response = await this.http.post(`${baseUrl}${path}`, kalshiOrder, { headers });
154
- const order = response.data.order;
155
- return {
156
- id: order.order_id,
157
- marketId: params.marketId,
158
- outcomeId: params.outcomeId,
159
- side: params.side,
160
- type: params.type,
161
- price: params.price,
162
- amount: params.amount,
163
- status: this.mapKalshiOrderStatus(order.status),
164
- filled: order.queue_position === 0 ? params.amount : 0,
165
- remaining: order.remaining_count || params.amount,
166
- timestamp: new Date(order.created_time).getTime()
167
- };
168
- }
169
- catch (error) {
170
- throw errors_1.kalshiErrorMapper.mapError(error);
171
173
  }
174
+ const data = await this.callApi('CreateOrder', kalshiOrder);
175
+ const order = data.order;
176
+ return {
177
+ id: order.order_id,
178
+ marketId: params.marketId,
179
+ outcomeId: params.outcomeId,
180
+ side: params.side,
181
+ type: params.type,
182
+ price: params.price,
183
+ amount: params.amount,
184
+ status: this.mapKalshiOrderStatus(order.status),
185
+ filled: order.queue_position === 0 ? params.amount : 0,
186
+ remaining: order.remaining_count || params.amount,
187
+ timestamp: new Date(order.created_time).getTime(),
188
+ };
172
189
  }
173
190
  async cancelOrder(orderId) {
174
- try {
175
- const auth = this.ensureAuth();
176
- const path = `/trade-api/v2/portfolio/orders/${orderId}`;
177
- const baseUrl = this.getBaseUrl();
178
- const headers = auth.getHeaders('DELETE', path);
179
- const response = await this.http.delete(`${baseUrl}${path}`, { headers });
180
- const order = response.data.order;
181
- return {
182
- id: order.order_id,
183
- marketId: order.ticker,
184
- outcomeId: order.ticker,
185
- side: order.side === 'yes' ? 'buy' : 'sell',
186
- type: 'limit',
187
- amount: order.count,
188
- status: 'cancelled',
189
- filled: order.count - (order.remaining_count || 0),
190
- remaining: 0,
191
- timestamp: new Date(order.created_time).getTime()
192
- };
193
- }
194
- catch (error) {
195
- throw errors_1.kalshiErrorMapper.mapError(error);
196
- }
191
+ const data = await this.callApi('CancelOrder', { order_id: orderId });
192
+ const order = data.order;
193
+ return {
194
+ id: order.order_id,
195
+ marketId: order.ticker,
196
+ outcomeId: order.ticker,
197
+ side: order.side === 'yes' ? 'buy' : 'sell',
198
+ type: 'limit',
199
+ amount: order.count,
200
+ status: 'cancelled',
201
+ filled: order.count - (order.remaining_count || 0),
202
+ remaining: 0,
203
+ timestamp: new Date(order.created_time).getTime(),
204
+ };
197
205
  }
198
206
  async fetchOrder(orderId) {
199
- try {
200
- const auth = this.ensureAuth();
201
- const path = `/trade-api/v2/portfolio/orders/${orderId}`;
202
- const baseUrl = this.getBaseUrl();
203
- const headers = auth.getHeaders('GET', path);
204
- const response = await this.http.get(`${baseUrl}${path}`, { headers });
205
- const order = response.data.order;
206
- return {
207
- id: order.order_id,
208
- marketId: order.ticker,
209
- outcomeId: order.ticker,
210
- side: order.side === 'yes' ? 'buy' : 'sell',
211
- type: order.type === 'limit' ? 'limit' : 'market',
212
- price: order.yes_price ? order.yes_price / 100 : undefined,
213
- amount: order.count,
214
- status: this.mapKalshiOrderStatus(order.status),
215
- filled: order.count - (order.remaining_count || 0),
216
- remaining: order.remaining_count || 0,
217
- timestamp: new Date(order.created_time).getTime()
218
- };
219
- }
220
- catch (error) {
221
- throw errors_1.kalshiErrorMapper.mapError(error);
222
- }
207
+ const data = await this.callApi('GetOrder', { order_id: orderId });
208
+ const order = data.order;
209
+ return {
210
+ id: order.order_id,
211
+ marketId: order.ticker,
212
+ outcomeId: order.ticker,
213
+ side: order.side === 'yes' ? 'buy' : 'sell',
214
+ type: order.type === 'limit' ? 'limit' : 'market',
215
+ price: order.yes_price ? order.yes_price / 100 : undefined,
216
+ amount: order.count,
217
+ status: this.mapKalshiOrderStatus(order.status),
218
+ filled: order.count - (order.remaining_count || 0),
219
+ remaining: order.remaining_count || 0,
220
+ timestamp: new Date(order.created_time).getTime(),
221
+ };
223
222
  }
224
223
  async fetchOpenOrders(marketId) {
225
- try {
226
- const auth = this.ensureAuth();
227
- // CRITICAL: Query parameters must NOT be included in the signature
228
- const basePath = '/trade-api/v2/portfolio/orders';
229
- let queryParams = '?status=resting';
230
- if (marketId) {
231
- queryParams += `&ticker=${marketId}`;
232
- }
233
- const baseUrl = this.getBaseUrl();
234
- // Sign only the base path, not the query parameters
235
- const headers = auth.getHeaders('GET', basePath);
236
- const response = await this.http.get(`${baseUrl}${basePath}${queryParams}`, { headers });
237
- const orders = response.data.orders || [];
238
- return orders.map((order) => ({
239
- id: order.order_id,
240
- marketId: order.ticker,
241
- outcomeId: order.ticker,
242
- side: order.side === 'yes' ? 'buy' : 'sell',
243
- type: order.type === 'limit' ? 'limit' : 'market',
244
- price: order.yes_price ? order.yes_price / 100 : undefined,
245
- amount: order.count,
246
- status: 'open',
247
- filled: order.count - (order.remaining_count || 0),
248
- remaining: order.remaining_count || 0,
249
- timestamp: new Date(order.created_time).getTime()
250
- }));
251
- }
252
- catch (error) {
253
- throw errors_1.kalshiErrorMapper.mapError(error);
224
+ const queryParams = { status: 'resting' };
225
+ if (marketId) {
226
+ queryParams.ticker = marketId;
254
227
  }
228
+ const data = await this.callApi('GetOrders', queryParams);
229
+ const orders = data.orders || [];
230
+ return orders.map((order) => ({
231
+ id: order.order_id,
232
+ marketId: order.ticker,
233
+ outcomeId: order.ticker,
234
+ side: order.side === 'yes' ? 'buy' : 'sell',
235
+ type: order.type === 'limit' ? 'limit' : 'market',
236
+ price: order.yes_price ? order.yes_price / 100 : undefined,
237
+ amount: order.count,
238
+ status: 'open',
239
+ filled: order.count - (order.remaining_count || 0),
240
+ remaining: order.remaining_count || 0,
241
+ timestamp: new Date(order.created_time).getTime(),
242
+ }));
255
243
  }
256
244
  async fetchPositions() {
257
- try {
258
- const auth = this.ensureAuth();
259
- const path = '/trade-api/v2/portfolio/positions';
260
- const baseUrl = this.getBaseUrl();
261
- const headers = auth.getHeaders('GET', path);
262
- const response = await this.http.get(`${baseUrl}${path}`, { headers });
263
- const positions = response.data.market_positions || [];
264
- return positions.map((pos) => {
265
- const absPosition = Math.abs(pos.position);
266
- // Prevent division by zero
267
- const entryPrice = absPosition > 0 ? pos.total_cost / absPosition / 100 : 0;
268
- return {
269
- marketId: pos.ticker,
270
- outcomeId: pos.ticker,
271
- outcomeLabel: pos.ticker, // Kalshi uses ticker as the outcome label
272
- size: pos.position, // Positive for long, negative for short
273
- entryPrice: entryPrice,
274
- currentPrice: pos.market_price ? pos.market_price / 100 : entryPrice,
275
- unrealizedPnL: pos.market_exposure ? pos.market_exposure / 100 : 0,
276
- realizedPnL: pos.realized_pnl ? pos.realized_pnl / 100 : 0
277
- };
278
- });
279
- }
280
- catch (error) {
281
- throw errors_1.kalshiErrorMapper.mapError(error);
282
- }
245
+ const data = await this.callApi('GetPositions');
246
+ const positions = data.market_positions || [];
247
+ return positions.map((pos) => {
248
+ const absPosition = Math.abs(pos.position);
249
+ const entryPrice = absPosition > 0 ? pos.total_cost / absPosition / 100 : 0;
250
+ return {
251
+ marketId: pos.ticker,
252
+ outcomeId: pos.ticker,
253
+ outcomeLabel: pos.ticker,
254
+ size: pos.position,
255
+ entryPrice,
256
+ currentPrice: pos.market_price ? pos.market_price / 100 : entryPrice,
257
+ unrealizedPnL: pos.market_exposure ? pos.market_exposure / 100 : 0,
258
+ realizedPnL: pos.realized_pnl ? pos.realized_pnl / 100 : 0,
259
+ };
260
+ });
283
261
  }
284
262
  // Helper to map Kalshi order status to unified status
285
263
  mapKalshiOrderStatus(status) {
@@ -18,10 +18,13 @@ jest.mock('axios', () => {
18
18
  get: jest.fn(),
19
19
  post: jest.fn(),
20
20
  delete: jest.fn(),
21
+ request: jest.fn(),
21
22
  defaults: { headers: { common: {} } },
22
23
  };
24
+ const actualAxios = jest.requireActual('axios');
23
25
  const mockAxios = {
24
26
  create: jest.fn(() => mockInstance),
27
+ isAxiosError: actualAxios.isAxiosError,
25
28
  };
26
29
  // Support both default and named exports
27
30
  return {
@@ -49,6 +52,7 @@ describe('KalshiExchange', () => {
49
52
  mockAxiosInstance.get.mockReset();
50
53
  mockAxiosInstance.post.mockReset();
51
54
  mockAxiosInstance.delete.mockReset();
55
+ mockAxiosInstance.request.mockReset();
52
56
  // Mock the getHeaders method
53
57
  MockedKalshiAuth.prototype.getHeaders = jest.fn().mockReturnValue({
54
58
  'KALSHI-ACCESS-KEY': 'test-api-key',
@@ -85,7 +89,7 @@ describe('KalshiExchange', () => {
85
89
  ]
86
90
  }
87
91
  };
88
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
92
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
89
93
  const markets = await exchange.fetchMarkets();
90
94
  expect(markets).toBeDefined();
91
95
  });
@@ -117,16 +121,20 @@ describe('KalshiExchange', () => {
117
121
  }
118
122
  }
119
123
  };
120
- mockAxiosInstance.post.mockResolvedValue(mockResponse);
124
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
121
125
  const order = await exchange.createOrder(orderParams);
122
- expect(mockAxiosInstance.post).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
123
- ticker: 'TEST-MARKET',
124
- side: 'yes',
125
- action: 'buy',
126
- count: 10,
127
- type: 'limit',
128
- yes_price: 55 // 0.55 * 100
129
- }), expect.any(Object));
126
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
127
+ method: 'POST',
128
+ url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
129
+ data: expect.objectContaining({
130
+ ticker: 'TEST-MARKET',
131
+ side: 'yes',
132
+ action: 'buy',
133
+ count: 10,
134
+ type: 'limit',
135
+ yes_price: 55 // 0.55 * 100
136
+ }),
137
+ }));
130
138
  expect(order.id).toBe('order-123');
131
139
  expect(order.status).toBe('open');
132
140
  });
@@ -152,16 +160,20 @@ describe('KalshiExchange', () => {
152
160
  }
153
161
  }
154
162
  };
155
- mockAxiosInstance.post.mockResolvedValue(mockResponse);
163
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
156
164
  await exchange.createOrder(orderParams);
157
- expect(mockAxiosInstance.post).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders', expect.objectContaining({
158
- ticker: 'TEST-MARKET',
159
- side: 'no',
160
- action: 'sell',
161
- count: 5,
162
- type: 'limit',
163
- no_price: 45 // 0.45 * 100
164
- }), expect.any(Object));
165
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
166
+ method: 'POST',
167
+ url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
168
+ data: expect.objectContaining({
169
+ ticker: 'TEST-MARKET',
170
+ side: 'no',
171
+ action: 'sell',
172
+ count: 5,
173
+ type: 'limit',
174
+ no_price: 45 // 0.45 * 100
175
+ }),
176
+ }));
165
177
  });
166
178
  });
167
179
  describe('fetchOpenOrders', () => {
@@ -182,18 +194,26 @@ describe('KalshiExchange', () => {
182
194
  ]
183
195
  }
184
196
  };
185
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
197
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
186
198
  await exchange.fetchOpenOrders();
187
- // Verify the request URL includes query params
188
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders?status=resting', expect.any(Object));
199
+ // Verify the request includes the correct params
200
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
201
+ method: 'GET',
202
+ url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
203
+ params: expect.objectContaining({ status: 'resting' }),
204
+ }));
189
205
  // Verify getHeaders was called with base path only (no query params)
190
206
  expect(MockedKalshiAuth.prototype.getHeaders).toHaveBeenCalledWith('GET', '/trade-api/v2/portfolio/orders');
191
207
  });
192
208
  it('should include ticker in query params when marketId provided', async () => {
193
209
  const mockResponse = { data: { orders: [] } };
194
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
210
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
195
211
  await exchange.fetchOpenOrders('TEST-MARKET');
196
- expect(mockAxiosInstance.get).toHaveBeenCalledWith('https://api.elections.kalshi.com/trade-api/v2/portfolio/orders?status=resting&ticker=TEST-MARKET', expect.any(Object));
212
+ expect(mockAxiosInstance.request).toHaveBeenCalledWith(expect.objectContaining({
213
+ method: 'GET',
214
+ url: 'https://api.elections.kalshi.com/trade-api/v2/portfolio/orders',
215
+ params: expect.objectContaining({ status: 'resting', ticker: 'TEST-MARKET' }),
216
+ }));
197
217
  });
198
218
  });
199
219
  describe('fetchPositions', () => {
@@ -211,7 +231,7 @@ describe('KalshiExchange', () => {
211
231
  ]
212
232
  }
213
233
  };
214
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
234
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
215
235
  const positions = await exchange.fetchPositions();
216
236
  expect(positions).toHaveLength(1);
217
237
  expect(positions[0].size).toBe(0);
@@ -231,7 +251,7 @@ describe('KalshiExchange', () => {
231
251
  ]
232
252
  }
233
253
  };
234
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
254
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
235
255
  const positions = await exchange.fetchPositions();
236
256
  expect(positions).toHaveLength(1);
237
257
  expect(positions[0].size).toBe(10);
@@ -253,7 +273,7 @@ describe('KalshiExchange', () => {
253
273
  ]
254
274
  }
255
275
  };
256
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
276
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
257
277
  const positions = await exchange.fetchPositions();
258
278
  expect(positions[0].size).toBe(-5); // Negative for short
259
279
  expect(Math.abs(positions[0].size)).toBe(5); // Absolute value
@@ -267,7 +287,7 @@ describe('KalshiExchange', () => {
267
287
  portfolio_value: 15000 // $150.00 total
268
288
  }
269
289
  };
270
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
290
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
271
291
  const balances = await exchange.fetchBalance();
272
292
  expect(balances).toHaveLength(1);
273
293
  expect(balances[0].currency).toBe('USD');
@@ -290,7 +310,7 @@ describe('KalshiExchange', () => {
290
310
  }
291
311
  }
292
312
  };
293
- mockAxiosInstance.delete.mockResolvedValue(mockResponse);
313
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
294
314
  const order = await exchange.cancelOrder('order-123');
295
315
  expect(order.status).toBe('cancelled');
296
316
  expect(order.filled).toBe(5); // count - remaining_count
@@ -314,7 +334,7 @@ describe('KalshiExchange', () => {
314
334
  }
315
335
  }
316
336
  };
317
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
337
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
318
338
  const order = await exchange.fetchOrder('order-123');
319
339
  expect(order.status).toBe('open');
320
340
  });
@@ -330,7 +350,7 @@ describe('KalshiExchange', () => {
330
350
  }
331
351
  }
332
352
  };
333
- mockAxiosInstance.get.mockResolvedValue(mockResponse);
353
+ mockAxiosInstance.request.mockResolvedValue(mockResponse);
334
354
  const order = await exchange.fetchOrder('order-123');
335
355
  expect(order.status).toBe('filled');
336
356
  });