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
|
@@ -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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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.
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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' | '
|
|
158
|
-
// Note: PMXT has no 'expired' status; expired orders are mapped to '
|
|
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 '
|
|
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-
|
|
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-
|
|
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:
|
|
6
|
-
protected mapBadRequestError(message: string, data:
|
|
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 (
|
|
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: '
|
|
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 '
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/dist/router/Router.js
CHANGED
|
@@ -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
|
|
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 ` +
|
package/dist/router/client.d.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import type {
|
|
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<
|
|
6
|
-
getEventMatches(params: FetchEventMatchesParams): Promise<
|
|
7
|
-
browseMarketMatches(params: FetchMarketMatchesParams): Promise<
|
|
8
|
-
browseEventMatches(params: FetchEventMatchesParams): Promise<
|
|
9
|
-
searchMarkets(params?: RouterMarketSearchParams): Promise<
|
|
10
|
-
searchEvents(params?: RouterEventSearchParams): Promise<
|
|
11
|
-
getArbitrage(query?: Record<string, string>): Promise<
|
|
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 {};
|
package/dist/router/client.js
CHANGED
|
@@ -169,10 +169,14 @@ class PmxtApiClient {
|
|
|
169
169
|
return new errors_1.BadRequest(message, 'Router');
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
-
if (error
|
|
173
|
-
|
|
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
|
|
179
|
+
return new Error(String(error));
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
182
|
exports.PmxtApiClient = PmxtApiClient;
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
-
|
|
46
|
+
logger_1.logger.info(`PMXT Sidecar Server v${version} running on http://localhost:${port}`);
|
|
46
47
|
if (version.includes('-dev.')) {
|
|
47
|
-
|
|
48
|
+
logger_1.logger.info('Running in Development Mode (auto-restart enabled)');
|
|
48
49
|
}
|
|
49
|
-
|
|
50
|
+
logger_1.logger.info(`Lock file created at ${lockFile.lockPath}`);
|
|
50
51
|
// Graceful shutdown
|
|
51
52
|
const shutdown = async () => {
|
|
52
|
-
|
|
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
|
-
|
|
62
|
+
logger_1.logger.error('Failed to start server:', error);
|
|
62
63
|
process.exit(1);
|
|
63
64
|
});
|
package/dist/server/openapi.yaml
CHANGED
|
@@ -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,
|
|
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>;
|