pmxt-core 2.49.7 → 2.49.9
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/fetcher.js +2 -1
- package/dist/exchanges/gemini-titan/fetcher.d.ts +2 -4
- package/dist/exchanges/gemini-titan/fetcher.js +40 -13
- package/dist/exchanges/gemini-titan/index.js +2 -13
- package/dist/exchanges/gemini-titan/normalizer.js +3 -1
- package/dist/exchanges/gemini-titan/types.d.ts +4 -0
- package/dist/exchanges/gemini-titan/websocket.d.ts +2 -1
- package/dist/exchanges/gemini-titan/websocket.js +13 -5
- package/dist/exchanges/hyperliquid/fetcher.d.ts +8 -1
- package/dist/exchanges/kalshi/api.d.ts +1 -7
- package/dist/exchanges/kalshi/api.js +5 -11
- package/dist/exchanges/kalshi/config.d.ts +7 -7
- package/dist/exchanges/kalshi/config.js +9 -9
- package/dist/exchanges/kalshi/fetcher.d.ts +10 -0
- package/dist/exchanges/kalshi/normalizer.js +26 -7
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/limitless/fetcher.d.ts +2 -1
- package/dist/exchanges/limitless/utils.js +19 -2
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/myriad/fetcher.d.ts +11 -0
- package/dist/exchanges/myriad/fetcher.js +3 -1
- package/dist/exchanges/myriad/normalizer.js +2 -2
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/opinion/fetcher.js +3 -2
- package/dist/exchanges/opinion/index.js +8 -4
- 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 +0 -5
- package/dist/exchanges/polymarket/fetcher.d.ts +2 -0
- package/dist/exchanges/polymarket/fetcher.js +12 -7
- package/dist/exchanges/polymarket/normalizer.js +3 -0
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/probable/auth.js +1 -1
- package/dist/exchanges/probable/websocket.d.ts +1 -1
- package/dist/exchanges/probable/websocket.js +1 -1
- package/dist/router/e2e-orderbook.js +17 -16
- package/dist/server/index.js +12 -3
- package/dist/server/openapi.yaml +10 -0
- package/dist/server/utils/lock-file.js +5 -2
- package/dist/types.d.ts +6 -0
- package/package.json +3 -3
|
@@ -14,12 +14,10 @@ export declare class GeminiFetcher implements IExchangeFetcher<GeminiRawEvent, G
|
|
|
14
14
|
fetchRawOrderBook(instrumentSymbol: string): Promise<GeminiRawOrderBook | undefined>;
|
|
15
15
|
getEventTickerForSymbol(instrumentSymbol: string): string | undefined;
|
|
16
16
|
submitRawOrder(payload: Record<string, unknown>): Promise<GeminiRawOrder>;
|
|
17
|
-
cancelRawOrder(orderId: number): Promise<
|
|
18
|
-
result: string;
|
|
19
|
-
message: string;
|
|
20
|
-
}>;
|
|
17
|
+
cancelRawOrder(orderId: number): Promise<GeminiRawOrder>;
|
|
21
18
|
fetchRawActiveOrders(symbol?: string): Promise<GeminiRawOrder[]>;
|
|
22
19
|
fetchRawOrderHistory(): Promise<GeminiRawOrder[]>;
|
|
20
|
+
private fetchPaginatedOrders;
|
|
23
21
|
fetchRawPositions(): Promise<GeminiRawPosition[]>;
|
|
24
22
|
private get;
|
|
25
23
|
private postAuthenticated;
|
|
@@ -30,14 +30,21 @@ class GeminiFetcher {
|
|
|
30
30
|
limit: String(Math.min(pageSize, maxResults - allEvents.length)),
|
|
31
31
|
offset: String(offset),
|
|
32
32
|
};
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const status = params.status;
|
|
34
|
+
if (Array.isArray(status)) {
|
|
35
|
+
const statuses = status.filter(s => s !== 'all');
|
|
36
|
+
if (statuses.length > 0)
|
|
37
|
+
queryParams.status = statuses;
|
|
35
38
|
}
|
|
36
|
-
else if (
|
|
39
|
+
else if (status && status !== 'all') {
|
|
40
|
+
queryParams.status = status === 'active' ? 'active' : status;
|
|
41
|
+
}
|
|
42
|
+
else if (!status) {
|
|
37
43
|
queryParams.status = 'active';
|
|
38
44
|
}
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
const category = params.category;
|
|
46
|
+
if (category) {
|
|
47
|
+
queryParams.category = category;
|
|
41
48
|
}
|
|
42
49
|
if (params.query) {
|
|
43
50
|
queryParams.search = params.query;
|
|
@@ -96,15 +103,28 @@ class GeminiFetcher {
|
|
|
96
103
|
return this.postAuthenticated('/v1/prediction-markets/order/cancel', { orderId });
|
|
97
104
|
}
|
|
98
105
|
async fetchRawActiveOrders(symbol) {
|
|
99
|
-
|
|
100
|
-
if (symbol)
|
|
101
|
-
extra.symbol = symbol;
|
|
102
|
-
const response = await this.postAuthenticated('/v1/prediction-markets/orders/active', extra);
|
|
103
|
-
return response.orders;
|
|
106
|
+
return this.fetchPaginatedOrders('/v1/prediction-markets/orders/active', symbol ? { symbol } : {});
|
|
104
107
|
}
|
|
105
108
|
async fetchRawOrderHistory() {
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
return this.fetchPaginatedOrders('/v1/prediction-markets/orders/history', {});
|
|
110
|
+
}
|
|
111
|
+
async fetchPaginatedOrders(path, extra) {
|
|
112
|
+
const allOrders = [];
|
|
113
|
+
const limit = 100;
|
|
114
|
+
let offset = 0;
|
|
115
|
+
while (true) {
|
|
116
|
+
const response = await this.postAuthenticated(path, { ...extra, limit, offset });
|
|
117
|
+
const orders = response.orders ?? [];
|
|
118
|
+
allOrders.push(...orders);
|
|
119
|
+
const pagination = response.pagination;
|
|
120
|
+
const count = pagination?.count ?? orders.length;
|
|
121
|
+
const pageOffset = pagination?.offset ?? offset;
|
|
122
|
+
if (orders.length === 0 || pageOffset + orders.length >= count) {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
offset = pageOffset + orders.length;
|
|
126
|
+
}
|
|
127
|
+
return allOrders;
|
|
108
128
|
}
|
|
109
129
|
async fetchRawPositions() {
|
|
110
130
|
const response = await this.postAuthenticated('/v1/prediction-markets/positions', {});
|
|
@@ -116,7 +136,14 @@ class GeminiFetcher {
|
|
|
116
136
|
const url = new URL(path, this.baseUrl);
|
|
117
137
|
if (params) {
|
|
118
138
|
for (const [key, value] of Object.entries(params)) {
|
|
119
|
-
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
for (const item of value) {
|
|
141
|
+
url.searchParams.append(key, item);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
url.searchParams.set(key, value);
|
|
146
|
+
}
|
|
120
147
|
}
|
|
121
148
|
}
|
|
122
149
|
const response = await this.ctx.http.get(url.toString());
|
|
@@ -197,19 +197,8 @@ class GeminiTitanExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
197
197
|
async cancelOrder(orderId) {
|
|
198
198
|
this.requireAuth();
|
|
199
199
|
try {
|
|
200
|
-
await this.fetcher.cancelRawOrder(parseInt(orderId, 10));
|
|
201
|
-
return
|
|
202
|
-
id: orderId,
|
|
203
|
-
marketId: '',
|
|
204
|
-
outcomeId: '',
|
|
205
|
-
side: 'buy',
|
|
206
|
-
type: 'limit',
|
|
207
|
-
amount: 0,
|
|
208
|
-
status: 'canceled',
|
|
209
|
-
filled: 0,
|
|
210
|
-
remaining: 0,
|
|
211
|
-
timestamp: Date.now(),
|
|
212
|
-
};
|
|
200
|
+
const rawOrder = await this.fetcher.cancelRawOrder(parseInt(orderId, 10));
|
|
201
|
+
return this.normalizer.normalizeOrder(rawOrder);
|
|
213
202
|
}
|
|
214
203
|
catch (error) {
|
|
215
204
|
throw errors_2.geminiErrorMapper.mapError(error);
|
|
@@ -282,7 +282,9 @@ class GeminiNormalizer {
|
|
|
282
282
|
size,
|
|
283
283
|
entryPrice,
|
|
284
284
|
currentPrice,
|
|
285
|
-
unrealizedPnL:
|
|
285
|
+
unrealizedPnL: raw.unrealizedPnl != null
|
|
286
|
+
? parseFloat(raw.unrealizedPnl)
|
|
287
|
+
: (currentPrice - entryPrice) * size,
|
|
286
288
|
};
|
|
287
289
|
}
|
|
288
290
|
}
|
|
@@ -141,6 +141,9 @@ export interface GeminiRawPosition {
|
|
|
141
141
|
isAboveAutoStartThreshold?: boolean;
|
|
142
142
|
isLive?: boolean;
|
|
143
143
|
realizedPl?: string;
|
|
144
|
+
marketValue?: string;
|
|
145
|
+
unrealizedPnl?: string;
|
|
146
|
+
unrealizedPct?: number;
|
|
144
147
|
}
|
|
145
148
|
export interface GeminiRawActiveOrdersResponse {
|
|
146
149
|
orders: GeminiRawOrder[];
|
|
@@ -150,6 +153,7 @@ export interface GeminiRawActiveOrdersResponse {
|
|
|
150
153
|
count: number;
|
|
151
154
|
};
|
|
152
155
|
}
|
|
156
|
+
export type GeminiRawOrderHistoryResponse = GeminiRawActiveOrdersResponse;
|
|
153
157
|
export interface GeminiRawPositionsResponse {
|
|
154
158
|
positions: GeminiRawPosition[];
|
|
155
159
|
total?: number;
|
|
@@ -9,7 +9,7 @@ export interface GeminiWebSocketConfig {
|
|
|
9
9
|
* Gemini Titan WebSocket for real-time order book and trade streaming.
|
|
10
10
|
*
|
|
11
11
|
* Subscribes to:
|
|
12
|
-
* - {symbol}@
|
|
12
|
+
* - {symbol}@depth@100ms (full depth snapshots/deltas at 100ms intervals)
|
|
13
13
|
* - {symbol}@trade (executed trades)
|
|
14
14
|
*
|
|
15
15
|
* Auth headers are sent during the handshake if credentials are provided
|
|
@@ -42,5 +42,6 @@ export declare class GeminiWebSocket {
|
|
|
42
42
|
private resolveOrderBook;
|
|
43
43
|
watchOrderBook(symbol: string): Promise<OrderBook>;
|
|
44
44
|
watchTrades(symbol: string): Promise<Trade[]>;
|
|
45
|
+
private depthStream;
|
|
45
46
|
close(): Promise<void>;
|
|
46
47
|
}
|
|
@@ -11,7 +11,7 @@ const watch_timeout_1 = require("../../utils/watch-timeout");
|
|
|
11
11
|
* Gemini Titan WebSocket for real-time order book and trade streaming.
|
|
12
12
|
*
|
|
13
13
|
* Subscribes to:
|
|
14
|
-
* - {symbol}@
|
|
14
|
+
* - {symbol}@depth@100ms (full depth snapshots/deltas at 100ms intervals)
|
|
15
15
|
* - {symbol}@trade (executed trades)
|
|
16
16
|
*
|
|
17
17
|
* Auth headers are sent during the handshake if credentials are provided
|
|
@@ -61,7 +61,7 @@ class GeminiWebSocket {
|
|
|
61
61
|
// Resubscribe on reconnect
|
|
62
62
|
const allStreams = [];
|
|
63
63
|
for (const sym of this.subscribedDepthSymbols) {
|
|
64
|
-
allStreams.push(
|
|
64
|
+
allStreams.push(this.depthStream(sym));
|
|
65
65
|
}
|
|
66
66
|
for (const sym of this.subscribedTradeSymbols) {
|
|
67
67
|
allStreams.push(`${sym}@trade`);
|
|
@@ -132,7 +132,7 @@ class GeminiWebSocket {
|
|
|
132
132
|
// -------------------------------------------------------------------------
|
|
133
133
|
handleMessage(message) {
|
|
134
134
|
// Gemini sends flat objects, NOT wrapped in { stream, data }.
|
|
135
|
-
// Depth snapshots: { lastUpdateId,
|
|
135
|
+
// Depth snapshots: { lastUpdateId, s, bids, asks }
|
|
136
136
|
// Depth deltas: { e, E, s, U, u, b, a }
|
|
137
137
|
// Trades: { E, s, t, p, q, m }
|
|
138
138
|
// Confirmations: { id, status: 200 }
|
|
@@ -151,7 +151,12 @@ class GeminiWebSocket {
|
|
|
151
151
|
handleDepthSnapshot(data) {
|
|
152
152
|
// symbol comes back lowercase from the API, but we subscribed with
|
|
153
153
|
// uppercase. Normalize to uppercase for resolver lookup.
|
|
154
|
-
const
|
|
154
|
+
const rawSymbol = data.s ?? data.symbol;
|
|
155
|
+
if (typeof rawSymbol !== 'string' || rawSymbol.length === 0) {
|
|
156
|
+
logger_1.logger.warn('[gemini-titan] depth snapshot missing symbol field');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const symbol = rawSymbol.toUpperCase();
|
|
155
160
|
const bids = (data.bids ?? []).map((level) => ({
|
|
156
161
|
price: parseFloat(level[0]),
|
|
157
162
|
size: parseFloat(level[1]),
|
|
@@ -240,7 +245,7 @@ class GeminiWebSocket {
|
|
|
240
245
|
});
|
|
241
246
|
}
|
|
242
247
|
else {
|
|
243
|
-
this.sendSubscribe([
|
|
248
|
+
this.sendSubscribe([this.depthStream(symbol)]);
|
|
244
249
|
}
|
|
245
250
|
const dataPromise = new Promise((resolve, reject) => {
|
|
246
251
|
if (!this.orderBookResolvers.has(symbol)) {
|
|
@@ -286,6 +291,9 @@ class GeminiWebSocket {
|
|
|
286
291
|
});
|
|
287
292
|
return (0, watch_timeout_1.withWatchTimeout)(dataPromise, this.config.watchTimeoutMs ?? watch_timeout_1.DEFAULT_WATCH_TIMEOUT_MS, `watchTrades('${symbol}')`);
|
|
288
293
|
}
|
|
294
|
+
depthStream(symbol) {
|
|
295
|
+
return `${symbol}@depth@100ms`;
|
|
296
|
+
}
|
|
289
297
|
async close() {
|
|
290
298
|
this.isTerminated = true;
|
|
291
299
|
if (this.reconnectTimer) {
|
|
@@ -9,7 +9,6 @@ export interface HyperliquidRawOutcome {
|
|
|
9
9
|
name: string;
|
|
10
10
|
description: string;
|
|
11
11
|
sideSpecs: HyperliquidRawSideSpec[];
|
|
12
|
-
quoteToken: string;
|
|
13
12
|
}
|
|
14
13
|
export interface HyperliquidRawQuestion {
|
|
15
14
|
question: number;
|
|
@@ -91,6 +90,7 @@ export interface HyperliquidRawPosition {
|
|
|
91
90
|
leverage: {
|
|
92
91
|
type: string;
|
|
93
92
|
value: number;
|
|
93
|
+
rawUsd?: string;
|
|
94
94
|
};
|
|
95
95
|
liquidationPx: string | null;
|
|
96
96
|
marginUsed: string;
|
|
@@ -99,6 +99,11 @@ export interface HyperliquidRawPosition {
|
|
|
99
99
|
returnOnEquity: string;
|
|
100
100
|
szi: string;
|
|
101
101
|
unrealizedPnl: string;
|
|
102
|
+
cumFunding?: {
|
|
103
|
+
allTime?: string;
|
|
104
|
+
sinceChange?: string;
|
|
105
|
+
sinceOpen?: string;
|
|
106
|
+
};
|
|
102
107
|
}
|
|
103
108
|
export interface HyperliquidRawUserState {
|
|
104
109
|
assetPositions: Array<{
|
|
@@ -117,6 +122,8 @@ export interface HyperliquidRawUserState {
|
|
|
117
122
|
totalNtlPos: string;
|
|
118
123
|
totalRawUsd: string;
|
|
119
124
|
};
|
|
125
|
+
crossMaintenanceMarginUsed?: string;
|
|
126
|
+
time?: number;
|
|
120
127
|
withdrawable: string;
|
|
121
128
|
}
|
|
122
129
|
export interface HyperliquidRawOutcomeWithQuestion {
|
|
@@ -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-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.351Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const kalshiApiSpec: {
|
|
@@ -11,12 +11,6 @@ export declare const kalshiApiSpec: {
|
|
|
11
11
|
};
|
|
12
12
|
servers: {
|
|
13
13
|
url: string;
|
|
14
|
-
variables: {
|
|
15
|
-
env: {
|
|
16
|
-
default: string;
|
|
17
|
-
enum: string[];
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
14
|
}[];
|
|
21
15
|
paths: {
|
|
22
16
|
"/historical/cutoff": {
|
|
@@ -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-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.351Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.kalshiApiSpec = {
|
|
@@ -14,16 +14,10 @@ exports.kalshiApiSpec = {
|
|
|
14
14
|
},
|
|
15
15
|
"servers": [
|
|
16
16
|
{
|
|
17
|
-
"url": "https://
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"enum": [
|
|
22
|
-
"api",
|
|
23
|
-
"demo-api"
|
|
24
|
-
]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
17
|
+
"url": "https://external-api.kalshi.com/trade-api/v2"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"url": "https://external-api.demo.kalshi.co/trade-api/v2"
|
|
27
21
|
}
|
|
28
22
|
],
|
|
29
23
|
"paths": {
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
* into core/src/exchanges/kalshi/api.ts by `npm run fetch:openapi`.
|
|
11
11
|
* Do NOT put runtime config into api.ts — it will be overwritten.
|
|
12
12
|
*
|
|
13
|
-
* Environment mapping
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Environment mapping:
|
|
14
|
+
* production: https://external-api.kalshi.com
|
|
15
|
+
* demo/paper: https://external-api.demo.kalshi.co
|
|
16
16
|
*/
|
|
17
17
|
export declare const KALSHI_PROD_API_URL: string;
|
|
18
18
|
export declare const KALSHI_DEMO_API_URL: string;
|
|
19
|
-
export declare const KALSHI_PROD_WS_URL = "wss://
|
|
20
|
-
export declare const KALSHI_DEMO_WS_URL = "wss://
|
|
19
|
+
export declare const KALSHI_PROD_WS_URL = "wss://external-api-ws.kalshi.com/trade-api/ws/v2";
|
|
20
|
+
export declare const KALSHI_DEMO_WS_URL = "wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2";
|
|
21
21
|
export declare const KALSHI_PATHS: {
|
|
22
22
|
TRADE_API: string;
|
|
23
23
|
EVENTS: string;
|
|
@@ -39,12 +39,12 @@ export interface KalshiApiConfig {
|
|
|
39
39
|
/**
|
|
40
40
|
* Return a typed config object for the requested environment.
|
|
41
41
|
*
|
|
42
|
-
* @param demoMode - Pass `true` to target
|
|
42
|
+
* @param demoMode - Pass `true` to target external-api.demo.kalshi.co.
|
|
43
43
|
*
|
|
44
44
|
* @example
|
|
45
45
|
* ```typescript
|
|
46
46
|
* const config = getKalshiConfig(true);
|
|
47
|
-
* // config.apiUrl === "https://
|
|
47
|
+
* // config.apiUrl === "https://external-api.demo.kalshi.co"
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
50
|
export declare function getKalshiConfig(demoMode?: boolean, baseUrlOverride?: string): KalshiApiConfig;
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* into core/src/exchanges/kalshi/api.ts by `npm run fetch:openapi`.
|
|
12
12
|
* Do NOT put runtime config into api.ts — it will be overwritten.
|
|
13
13
|
*
|
|
14
|
-
* Environment mapping
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Environment mapping:
|
|
15
|
+
* production: https://external-api.kalshi.com
|
|
16
|
+
* demo/paper: https://external-api.demo.kalshi.co
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.KALSHI_PATHS = exports.KALSHI_DEMO_WS_URL = exports.KALSHI_PROD_WS_URL = exports.KALSHI_DEMO_API_URL = exports.KALSHI_PROD_API_URL = void 0;
|
|
@@ -24,10 +24,10 @@ exports.getSeriesUrl = getSeriesUrl;
|
|
|
24
24
|
exports.getPortfolioUrl = getPortfolioUrl;
|
|
25
25
|
exports.getMarketsUrl = getMarketsUrl;
|
|
26
26
|
// ── Base URL constants ────────────────────────────────────────────────────────
|
|
27
|
-
exports.KALSHI_PROD_API_URL = process.env.KALSHI_BASE_URL || "https://
|
|
28
|
-
exports.KALSHI_DEMO_API_URL = process.env.KALSHI_DEMO_BASE_URL || "https://
|
|
29
|
-
exports.KALSHI_PROD_WS_URL = "wss://
|
|
30
|
-
exports.KALSHI_DEMO_WS_URL = "wss://
|
|
27
|
+
exports.KALSHI_PROD_API_URL = process.env.KALSHI_BASE_URL || "https://external-api.kalshi.com";
|
|
28
|
+
exports.KALSHI_DEMO_API_URL = process.env.KALSHI_DEMO_BASE_URL || "https://external-api.demo.kalshi.co";
|
|
29
|
+
exports.KALSHI_PROD_WS_URL = "wss://external-api-ws.kalshi.com/trade-api/ws/v2";
|
|
30
|
+
exports.KALSHI_DEMO_WS_URL = "wss://external-api-ws.demo.kalshi.co/trade-api/ws/v2";
|
|
31
31
|
// ── Path constants ────────────────────────────────────────────────────────────
|
|
32
32
|
exports.KALSHI_PATHS = {
|
|
33
33
|
TRADE_API: "/trade-api/v2",
|
|
@@ -42,12 +42,12 @@ exports.KALSHI_PATHS = {
|
|
|
42
42
|
/**
|
|
43
43
|
* Return a typed config object for the requested environment.
|
|
44
44
|
*
|
|
45
|
-
* @param demoMode - Pass `true` to target
|
|
45
|
+
* @param demoMode - Pass `true` to target external-api.demo.kalshi.co.
|
|
46
46
|
*
|
|
47
47
|
* @example
|
|
48
48
|
* ```typescript
|
|
49
49
|
* const config = getKalshiConfig(true);
|
|
50
|
-
* // config.apiUrl === "https://
|
|
50
|
+
* // config.apiUrl === "https://external-api.demo.kalshi.co"
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
53
|
function getKalshiConfig(demoMode = false, baseUrlOverride) {
|
|
@@ -13,6 +13,16 @@ export interface KalshiRawMarket {
|
|
|
13
13
|
last_price_dollars?: string;
|
|
14
14
|
yes_ask_dollars?: string;
|
|
15
15
|
yes_bid_dollars?: string;
|
|
16
|
+
no_ask_dollars?: string;
|
|
17
|
+
no_bid_dollars?: string;
|
|
18
|
+
response_price_units?: string;
|
|
19
|
+
market_type?: string;
|
|
20
|
+
mve_collection_ticker?: string;
|
|
21
|
+
mve_selected_legs?: Array<{
|
|
22
|
+
event_ticker: string;
|
|
23
|
+
market_ticker: string;
|
|
24
|
+
side: string;
|
|
25
|
+
}>;
|
|
16
26
|
yes_ask_size_fp?: string;
|
|
17
27
|
yes_bid_size_fp?: string;
|
|
18
28
|
rules_primary?: string;
|
|
@@ -18,8 +18,18 @@ const KALSHI_PROMOTED_MARKET_KEYS = [
|
|
|
18
18
|
'volume_24h_fp', 'volume_24h', 'volume', 'volume_fp',
|
|
19
19
|
'liquidity_dollars', 'liquidity', 'open_interest_fp', 'open_interest',
|
|
20
20
|
'status', 'last_price_dollars', 'previous_price_dollars',
|
|
21
|
-
'yes_ask_dollars', 'yes_bid_dollars', '
|
|
21
|
+
'yes_ask_dollars', 'yes_bid_dollars', 'no_ask_dollars', 'no_bid_dollars',
|
|
22
|
+
'response_price_units', 'last_price', 'yes_ask', 'yes_bid',
|
|
22
23
|
];
|
|
24
|
+
function parseKalshiPrice(value, responseUnits) {
|
|
25
|
+
if (value == null)
|
|
26
|
+
return undefined;
|
|
27
|
+
const parsed = typeof value === 'number' ? value : parseFloat(value);
|
|
28
|
+
if (!Number.isFinite(parsed))
|
|
29
|
+
return undefined;
|
|
30
|
+
const normalizedUnits = responseUnits?.toLowerCase();
|
|
31
|
+
return normalizedUnits === 'cent' || normalizedUnits === 'cents' ? parsed / 100 : parsed;
|
|
32
|
+
}
|
|
23
33
|
class KalshiNormalizer {
|
|
24
34
|
normalizeMarket(raw) {
|
|
25
35
|
// This normalizes a single-market event. For multi-market events, use normalizeMarketsFromEvent.
|
|
@@ -42,15 +52,19 @@ class KalshiNormalizer {
|
|
|
42
52
|
return null;
|
|
43
53
|
// Kalshi API v2 migrated from cent integers to FixedPointDollars strings.
|
|
44
54
|
// Prefer the _dollars fields; fall back to deprecated cent fields.
|
|
55
|
+
const yesBid = parseKalshiPrice(market.yes_bid_dollars, market.response_price_units);
|
|
56
|
+
const yesAsk = parseKalshiPrice(market.yes_ask_dollars, market.response_price_units);
|
|
57
|
+
const noBid = parseKalshiPrice(market.no_bid_dollars, market.response_price_units);
|
|
58
|
+
const noAsk = parseKalshiPrice(market.no_ask_dollars, market.response_price_units);
|
|
45
59
|
let price = 0;
|
|
46
60
|
if (market.last_price_dollars != null) {
|
|
47
|
-
price =
|
|
61
|
+
price = parseKalshiPrice(market.last_price_dollars, market.response_price_units) ?? 0;
|
|
48
62
|
}
|
|
49
|
-
else if (
|
|
50
|
-
price = (
|
|
63
|
+
else if (yesAsk != null && yesBid != null) {
|
|
64
|
+
price = (yesAsk + yesBid) / 2;
|
|
51
65
|
}
|
|
52
|
-
else if (
|
|
53
|
-
price =
|
|
66
|
+
else if (yesAsk != null) {
|
|
67
|
+
price = yesAsk;
|
|
54
68
|
}
|
|
55
69
|
else if (market.last_price) {
|
|
56
70
|
price = (0, price_1.fromKalshiCents)(market.last_price);
|
|
@@ -66,6 +80,9 @@ class KalshiNormalizer {
|
|
|
66
80
|
if (market.previous_price_dollars != null && market.last_price_dollars != null) {
|
|
67
81
|
priceChange = parseFloat(market.last_price_dollars) - parseFloat(market.previous_price_dollars);
|
|
68
82
|
}
|
|
83
|
+
const noPrice = noAsk != null && noBid != null
|
|
84
|
+
? (noAsk + noBid) / 2
|
|
85
|
+
: noAsk ?? noBid ?? (0, price_1.invertKalshiUnified)(price);
|
|
69
86
|
const outcomes = [
|
|
70
87
|
{
|
|
71
88
|
outcomeId: market.ticker,
|
|
@@ -73,13 +90,15 @@ class KalshiNormalizer {
|
|
|
73
90
|
label: candidateName || 'Yes',
|
|
74
91
|
price,
|
|
75
92
|
priceChange24h: priceChange,
|
|
93
|
+
metadata: { bid: yesBid, ask: yesAsk },
|
|
76
94
|
},
|
|
77
95
|
{
|
|
78
96
|
outcomeId: `${market.ticker}-NO`,
|
|
79
97
|
marketId: market.ticker,
|
|
80
98
|
label: candidateName ? `Not ${candidateName}` : 'No',
|
|
81
|
-
price:
|
|
99
|
+
price: noPrice,
|
|
82
100
|
priceChange24h: -priceChange,
|
|
101
|
+
metadata: { bid: noBid, ask: noAsk },
|
|
83
102
|
},
|
|
84
103
|
];
|
|
85
104
|
const unifiedTags = [];
|
|
@@ -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-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.389Z
|
|
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-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.389Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.limitlessApiSpec = {
|
|
@@ -14,7 +14,7 @@ exports.DEFAULT_LIMITLESS_API_URL = 'https://api.limitless.exchange';
|
|
|
14
14
|
const LIMITLESS_PROMOTED_MARKET_KEYS = [
|
|
15
15
|
'slug', 'title', 'question', 'description',
|
|
16
16
|
'tokens', 'prices',
|
|
17
|
-
'expirationTimestamp',
|
|
17
|
+
'expirationDate', 'expirationTimestamp',
|
|
18
18
|
'volumeFormatted', 'volume',
|
|
19
19
|
'logo',
|
|
20
20
|
'categories', 'tags',
|
|
@@ -100,7 +100,7 @@ function mapMarketToUnified(market, context = {}) {
|
|
|
100
100
|
description: market.description || resolvedContext.eventDescription,
|
|
101
101
|
slug: market.slug,
|
|
102
102
|
outcomes: outcomes,
|
|
103
|
-
resolutionDate:
|
|
103
|
+
resolutionDate: parseLimitlessExpiration(market),
|
|
104
104
|
volume24h: Number(market.volumeFormatted || 0),
|
|
105
105
|
volume: Number(market.volume || 0),
|
|
106
106
|
liquidity: 0, // Not directly in the flat market list
|
|
@@ -115,6 +115,23 @@ function mapMarketToUnified(market, context = {}) {
|
|
|
115
115
|
(0, market_utils_1.addBinaryOutcomes)(um);
|
|
116
116
|
return um;
|
|
117
117
|
}
|
|
118
|
+
function parseLimitlessExpiration(market) {
|
|
119
|
+
if (typeof market.expirationDate === 'string' && market.expirationDate.trim()) {
|
|
120
|
+
return new Date(market.expirationDate);
|
|
121
|
+
}
|
|
122
|
+
const rawTimestamp = market.expirationTimestamp;
|
|
123
|
+
if (typeof rawTimestamp === 'number' && Number.isFinite(rawTimestamp)) {
|
|
124
|
+
return new Date(rawTimestamp < 1e12 ? rawTimestamp * 1000 : rawTimestamp);
|
|
125
|
+
}
|
|
126
|
+
if (typeof rawTimestamp === 'string' && rawTimestamp.trim()) {
|
|
127
|
+
const numeric = Number(rawTimestamp);
|
|
128
|
+
if (Number.isFinite(numeric)) {
|
|
129
|
+
return new Date(numeric < 1e12 ? numeric * 1000 : numeric);
|
|
130
|
+
}
|
|
131
|
+
return new Date(rawTimestamp);
|
|
132
|
+
}
|
|
133
|
+
return new Date();
|
|
134
|
+
}
|
|
118
135
|
function getText(value) {
|
|
119
136
|
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
120
137
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.400Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const myriadApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.myriadApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.400Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.myriadApiSpec = {
|
|
@@ -4,15 +4,25 @@ export interface MyriadRawMarket {
|
|
|
4
4
|
id: number;
|
|
5
5
|
networkId: number;
|
|
6
6
|
title?: string;
|
|
7
|
+
shortName?: string;
|
|
7
8
|
description?: string;
|
|
8
9
|
slug?: string;
|
|
9
10
|
imageUrl?: string;
|
|
10
11
|
expiresAt?: string;
|
|
12
|
+
publishedAt?: string;
|
|
11
13
|
volume24h?: number;
|
|
12
14
|
volume?: number;
|
|
15
|
+
volumeNotional?: number;
|
|
16
|
+
volumeNotional24h?: number;
|
|
13
17
|
liquidity?: number;
|
|
14
18
|
eventId?: number;
|
|
19
|
+
outcomeIndex?: number;
|
|
20
|
+
tradingModel?: string;
|
|
21
|
+
negRisk?: boolean;
|
|
22
|
+
executionMode?: string;
|
|
23
|
+
oracle?: Record<string, unknown>;
|
|
15
24
|
topics?: string[];
|
|
25
|
+
tags?: string[];
|
|
16
26
|
outcomes?: MyriadRawOutcome[];
|
|
17
27
|
[key: string]: unknown;
|
|
18
28
|
}
|
|
@@ -39,6 +49,7 @@ export interface MyriadRawQuestion {
|
|
|
39
49
|
export interface MyriadRawTradeEvent {
|
|
40
50
|
action?: string;
|
|
41
51
|
blockNumber?: number;
|
|
52
|
+
txId?: string;
|
|
42
53
|
timestamp?: number;
|
|
43
54
|
value?: number;
|
|
44
55
|
shares?: number;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MyriadFetcher = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
5
|
const errors_1 = require("./errors");
|
|
6
|
+
const logger_1 = require("../../utils/logger");
|
|
6
7
|
const MAX_PAGE_SIZE = 100;
|
|
7
8
|
class MyriadFetcher {
|
|
8
9
|
ctx;
|
|
@@ -129,7 +130,8 @@ class MyriadFetcher {
|
|
|
129
130
|
return null;
|
|
130
131
|
return { bids: data.bids || [], asks: data.asks || [] };
|
|
131
132
|
}
|
|
132
|
-
catch {
|
|
133
|
+
catch (err) {
|
|
134
|
+
logger_1.logger.warn('MyriadFetcher: fetchClobOrderBook failed', { marketId, error: String(err) });
|
|
133
135
|
return null;
|
|
134
136
|
}
|
|
135
137
|
}
|
|
@@ -203,7 +203,7 @@ class MyriadNormalizer {
|
|
|
203
203
|
}
|
|
204
204
|
normalizeTrade(raw, index) {
|
|
205
205
|
return {
|
|
206
|
-
id: `${raw.blockNumber || raw.timestamp}-${index}`,
|
|
206
|
+
id: raw.txId ?? `${raw.blockNumber || raw.timestamp}-${index}`,
|
|
207
207
|
timestamp: (raw.timestamp || 0) * 1000,
|
|
208
208
|
price: (0, price_1.resolveMyriadPrice)(raw),
|
|
209
209
|
amount: Number(raw.shares || 0),
|
|
@@ -212,7 +212,7 @@ class MyriadNormalizer {
|
|
|
212
212
|
}
|
|
213
213
|
normalizeUserTrade(raw, index) {
|
|
214
214
|
return {
|
|
215
|
-
id: `${raw.blockNumber || raw.timestamp}-${index}`,
|
|
215
|
+
id: raw.txId ?? `${raw.blockNumber || raw.timestamp}-${index}`,
|
|
216
216
|
timestamp: (raw.timestamp || 0) * 1000,
|
|
217
217
|
price: (0, price_1.resolveMyriadPrice)(raw),
|
|
218
218
|
amount: Number(raw.shares || 0),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.403Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const opinionApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.opinionApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.403Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.opinionApiSpec = {
|
|
@@ -297,8 +297,9 @@ class OpinionFetcher {
|
|
|
297
297
|
* Validates that the API response envelope indicates success (code === 0).
|
|
298
298
|
*/
|
|
299
299
|
assertSuccess(data) {
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
const code = data.code ?? data.errno;
|
|
301
|
+
if (code !== 0) {
|
|
302
|
+
throw new Error(`Opinion API error (code ${code ?? 'unknown'}): ${data.msg || data.errmsg || 'Unknown error'}`);
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
}
|
|
@@ -323,8 +323,10 @@ class OpinionExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
323
323
|
await auth.ensureTradingEnabled();
|
|
324
324
|
const client = await auth.getClobClient();
|
|
325
325
|
const response = await client.placeOrder(built.raw);
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
const errorCode = response.code ?? response.errno;
|
|
327
|
+
const errorMessage = response.msg || response.errmsg || 'Unknown error';
|
|
328
|
+
if (errorCode !== 0) {
|
|
329
|
+
throw new Error(`Order submission failed: ${errorMessage} (code: ${errorCode ?? 'unknown'})`);
|
|
328
330
|
}
|
|
329
331
|
const result = response.result;
|
|
330
332
|
const orderId = result?.orderId ?? result?.order_id ?? 'unknown';
|
|
@@ -356,8 +358,10 @@ class OpinionExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
|
356
358
|
const auth = this.ensureTradeAuth();
|
|
357
359
|
const client = await auth.getClobClient();
|
|
358
360
|
const response = await client.cancelOrder(orderId);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
+
const errorCode = response.code ?? response.errno;
|
|
362
|
+
const errorMessage = response.msg || response.errmsg || 'Unknown error';
|
|
363
|
+
if (errorCode !== 0) {
|
|
364
|
+
throw new Error(`Order cancellation failed: ${errorMessage} (code: ${errorCode ?? 'unknown'})`);
|
|
361
365
|
}
|
|
362
366
|
return {
|
|
363
367
|
id: orderId,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.359Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketClobSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketClobSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.359Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketClobSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.372Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketDataSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketDataSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.372Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketDataSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
3
|
-
* Generated at: 2026-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.369Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketGammaSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketGammaSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
6
|
-
* Generated at: 2026-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.369Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketGammaSpec = {
|
|
@@ -91,7 +91,6 @@ class PolymarketAuth {
|
|
|
91
91
|
// 2. If that fails (e.g. 404 or 400), try to CREATE new ones.
|
|
92
92
|
let creds;
|
|
93
93
|
try {
|
|
94
|
-
// console.log('Trying to derive existing API key...');
|
|
95
94
|
creds = await l1Client.deriveApiKey();
|
|
96
95
|
if (!creds || !creds.key || !creds.secret || !creds.passphrase) {
|
|
97
96
|
// If derived creds are missing, throw to trigger catch -> create
|
|
@@ -111,7 +110,6 @@ class PolymarketAuth {
|
|
|
111
110
|
logger_1.logger.error('Incomplete credentials after derivation', { hasKey: !!creds?.key, hasSecret: !!creds?.secret, hasPassphrase: !!creds?.passphrase });
|
|
112
111
|
throw new Error('Authentication failed: Derived credentials are incomplete.');
|
|
113
112
|
}
|
|
114
|
-
// console.log(`[PolymarketAuth] Successfully obtained API credentials for key ${creds.key.substring(0, 8)}...`);
|
|
115
113
|
this.apiCreds = creds;
|
|
116
114
|
return creds;
|
|
117
115
|
}
|
|
@@ -138,14 +136,12 @@ class PolymarketAuth {
|
|
|
138
136
|
timeout: 10_000,
|
|
139
137
|
});
|
|
140
138
|
const profile = response.data;
|
|
141
|
-
// console.log(`[PolymarketAuth] Profile for ${address}:`, JSON.stringify(profile));
|
|
142
139
|
if (profile && profile.proxyAddress) {
|
|
143
140
|
this.discoveredProxyAddress = profile.proxyAddress;
|
|
144
141
|
// Determine signature type.
|
|
145
142
|
// Polymarket usually uses 1 for their own proxy and 2 for Gnosis Safe (which is what their new profiles use).
|
|
146
143
|
// If it's a proxy address but we don't know the type, 1 is a safe default for Polymarket.
|
|
147
144
|
this.discoveredSignatureType = profile.isGnosisSafe ? SIG_TYPE_GNOSIS_SAFE : SIG_TYPE_POLY_PROXY;
|
|
148
|
-
// console.log(`[PolymarketAuth] Auto-discovered proxy for ${address}: ${this.discoveredProxyAddress} (Type: ${this.discoveredSignatureType})`);
|
|
149
145
|
if (!this.discoveredProxyAddress || this.discoveredSignatureType === undefined) {
|
|
150
146
|
throw new Error('[polymarket] Proxy discovery incomplete — missing proxyAddress or signatureType');
|
|
151
147
|
}
|
|
@@ -250,7 +246,6 @@ class PolymarketAuth {
|
|
|
250
246
|
const finalProxyAddress = proxyAddress || signerAddress;
|
|
251
247
|
const finalSignatureType = signatureType;
|
|
252
248
|
// Create L2-authenticated client
|
|
253
|
-
// console.log(`[PolymarketAuth] Initializing ClobClient | Signer: ${signerAddress} | Funder: ${finalProxyAddress} | SigType: ${finalSignatureType}`);
|
|
254
249
|
this.clobClient = new clob_client_v2_1.ClobClient({
|
|
255
250
|
host: this.host,
|
|
256
251
|
chain: config_1.POLYMARKET_CHAIN_ID,
|
|
@@ -55,6 +55,8 @@ export interface PolymarketRawOrderBook {
|
|
|
55
55
|
bids?: PolymarketRawOrderBookLevel[];
|
|
56
56
|
asks?: PolymarketRawOrderBookLevel[];
|
|
57
57
|
timestamp?: string | number;
|
|
58
|
+
neg_risk?: boolean;
|
|
59
|
+
last_trade_price?: string;
|
|
58
60
|
}
|
|
59
61
|
export interface PolymarketRawTrade {
|
|
60
62
|
id?: string;
|
|
@@ -58,6 +58,8 @@ function ensureDate(d) {
|
|
|
58
58
|
return new Date(d);
|
|
59
59
|
}
|
|
60
60
|
const GAMMA_MARKETS_URL = process.env.POLYMARKET_GAMMA_MARKETS_URL || 'https://gamma-api.polymarket.com/markets';
|
|
61
|
+
// Cloudflare blocks large single-shot Gamma lookups (~50 condition_ids / ~3.5KB query).
|
|
62
|
+
const GAMMA_CONDITION_IDS_BATCH_SIZE = 40;
|
|
61
63
|
class PolymarketFetcher {
|
|
62
64
|
ctx;
|
|
63
65
|
http;
|
|
@@ -246,14 +248,17 @@ class PolymarketFetcher {
|
|
|
246
248
|
}));
|
|
247
249
|
}
|
|
248
250
|
async resolveConditionIds(conditionIds) {
|
|
249
|
-
const response = await this.http.get(GAMMA_MARKETS_URL, {
|
|
250
|
-
params: { condition_ids: conditionIds.join(',') },
|
|
251
|
-
});
|
|
252
|
-
const markets = Array.isArray(response.data) ? response.data : [];
|
|
253
251
|
const result = new Map();
|
|
254
|
-
for (
|
|
255
|
-
|
|
256
|
-
|
|
252
|
+
for (let offset = 0; offset < conditionIds.length; offset += GAMMA_CONDITION_IDS_BATCH_SIZE) {
|
|
253
|
+
const batch = conditionIds.slice(offset, offset + GAMMA_CONDITION_IDS_BATCH_SIZE);
|
|
254
|
+
const response = await this.http.get(GAMMA_MARKETS_URL, {
|
|
255
|
+
params: { condition_ids: batch.join(',') },
|
|
256
|
+
});
|
|
257
|
+
const markets = Array.isArray(response.data) ? response.data : [];
|
|
258
|
+
for (const market of markets) {
|
|
259
|
+
if (market.conditionId && market.id) {
|
|
260
|
+
result.set(market.conditionId, String(market.id));
|
|
261
|
+
}
|
|
257
262
|
}
|
|
258
263
|
}
|
|
259
264
|
return result;
|
|
@@ -139,6 +139,9 @@ class PolymarketNormalizer {
|
|
|
139
139
|
bids,
|
|
140
140
|
asks,
|
|
141
141
|
timestamp: raw.timestamp ? (typeof raw.timestamp === 'string' ? (isFinite(Number(raw.timestamp)) ? Number(raw.timestamp) : new Date(raw.timestamp).getTime()) : Number(raw.timestamp)) : Date.now(),
|
|
142
|
+
isNegRisk: raw.neg_risk ?? false,
|
|
143
|
+
lastTradePrice: raw.last_trade_price != null ? parseFloat(raw.last_trade_price) : undefined,
|
|
144
|
+
sourceMetadata: (0, metadata_1.buildSourceMetadata)(raw, ['asset_id', 'bids', 'asks', 'timestamp', 'neg_risk', 'last_trade_price']),
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
normalizeTrade(raw, index) {
|
|
@@ -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-06-
|
|
3
|
+
* Generated at: 2026-06-11T07:35:21.396Z
|
|
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-06-
|
|
6
|
+
* Generated at: 2026-06-11T07:35:21.396Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.probableApiSpec = {
|
|
@@ -54,7 +54,7 @@ class ProbableAuth {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
const baseUrl = process.env.PROBABLE_BASE_URL || 'https://api.probable.markets/public/api/v1';
|
|
57
|
+
const baseUrl = process.env.PROBABLE_BASE_URL || 'https://market-api.probable.markets/public/api/v1';
|
|
58
58
|
this.clobClient = (0, clob_1.createClobClient)({
|
|
59
59
|
chainId,
|
|
60
60
|
baseUrl,
|
|
@@ -2,7 +2,7 @@ import { OrderBook } from '../../types';
|
|
|
2
2
|
export interface ProbableWebSocketConfig {
|
|
3
3
|
/** WebSocket URL (default: wss://ws.probable.markets/public/api/v1) */
|
|
4
4
|
wsUrl?: string;
|
|
5
|
-
/** Base URL for the CLOB client (default: https://api.probable.markets/public/api/v1) */
|
|
5
|
+
/** Base URL for the CLOB client (default: https://market-api.probable.markets/public/api/v1) */
|
|
6
6
|
baseUrl?: string;
|
|
7
7
|
/** Chain ID (default: 56 for BSC mainnet) */
|
|
8
8
|
chainId?: number;
|
|
@@ -22,7 +22,7 @@ class ProbableWebSocket {
|
|
|
22
22
|
return this.client;
|
|
23
23
|
const chainId = this.config.chainId || parseInt(process.env.PROBABLE_CHAIN_ID || String(config_1.PROBABLE_CHAIN_ID), 10);
|
|
24
24
|
const wsUrl = this.config.wsUrl || process.env.PROBABLE_WS_URL || 'wss://ws.probable.markets/public/api/v1';
|
|
25
|
-
const baseUrl = this.config.baseUrl || process.env.PROBABLE_BASE_URL || 'https://api.probable.markets/public/api/v1';
|
|
25
|
+
const baseUrl = this.config.baseUrl || process.env.PROBABLE_BASE_URL || 'https://market-api.probable.markets/public/api/v1';
|
|
26
26
|
// Dynamically import @prob/clob using eval to bypass TS compilation to require()
|
|
27
27
|
// which forces native import() usage, resolving ESM/CJS issues
|
|
28
28
|
const { createClobClient: createClient } = await eval('import("@prob/clob")');
|
|
@@ -9,10 +9,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
9
9
|
const Router_1 = require("./Router");
|
|
10
10
|
const polymarket_1 = require("../exchanges/polymarket");
|
|
11
11
|
const limitless_1 = require("../exchanges/limitless");
|
|
12
|
+
const logger_1 = require("../utils/logger");
|
|
12
13
|
async function main() {
|
|
13
14
|
const apiKey = process.env.PMXT_API_KEY;
|
|
14
15
|
if (!apiKey) {
|
|
15
|
-
|
|
16
|
+
logger_1.logger.error('Set PMXT_API_KEY');
|
|
16
17
|
process.exit(1);
|
|
17
18
|
}
|
|
18
19
|
const polymarket = new polymarket_1.PolymarketExchange({});
|
|
@@ -23,36 +24,36 @@ async function main() {
|
|
|
23
24
|
});
|
|
24
25
|
// Morocco FIFA World Cup market on Polymarket
|
|
25
26
|
const marketId = 'f017596d-4d53-49d5-a7d6-36ed9c37fdc4';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
logger_1.logger.info('Fetching unified orderbook for Morocco (Polymarket + Limitless)...');
|
|
28
|
+
logger_1.logger.info(`Input market ID: ${marketId}`);
|
|
29
|
+
logger_1.logger.info('---');
|
|
29
30
|
const book = await router.fetchOrderBook(marketId, undefined, { side: 'yes' });
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
logger_1.logger.info(`Bids: ${book.bids.length} levels`);
|
|
32
|
+
logger_1.logger.info(`Asks: ${book.asks.length} levels`);
|
|
33
|
+
logger_1.logger.info('Top 5 bids:', { bids: book.bids.slice(0, 5) });
|
|
34
|
+
logger_1.logger.info('Top 5 asks:', { asks: book.asks.slice(0, 5) });
|
|
34
35
|
// Verify we got data from BOTH exchanges
|
|
35
36
|
// Polymarket top bid was 0.016, Limitless had 0.002
|
|
36
37
|
// If merged correctly, we should see both
|
|
37
38
|
const hasPoly = book.bids.some((b) => b.price === 0.016);
|
|
38
39
|
const hasLimitless = book.bids.some((b) => b.price === 0.002);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
logger_1.logger.info('---');
|
|
41
|
+
logger_1.logger.info(`Has Polymarket levels: ${hasPoly}`);
|
|
42
|
+
logger_1.logger.info(`Has Limitless levels: ${hasLimitless}`);
|
|
42
43
|
if (hasPoly && hasLimitless) {
|
|
43
|
-
|
|
44
|
+
logger_1.logger.info('SUCCESS: Merged orderbook contains levels from both exchanges');
|
|
44
45
|
}
|
|
45
46
|
else if (!hasPoly && hasLimitless) {
|
|
46
|
-
|
|
47
|
+
logger_1.logger.info('PARTIAL: Only Limitless book (source market fetch failed)');
|
|
47
48
|
}
|
|
48
49
|
else if (hasPoly && !hasLimitless) {
|
|
49
|
-
|
|
50
|
+
logger_1.logger.info('PARTIAL: Only Polymarket book (matched market fetch failed)');
|
|
50
51
|
}
|
|
51
52
|
else {
|
|
52
|
-
|
|
53
|
+
logger_1.logger.info('FAIL: No data from either exchange');
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
main().catch((err) => {
|
|
56
|
-
|
|
57
|
+
logger_1.logger.error('E2E orderbook script failed', { error: String(err) });
|
|
57
58
|
process.exit(1);
|
|
58
59
|
});
|
package/dist/server/index.js
CHANGED
|
@@ -49,11 +49,20 @@ async function main() {
|
|
|
49
49
|
}
|
|
50
50
|
logger_1.logger.info(`Lock file created at ${lockFile.lockPath}`);
|
|
51
51
|
// Graceful shutdown
|
|
52
|
-
|
|
52
|
+
let shuttingDown = false;
|
|
53
|
+
const shutdown = () => {
|
|
54
|
+
if (shuttingDown)
|
|
55
|
+
return;
|
|
56
|
+
shuttingDown = true;
|
|
53
57
|
logger_1.logger.info('Shutting down gracefully...');
|
|
54
58
|
server.close();
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
void lockFile.remove()
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
logger_1.logger.warn('Failed to remove lock file during shutdown:', error);
|
|
62
|
+
})
|
|
63
|
+
.finally(() => {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
});
|
|
57
66
|
};
|
|
58
67
|
process.on('SIGTERM', shutdown);
|
|
59
68
|
process.on('SIGINT', shutdown);
|
package/dist/server/openapi.yaml
CHANGED
|
@@ -3022,6 +3022,16 @@ components:
|
|
|
3022
3022
|
datetime:
|
|
3023
3023
|
type: string
|
|
3024
3024
|
description: ISO 8601 datetime string of the snapshot (CCXT-compatible).
|
|
3025
|
+
isNegRisk:
|
|
3026
|
+
type: boolean
|
|
3027
|
+
description: Whether the venue marks this snapshot as a negative-risk market.
|
|
3028
|
+
lastTradePrice:
|
|
3029
|
+
type: number
|
|
3030
|
+
description: Last traded price from venues that include it with the book snapshot.
|
|
3031
|
+
sourceMetadata:
|
|
3032
|
+
type: object
|
|
3033
|
+
additionalProperties: {}
|
|
3034
|
+
description: Venue-specific metadata preserved from the raw order book snapshot.
|
|
3025
3035
|
required:
|
|
3026
3036
|
- bids
|
|
3027
3037
|
- asks
|
|
@@ -38,6 +38,7 @@ const fs = __importStar(require("fs/promises"));
|
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
|
+
const logger_1 = require("../../utils/logger");
|
|
41
42
|
class LockFile {
|
|
42
43
|
lockPath;
|
|
43
44
|
constructor() {
|
|
@@ -62,8 +63,10 @@ class LockFile {
|
|
|
62
63
|
try {
|
|
63
64
|
await fs.unlink(this.lockPath);
|
|
64
65
|
}
|
|
65
|
-
catch {
|
|
66
|
-
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (err?.code !== 'ENOENT') {
|
|
68
|
+
logger_1.logger.warn('LockFile: failed to remove lock', { path: this.lockPath, error: String(err) });
|
|
69
|
+
}
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
isProcessRunning(pid) {
|
package/dist/types.d.ts
CHANGED
|
@@ -159,6 +159,12 @@ export interface OrderBook {
|
|
|
159
159
|
timestamp?: number;
|
|
160
160
|
/** ISO 8601 datetime string of the snapshot (CCXT-compatible). */
|
|
161
161
|
datetime?: string;
|
|
162
|
+
/** Whether the venue marks this snapshot as a negative-risk market. */
|
|
163
|
+
isNegRisk?: boolean;
|
|
164
|
+
/** Last traded price from venues that include it with the book snapshot. */
|
|
165
|
+
lastTradePrice?: number;
|
|
166
|
+
/** Venue-specific metadata preserved from the raw order book snapshot. */
|
|
167
|
+
sourceMetadata?: Record<string, unknown>;
|
|
162
168
|
}
|
|
163
169
|
export interface Trade {
|
|
164
170
|
/** The unique identifier for this trade. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxt-core",
|
|
3
|
-
"version": "2.49.
|
|
3
|
+
"version": "2.49.9",
|
|
4
4
|
"description": "pmxt is a unified prediction market data API. The ccxt for prediction markets.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"test": "jest -c jest.config.js",
|
|
30
30
|
"server": "tsx watch src/server/index.ts",
|
|
31
31
|
"server:prod": "node dist/server/index.js",
|
|
32
|
-
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.49.
|
|
33
|
-
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.49.
|
|
32
|
+
"generate:sdk:python": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g python -o ../sdks/python/generated --package-name pmxt_internal --additional-properties=projectName=pmxt-internal,packageVersion=2.49.9,library=urllib3",
|
|
33
|
+
"generate:sdk:typescript": "npx @openapitools/openapi-generator-cli generate -i src/server/openapi.yaml -g typescript-fetch -o ../sdks/typescript/generated --additional-properties=npmName=pmxtjs,npmVersion=2.49.9,supportsES6=true,typescriptThreePlus=true && node ../sdks/typescript/scripts/fix-generated.js",
|
|
34
34
|
"fetch:openapi": "node scripts/fetch-openapi-specs.js",
|
|
35
35
|
"extract:jsdoc": "node ../scripts/extract-jsdoc.js",
|
|
36
36
|
"generate:docs": "npm run extract:jsdoc && node ../scripts/generate-api-docs.js",
|