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.
- package/dist/exchanges/baozi/errors.d.ts +1 -1
- package/dist/exchanges/baozi/errors.js +1 -1
- package/dist/exchanges/gemini-titan/errors.d.ts +3 -3
- package/dist/exchanges/gemini-titan/errors.js +1 -1
- package/dist/exchanges/gemini-titan/index.js +1 -1
- package/dist/exchanges/gemini-titan/normalizer.js +2 -2
- package/dist/exchanges/gemini-titan/websocket.js +16 -3
- package/dist/exchanges/hyperliquid/errors.d.ts +3 -3
- package/dist/exchanges/hyperliquid/errors.js +1 -1
- package/dist/exchanges/hyperliquid/index.js +1 -1
- package/dist/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/kalshi/auth.js +3 -0
- package/dist/exchanges/kalshi/errors.d.ts +2 -2
- package/dist/exchanges/kalshi/fetcher.d.ts +1 -0
- package/dist/exchanges/kalshi/fetcher.js +3 -3
- package/dist/exchanges/kalshi/index.js +1 -1
- package/dist/exchanges/kalshi/normalizer.d.ts +1 -1
- package/dist/exchanges/kalshi/normalizer.js +4 -4
- package/dist/exchanges/kalshi/websocket.d.ts +1 -0
- package/dist/exchanges/kalshi/websocket.js +29 -13
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/auth.js +3 -0
- package/dist/exchanges/limitless/client.js +9 -0
- package/dist/exchanges/limitless/errors.d.ts +2 -2
- package/dist/exchanges/limitless/index.js +1 -1
- package/dist/exchanges/limitless/normalizer.js +4 -2
- package/dist/exchanges/limitless/utils.js +3 -0
- package/dist/exchanges/limitless/websocket.js +37 -7
- package/dist/exchanges/metaculus/cancelOrder.d.ts +1 -1
- package/dist/exchanges/metaculus/cancelOrder.js +3 -3
- package/dist/exchanges/metaculus/errors.d.ts +3 -3
- package/dist/exchanges/mock/index.js +32 -15
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/errors.d.ts +2 -2
- package/dist/exchanges/myriad/websocket.js +16 -4
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/opinion/errors.d.ts +2 -2
- package/dist/exchanges/opinion/errors.js +4 -3
- package/dist/exchanges/opinion/fetcher.js +1 -1
- package/dist/exchanges/opinion/index.js +1 -1
- package/dist/exchanges/opinion/utils.d.ts +1 -1
- package/dist/exchanges/opinion/utils.js +2 -2
- package/dist/exchanges/opinion/websocket.js +35 -6
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/polymarket/auth.js +22 -3
- package/dist/exchanges/polymarket/errors.d.ts +3 -3
- package/dist/exchanges/polymarket/fetcher.js +3 -0
- package/dist/exchanges/polymarket/index.js +1 -1
- package/dist/exchanges/polymarket/normalizer.js +6 -4
- package/dist/exchanges/polymarket/websocket.d.ts +2 -0
- package/dist/exchanges/polymarket/websocket.js +53 -27
- package/dist/exchanges/polymarket_us/normalizer.js +3 -3
- package/dist/exchanges/polymarket_us/websocket.js +6 -0
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/probable/errors.d.ts +2 -2
- package/dist/exchanges/probable/errors.js +1 -1
- package/dist/exchanges/probable/index.js +2 -2
- package/dist/exchanges/smarkets/auth.js +6 -0
- package/dist/exchanges/smarkets/errors.d.ts +3 -3
- package/dist/exchanges/smarkets/errors.js +7 -2
- package/dist/exchanges/smarkets/fetcher.js +16 -6
- package/dist/feeds/binance/binance-feed.js +20 -2
- package/dist/feeds/chainlink/chainlink-feed.js +18 -3
- package/dist/router/Router.js +1 -3
- package/dist/router/client.d.ts +16 -8
- package/dist/router/client.js +7 -3
- package/dist/server/index.js +6 -5
- package/dist/server/openapi.yaml +1 -1
- package/dist/subscriber/external/goldsky.d.ts +2 -1
- package/dist/subscriber/external/goldsky.js +33 -14
- package/dist/subscriber/watcher.js +6 -8
- package/dist/types.d.ts +1 -1
- package/dist/utils/error-mapper.d.ts +7 -7
- package/dist/utils/error-mapper.js +54 -47
- package/dist/utils/market-utils.js +4 -4
- package/dist/utils/throttler.d.ts +2 -0
- package/dist/utils/throttler.js +8 -0
- package/package.json +3 -3
|
@@ -5,7 +5,7 @@ import { BaseError } from '../../errors';
|
|
|
5
5
|
*/
|
|
6
6
|
export declare class BaoziErrorMapper extends ErrorMapper {
|
|
7
7
|
constructor();
|
|
8
|
-
mapError(error:
|
|
8
|
+
mapError(error: unknown): BaseError;
|
|
9
9
|
private extractAnchorError;
|
|
10
10
|
}
|
|
11
11
|
export declare const baoziErrorMapper: BaoziErrorMapper;
|
|
@@ -22,7 +22,7 @@ class BaoziErrorMapper extends error_mapper_1.ErrorMapper {
|
|
|
22
22
|
}
|
|
23
23
|
mapError(error) {
|
|
24
24
|
// Handle Solana transaction errors
|
|
25
|
-
if (error
|
|
25
|
+
if (error instanceof Error) {
|
|
26
26
|
const msg = error.message;
|
|
27
27
|
// Solana insufficient funds
|
|
28
28
|
if (msg.includes('Attempt to debit an account but found no record of a prior credit') ||
|
|
@@ -13,8 +13,8 @@ import { BadRequest } from '../../errors';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare class GeminiErrorMapper extends ErrorMapper {
|
|
15
15
|
constructor();
|
|
16
|
-
protected extractErrorMessage(error:
|
|
17
|
-
protected mapBadRequestError(message: string, data:
|
|
18
|
-
mapError(error:
|
|
16
|
+
protected extractErrorMessage(error: unknown): string;
|
|
17
|
+
protected mapBadRequestError(message: string, data: unknown): BadRequest;
|
|
18
|
+
mapError(error: unknown): ReturnType<ErrorMapper['mapError']>;
|
|
19
19
|
}
|
|
20
20
|
export declare const geminiErrorMapper: GeminiErrorMapper;
|
|
@@ -38,7 +38,7 @@ class GeminiErrorMapper extends error_mapper_1.ErrorMapper {
|
|
|
38
38
|
return super.extractErrorMessage(error);
|
|
39
39
|
}
|
|
40
40
|
mapBadRequestError(message, data) {
|
|
41
|
-
const reason = typeof data === 'object' && data
|
|
41
|
+
const reason = typeof data === 'object' && data !== null && 'reason' in data
|
|
42
42
|
? String(data.reason)
|
|
43
43
|
: '';
|
|
44
44
|
const lowerReason = reason.toLowerCase();
|
|
@@ -23,8 +23,8 @@ function mapOrderStatus(geminiStatus) {
|
|
|
23
23
|
case 'open': return 'open';
|
|
24
24
|
case 'accepted': return 'open';
|
|
25
25
|
case 'filled': return 'filled';
|
|
26
|
-
case 'cancelled': return '
|
|
27
|
-
case 'canceled': return '
|
|
26
|
+
case 'cancelled': return 'canceled';
|
|
27
|
+
case 'canceled': return 'canceled';
|
|
28
28
|
case 'rejected': return 'rejected';
|
|
29
29
|
default: return 'open';
|
|
30
30
|
}
|
|
@@ -50,7 +50,10 @@ class GeminiWebSocket {
|
|
|
50
50
|
const headers = this.auth
|
|
51
51
|
? this.auth.buildWsHeaders()
|
|
52
52
|
: {};
|
|
53
|
-
this.ws = new ws_1.default(this.config.wsUrl, {
|
|
53
|
+
this.ws = new ws_1.default(this.config.wsUrl, {
|
|
54
|
+
headers,
|
|
55
|
+
handshakeTimeout: 30_000,
|
|
56
|
+
});
|
|
54
57
|
this.ws.on('open', () => {
|
|
55
58
|
this.isConnected = true;
|
|
56
59
|
this.isConnecting = false;
|
|
@@ -236,7 +239,12 @@ class GeminiWebSocket {
|
|
|
236
239
|
if (!this.orderBookResolvers.has(symbol)) {
|
|
237
240
|
this.orderBookResolvers.set(symbol, []);
|
|
238
241
|
}
|
|
239
|
-
this.orderBookResolvers.get(symbol)
|
|
242
|
+
const resolvers = this.orderBookResolvers.get(symbol);
|
|
243
|
+
if (!resolvers) {
|
|
244
|
+
reject(new Error(`[gemini-titan] resolver queue missing for ${symbol}`));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
resolvers.push({ resolve, reject });
|
|
240
248
|
});
|
|
241
249
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${symbol}')`);
|
|
242
250
|
}
|
|
@@ -257,7 +265,12 @@ class GeminiWebSocket {
|
|
|
257
265
|
if (!this.tradeResolvers.has(symbol)) {
|
|
258
266
|
this.tradeResolvers.set(symbol, []);
|
|
259
267
|
}
|
|
260
|
-
this.tradeResolvers.get(symbol)
|
|
268
|
+
const resolvers = this.tradeResolvers.get(symbol);
|
|
269
|
+
if (!resolvers) {
|
|
270
|
+
reject(new Error(`[gemini-titan] resolver queue missing for ${symbol}`));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
resolvers.push({ resolve, reject });
|
|
261
274
|
});
|
|
262
275
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${symbol}')`);
|
|
263
276
|
}
|
|
@@ -11,8 +11,8 @@ import { BadRequest } from '../../errors';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class HyperliquidErrorMapper extends ErrorMapper {
|
|
13
13
|
constructor();
|
|
14
|
-
protected extractErrorMessage(error:
|
|
15
|
-
protected mapBadRequestError(message: string, data:
|
|
16
|
-
mapError(error:
|
|
14
|
+
protected extractErrorMessage(error: unknown): string;
|
|
15
|
+
protected mapBadRequestError(message: string, data: unknown): BadRequest;
|
|
16
|
+
mapError(error: unknown): ReturnType<ErrorMapper['mapError']>;
|
|
17
17
|
}
|
|
18
18
|
export declare const hyperliquidErrorMapper: HyperliquidErrorMapper;
|
|
@@ -34,7 +34,7 @@ class HyperliquidErrorMapper extends error_mapper_1.ErrorMapper {
|
|
|
34
34
|
}
|
|
35
35
|
mapBadRequestError(message, data) {
|
|
36
36
|
const lowerMessage = message.toLowerCase();
|
|
37
|
-
const responseStr = typeof data === 'object' && data
|
|
37
|
+
const responseStr = typeof data === 'object' && data !== null && 'response' in data
|
|
38
38
|
? String(data.response).toLowerCase()
|
|
39
39
|
: lowerMessage;
|
|
40
40
|
if (responseStr.includes('insufficient margin') || responseStr.includes('not enough')) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T18:05:59.921Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const kalshiApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.kalshiApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T18:05:59.921Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.kalshiApiSpec = {
|
|
@@ -83,6 +83,9 @@ class KalshiAuth {
|
|
|
83
83
|
// Allow input of private key in both raw string or PEM format
|
|
84
84
|
// If it's a raw key without headers, accessing it might be tricky with implicit types,
|
|
85
85
|
// but standard PEM is best. We assume the user provides a valid PEM.
|
|
86
|
+
if (!this.credentials.privateKey) {
|
|
87
|
+
throw new Error('[kalshi] privateKey is required for authenticated requests');
|
|
88
|
+
}
|
|
86
89
|
let privateKey = this.credentials.privateKey;
|
|
87
90
|
// Fix for common .env issue where newlines are escaped
|
|
88
91
|
if (privateKey.includes('\\n')) {
|
|
@@ -10,10 +10,10 @@ export declare class KalshiErrorMapper extends ErrorMapper {
|
|
|
10
10
|
/**
|
|
11
11
|
* Override to handle Kalshi-specific error patterns
|
|
12
12
|
*/
|
|
13
|
-
protected extractErrorMessage(error:
|
|
13
|
+
protected extractErrorMessage(error: unknown): string;
|
|
14
14
|
/**
|
|
15
15
|
* Override to detect Kalshi-specific error patterns
|
|
16
16
|
*/
|
|
17
|
-
protected mapBadRequestError(message: string, data:
|
|
17
|
+
protected mapBadRequestError(message: string, data: unknown): BadRequest;
|
|
18
18
|
}
|
|
19
19
|
export declare const kalshiErrorMapper: KalshiErrorMapper;
|
|
@@ -308,7 +308,7 @@ class KalshiFetcher {
|
|
|
308
308
|
const events = data.events || [];
|
|
309
309
|
if (events.length === 0)
|
|
310
310
|
break;
|
|
311
|
-
allEvents
|
|
311
|
+
allEvents.push(...events);
|
|
312
312
|
if (targetMarketCount) {
|
|
313
313
|
for (const event of events) {
|
|
314
314
|
totalMarketCount += (event.markets || []).length;
|
|
@@ -343,7 +343,7 @@ class KalshiFetcher {
|
|
|
343
343
|
const events = data.events || [];
|
|
344
344
|
if (events.length === 0)
|
|
345
345
|
break;
|
|
346
|
-
allEvents
|
|
346
|
+
allEvents.push(...events);
|
|
347
347
|
cursor = data.cursor;
|
|
348
348
|
page++;
|
|
349
349
|
} while (cursor && page < MAX_PAGES);
|
|
@@ -370,7 +370,7 @@ class KalshiFetcher {
|
|
|
370
370
|
page++;
|
|
371
371
|
if (events.length === 0)
|
|
372
372
|
break;
|
|
373
|
-
allEvents
|
|
373
|
+
allEvents.push(...events);
|
|
374
374
|
} while (cursor && allEvents.length < maxEvents && page < MAX_PAGES);
|
|
375
375
|
return {
|
|
376
376
|
events: allEvents.slice(0, maxEvents),
|
|
@@ -260,7 +260,7 @@ class KalshiExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
260
260
|
side: order.side === 'yes' ? 'buy' : 'sell',
|
|
261
261
|
type: 'limit',
|
|
262
262
|
amount: order.count,
|
|
263
|
-
status: '
|
|
263
|
+
status: 'canceled',
|
|
264
264
|
filled: order.count - (order.remaining_count || 0),
|
|
265
265
|
remaining: 0,
|
|
266
266
|
timestamp: new Date(order.created_time).getTime(),
|
|
@@ -25,4 +25,4 @@ export declare class KalshiNormalizer implements IExchangeNormalizer<KalshiRawEv
|
|
|
25
25
|
private cleanLabel;
|
|
26
26
|
private templateRule;
|
|
27
27
|
}
|
|
28
|
-
export declare function sortRawEvents(events:
|
|
28
|
+
export declare function sortRawEvents(events: KalshiRawEvent[], sort: string): KalshiRawEvent[];
|
|
@@ -125,9 +125,9 @@ class KalshiNormalizer {
|
|
|
125
125
|
const pf = p[field];
|
|
126
126
|
const af = ask[field];
|
|
127
127
|
const bf = bid[field];
|
|
128
|
-
if (pf
|
|
128
|
+
if (pf != null)
|
|
129
129
|
return pf;
|
|
130
|
-
if (af
|
|
130
|
+
if (af != null && bf != null) {
|
|
131
131
|
return (af + bf) / 2;
|
|
132
132
|
}
|
|
133
133
|
return p.previous || 0;
|
|
@@ -257,7 +257,7 @@ class KalshiNormalizer {
|
|
|
257
257
|
switch ((status ?? '').toLowerCase()) {
|
|
258
258
|
case 'resting': return 'open';
|
|
259
259
|
case 'canceled':
|
|
260
|
-
case 'cancelled': return '
|
|
260
|
+
case 'cancelled': return 'canceled';
|
|
261
261
|
case 'executed':
|
|
262
262
|
case 'filled': return 'filled';
|
|
263
263
|
default: return 'open';
|
|
@@ -337,7 +337,7 @@ function eventVolume(event) {
|
|
|
337
337
|
return (event.markets || []).reduce((sum, m) => sum + (parseFloat(m.volume_fp ?? '') || Number(m.volume || 0)), 0);
|
|
338
338
|
}
|
|
339
339
|
function eventLiquidity(event) {
|
|
340
|
-
return (event.markets || []).reduce((sum, m) => sum + (parseFloat(m.open_interest_fp ?? '') || parseFloat(m.liquidity_dollars || m.open_interest || m.liquidity || '0') || 0), 0);
|
|
340
|
+
return (event.markets || []).reduce((sum, m) => sum + (parseFloat(m.open_interest_fp ?? '') || parseFloat(String(m.liquidity_dollars || m.open_interest || m.liquidity || '0')) || 0), 0);
|
|
341
341
|
}
|
|
342
342
|
function eventNewest(event) {
|
|
343
343
|
const times = (event.markets || [])
|
|
@@ -28,6 +28,7 @@ export declare class KalshiWebSocket {
|
|
|
28
28
|
private reconnectTimer?;
|
|
29
29
|
private connectionPromise?;
|
|
30
30
|
private isTerminated;
|
|
31
|
+
private static readonly CONNECTION_TIMEOUT_MS;
|
|
31
32
|
constructor(auth: KalshiAuth, config?: KalshiWebSocketConfig);
|
|
32
33
|
private connect;
|
|
33
34
|
private scheduleReconnect;
|
|
@@ -27,10 +27,14 @@ class KalshiWebSocket {
|
|
|
27
27
|
reconnectTimer;
|
|
28
28
|
connectionPromise;
|
|
29
29
|
isTerminated = false;
|
|
30
|
+
static CONNECTION_TIMEOUT_MS = 30_000;
|
|
30
31
|
constructor(auth, config = {}) {
|
|
31
32
|
this.auth = auth;
|
|
32
33
|
this.config = config;
|
|
33
|
-
|
|
34
|
+
if (!config.wsUrl) {
|
|
35
|
+
throw new Error('KalshiWebSocket: wsUrl is required in config');
|
|
36
|
+
}
|
|
37
|
+
this.wsUrl = config.wsUrl;
|
|
34
38
|
}
|
|
35
39
|
async connect() {
|
|
36
40
|
if (this.isConnected) {
|
|
@@ -52,7 +56,20 @@ class KalshiWebSocket {
|
|
|
52
56
|
// Get authentication headers
|
|
53
57
|
const headers = this.auth.getHeaders("GET", path);
|
|
54
58
|
this.ws = new ws_1.default(this.wsUrl, { headers });
|
|
59
|
+
// Connection timeout: close the socket if not connected within 30s
|
|
60
|
+
const connectionTimeout = setTimeout(() => {
|
|
61
|
+
if (!this.isConnected && this.ws) {
|
|
62
|
+
logger_1.logger.error("Kalshi WebSocket connection timed out", {
|
|
63
|
+
timeoutMs: KalshiWebSocket.CONNECTION_TIMEOUT_MS,
|
|
64
|
+
});
|
|
65
|
+
this.ws.close();
|
|
66
|
+
this.isConnecting = false;
|
|
67
|
+
this.connectionPromise = undefined;
|
|
68
|
+
reject(new Error(`Kalshi WebSocket connection timed out after ${KalshiWebSocket.CONNECTION_TIMEOUT_MS}ms`));
|
|
69
|
+
}
|
|
70
|
+
}, KalshiWebSocket.CONNECTION_TIMEOUT_MS);
|
|
55
71
|
this.ws.on("open", () => {
|
|
72
|
+
clearTimeout(connectionTimeout);
|
|
56
73
|
this.isConnected = true;
|
|
57
74
|
this.isConnecting = false;
|
|
58
75
|
this.connectionPromise = undefined;
|
|
@@ -76,12 +93,14 @@ class KalshiWebSocket {
|
|
|
76
93
|
}
|
|
77
94
|
});
|
|
78
95
|
this.ws.on("error", (error) => {
|
|
96
|
+
clearTimeout(connectionTimeout);
|
|
79
97
|
logger_1.logger.error("Kalshi WebSocket error", { error: String(error) });
|
|
80
98
|
this.isConnecting = false;
|
|
81
99
|
this.connectionPromise = undefined;
|
|
82
100
|
reject(error);
|
|
83
101
|
});
|
|
84
102
|
this.ws.on("close", () => {
|
|
103
|
+
clearTimeout(connectionTimeout);
|
|
85
104
|
if (!this.isTerminated) {
|
|
86
105
|
logger_1.logger.info("Kalshi WebSocket closed");
|
|
87
106
|
this.scheduleReconnect();
|
|
@@ -394,10 +413,9 @@ class KalshiWebSocket {
|
|
|
394
413
|
}
|
|
395
414
|
// Return a promise that resolves on the next orderbook update
|
|
396
415
|
const dataPromise = new Promise((resolve, reject) => {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this.orderBookResolvers.get(ticker).push({ resolve, reject });
|
|
416
|
+
const resolvers = this.orderBookResolvers.get(ticker) ?? [];
|
|
417
|
+
resolvers.push({ resolve, reject });
|
|
418
|
+
this.orderBookResolvers.set(ticker, resolvers);
|
|
401
419
|
});
|
|
402
420
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBook('${ticker}')`);
|
|
403
421
|
}
|
|
@@ -421,15 +439,14 @@ class KalshiWebSocket {
|
|
|
421
439
|
}
|
|
422
440
|
// Wait for all tickers to receive at least one snapshot/update
|
|
423
441
|
const dataPromise = Promise.all(tickers.map((ticker) => new Promise((resolve, reject) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
this.orderBookResolvers.get(ticker).push({
|
|
442
|
+
const resolvers = this.orderBookResolvers.get(ticker) ?? [];
|
|
443
|
+
resolvers.push({
|
|
428
444
|
resolve: (book) => {
|
|
429
445
|
Promise.resolve(book).then((b) => resolve([ticker, b]));
|
|
430
446
|
},
|
|
431
447
|
reject,
|
|
432
448
|
});
|
|
449
|
+
this.orderBookResolvers.set(ticker, resolvers);
|
|
433
450
|
})));
|
|
434
451
|
const entries = await (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchOrderBooks(${JSON.stringify(tickers)})`);
|
|
435
452
|
const result = {};
|
|
@@ -454,10 +471,9 @@ class KalshiWebSocket {
|
|
|
454
471
|
this.subscribeToTrades([ticker]);
|
|
455
472
|
}
|
|
456
473
|
const dataPromise = new Promise((resolve, reject) => {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.tradeResolvers.get(ticker).push({ resolve, reject });
|
|
474
|
+
const resolvers = this.tradeResolvers.get(ticker) ?? [];
|
|
475
|
+
resolvers.push({ resolve, reject });
|
|
476
|
+
this.tradeResolvers.set(ticker, resolvers);
|
|
461
477
|
});
|
|
462
478
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${ticker}')`);
|
|
463
479
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
3
|
-
* Generated at: 2026-05-
|
|
3
|
+
* Generated at: 2026-05-24T18:05:59.968Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const limitlessApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.limitlessApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
6
|
-
* Generated at: 2026-05-
|
|
6
|
+
* Generated at: 2026-05-24T18:05:59.968Z
|
|
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));
|
|
@@ -10,10 +10,10 @@ export declare class LimitlessErrorMapper extends ErrorMapper {
|
|
|
10
10
|
/**
|
|
11
11
|
* Override to handle Limitless-specific error patterns
|
|
12
12
|
*/
|
|
13
|
-
protected extractErrorMessage(error:
|
|
13
|
+
protected extractErrorMessage(error: unknown): string;
|
|
14
14
|
/**
|
|
15
15
|
* Override to detect Limitless-specific error patterns
|
|
16
16
|
*/
|
|
17
|
-
protected mapBadRequestError(message: string, data:
|
|
17
|
+
protected mapBadRequestError(message: string, data: unknown): BadRequest;
|
|
18
18
|
}
|
|
19
19
|
export declare const limitlessErrorMapper: LimitlessErrorMapper;
|
|
@@ -56,10 +56,12 @@ class LimitlessNormalizer {
|
|
|
56
56
|
};
|
|
57
57
|
}).sort((a, b) => a.timestamp - b.timestamp);
|
|
58
58
|
if (params.start) {
|
|
59
|
-
|
|
59
|
+
const start = params.start;
|
|
60
|
+
candles = candles.filter((c) => c.timestamp >= start.getTime());
|
|
60
61
|
}
|
|
61
62
|
if (params.end) {
|
|
62
|
-
|
|
63
|
+
const end = params.end;
|
|
64
|
+
candles = candles.filter((c) => c.timestamp <= end.getTime());
|
|
63
65
|
}
|
|
64
66
|
if (params.limit) {
|
|
65
67
|
candles = candles.slice(0, params.limit);
|
|
@@ -16,6 +16,9 @@ function mapMarketToUnified(market) {
|
|
|
16
16
|
// Use explicit key lookup — Object.entries order is not guaranteed to
|
|
17
17
|
// match the prices array.
|
|
18
18
|
if (market.tokens) {
|
|
19
|
+
if (!market.tokens.yes || !market.tokens.no) {
|
|
20
|
+
throw new Error(`[limitless] Market "${market.slug}" is missing token addresses`);
|
|
21
|
+
}
|
|
19
22
|
const prices = Array.isArray(market.prices) ? market.prices : [];
|
|
20
23
|
const yesPrice = prices[0] || 0;
|
|
21
24
|
const noPrice = prices[1] || 0;
|
|
@@ -81,7 +81,9 @@ class LimitlessWebSocket {
|
|
|
81
81
|
// 1. If we have buffered data, return it immediately
|
|
82
82
|
const buffer = this.orderbookBuffers.get(marketSlug);
|
|
83
83
|
if (buffer && buffer.length > 0) {
|
|
84
|
-
|
|
84
|
+
const entry = buffer.shift();
|
|
85
|
+
if (entry)
|
|
86
|
+
return entry;
|
|
85
87
|
}
|
|
86
88
|
// 2. Special case: If this is the FIRST call for this market and we have no data,
|
|
87
89
|
// fetch a snapshot to get things moving.
|
|
@@ -113,14 +115,30 @@ class LimitlessWebSocket {
|
|
|
113
115
|
}
|
|
114
116
|
// Wait for WebSocket update with timeout
|
|
115
117
|
try {
|
|
118
|
+
const resolverEntry = {
|
|
119
|
+
resolve: () => { },
|
|
120
|
+
reject: () => { },
|
|
121
|
+
};
|
|
116
122
|
const wsUpdatePromise = new Promise((resolve, reject) => {
|
|
123
|
+
resolverEntry.resolve = resolve;
|
|
124
|
+
resolverEntry.reject = reject;
|
|
117
125
|
if (!this.orderbookResolvers.has(marketSlug)) {
|
|
118
126
|
this.orderbookResolvers.set(marketSlug, []);
|
|
119
127
|
}
|
|
120
|
-
this.orderbookResolvers.get(marketSlug)
|
|
128
|
+
const resolvers = this.orderbookResolvers.get(marketSlug);
|
|
129
|
+
if (resolvers) {
|
|
130
|
+
resolvers.push(resolverEntry);
|
|
131
|
+
}
|
|
121
132
|
});
|
|
122
133
|
const timeoutPromise = new Promise((resolve) => {
|
|
123
134
|
setTimeout(async () => {
|
|
135
|
+
// Timeout won the race -- remove the stale resolver (#372)
|
|
136
|
+
const resolvers = this.orderbookResolvers.get(marketSlug);
|
|
137
|
+
if (resolvers) {
|
|
138
|
+
const idx = resolvers.indexOf(resolverEntry);
|
|
139
|
+
if (idx !== -1)
|
|
140
|
+
resolvers.splice(idx, 1);
|
|
141
|
+
}
|
|
124
142
|
// Timeout: fetch REST snapshot as fallback
|
|
125
143
|
try {
|
|
126
144
|
this.lastOrderbookTimestamps.set(marketSlug, Date.now());
|
|
@@ -222,6 +240,14 @@ class LimitlessWebSocket {
|
|
|
222
240
|
async close() {
|
|
223
241
|
this.orderbookCallbacks.clear();
|
|
224
242
|
this.priceCallbacks.clear();
|
|
243
|
+
// Reject any pending resolvers before clearing (#372)
|
|
244
|
+
for (const [, resolvers] of this.orderbookResolvers) {
|
|
245
|
+
for (const resolver of resolvers) {
|
|
246
|
+
resolver.reject(new Error('WebSocket closed'));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
this.orderbookResolvers.clear();
|
|
250
|
+
this.orderbookBuffers.clear();
|
|
225
251
|
await this.client.disconnect();
|
|
226
252
|
this.watcher.close();
|
|
227
253
|
}
|
|
@@ -254,7 +280,9 @@ class LimitlessWebSocket {
|
|
|
254
280
|
if (resolvers.length > 0) {
|
|
255
281
|
// If someone is waiting, give it to them immediately
|
|
256
282
|
const resolver = resolvers.shift();
|
|
257
|
-
resolver
|
|
283
|
+
if (resolver) {
|
|
284
|
+
resolver.resolve(pmxtOrderbook);
|
|
285
|
+
}
|
|
258
286
|
}
|
|
259
287
|
else {
|
|
260
288
|
// Otherwise, buffer it for the next call
|
|
@@ -262,10 +290,12 @@ class LimitlessWebSocket {
|
|
|
262
290
|
this.orderbookBuffers.set(marketSlug, []);
|
|
263
291
|
}
|
|
264
292
|
const buffer = this.orderbookBuffers.get(marketSlug);
|
|
265
|
-
buffer
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
buffer.
|
|
293
|
+
if (buffer) {
|
|
294
|
+
buffer.push(pmxtOrderbook);
|
|
295
|
+
// Keep buffer size reasonable
|
|
296
|
+
if (buffer.length > 100)
|
|
297
|
+
buffer.shift();
|
|
298
|
+
}
|
|
269
299
|
}
|
|
270
300
|
});
|
|
271
301
|
// Handle AMM price updates
|
|
@@ -32,7 +32,7 @@ export interface CancelOrderContext {
|
|
|
32
32
|
*
|
|
33
33
|
* @param orderId The Metaculus question ID to withdraw the forecast from.
|
|
34
34
|
* @param ctx HTTP client and auth context.
|
|
35
|
-
* @returns A synthetic Order with status "
|
|
35
|
+
* @returns A synthetic Order with status "canceled".
|
|
36
36
|
*
|
|
37
37
|
* @throws {AuthenticationError} If no API token is configured.
|
|
38
38
|
* @throws {ValidationError} If the orderId is not a valid numeric question ID.
|
|
@@ -24,7 +24,7 @@ const errors_2 = require("./errors");
|
|
|
24
24
|
*
|
|
25
25
|
* @param orderId The Metaculus question ID to withdraw the forecast from.
|
|
26
26
|
* @param ctx HTTP client and auth context.
|
|
27
|
-
* @returns A synthetic Order with status "
|
|
27
|
+
* @returns A synthetic Order with status "canceled".
|
|
28
28
|
*
|
|
29
29
|
* @throws {AuthenticationError} If no API token is configured.
|
|
30
30
|
* @throws {ValidationError} If the orderId is not a valid numeric question ID.
|
|
@@ -51,7 +51,7 @@ async function cancelOrder(orderId, ctx) {
|
|
|
51
51
|
data: [{ question: questionId }],
|
|
52
52
|
headers: { "Content-Type": "application/json", ...headers },
|
|
53
53
|
});
|
|
54
|
-
// 4. Return synthetic
|
|
54
|
+
// 4. Return synthetic canceled order
|
|
55
55
|
return {
|
|
56
56
|
id: `mc-withdraw-${questionId}-${Date.now()}`,
|
|
57
57
|
marketId: orderId,
|
|
@@ -59,7 +59,7 @@ async function cancelOrder(orderId, ctx) {
|
|
|
59
59
|
side: "buy",
|
|
60
60
|
type: "market",
|
|
61
61
|
amount: 1,
|
|
62
|
-
status: "
|
|
62
|
+
status: "canceled",
|
|
63
63
|
filled: 0,
|
|
64
64
|
remaining: 0,
|
|
65
65
|
timestamp: Date.now(),
|
|
@@ -11,11 +11,11 @@ import { NotFound, BadRequest, BaseError } from '../../errors';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class MetaculusErrorMapper extends ErrorMapper {
|
|
13
13
|
constructor();
|
|
14
|
-
protected mapNotFoundError(message: string, _data:
|
|
15
|
-
protected mapBadRequestError(message: string, data:
|
|
14
|
+
protected mapNotFoundError(message: string, _data: unknown): NotFound;
|
|
15
|
+
protected mapBadRequestError(message: string, data: unknown): BadRequest;
|
|
16
16
|
/**
|
|
17
17
|
* Override the top-level mapByStatusCode for Metaculus-specific auth messages.
|
|
18
18
|
*/
|
|
19
|
-
protected mapByStatusCode(status: number, message: string, data:
|
|
19
|
+
protected mapByStatusCode(status: number, message: string, data: unknown, response?: unknown): BaseError;
|
|
20
20
|
}
|
|
21
21
|
export declare const metaculusErrorMapper: MetaculusErrorMapper;
|