pmxt-core 2.3.0 → 2.4.0
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/BaseExchange.js +1 -0
- package/dist/errors.js +11 -0
- package/dist/exchanges/kalshi/auth.js +1 -0
- package/dist/exchanges/kalshi/index.js +6 -0
- package/dist/exchanges/kalshi/websocket.js +14 -9
- package/dist/exchanges/limitless/auth.js +4 -0
- package/dist/exchanges/limitless/client.js +5 -1
- package/dist/exchanges/limitless/index.js +8 -1
- package/dist/exchanges/limitless/websocket.js +7 -5
- package/dist/exchanges/polymarket/auth.js +6 -0
- package/dist/exchanges/polymarket/index.js +6 -0
- package/dist/exchanges/polymarket/websocket.js +6 -3
- package/dist/exchanges/probable/auth.d.ts +14 -0
- package/dist/exchanges/probable/auth.js +67 -0
- package/dist/exchanges/probable/errors.d.ts +8 -0
- package/dist/exchanges/probable/errors.js +61 -0
- package/dist/exchanges/probable/fetchEvents.d.ts +5 -0
- package/dist/exchanges/probable/fetchEvents.js +138 -0
- package/dist/exchanges/probable/fetchMarkets.d.ts +3 -0
- package/dist/exchanges/probable/fetchMarkets.js +205 -0
- package/dist/exchanges/probable/fetchOHLCV.d.ts +3 -0
- package/dist/exchanges/probable/fetchOHLCV.js +83 -0
- package/dist/exchanges/probable/fetchOrderBook.d.ts +2 -0
- package/dist/exchanges/probable/fetchOrderBook.js +37 -0
- package/dist/exchanges/probable/fetchPositions.d.ts +2 -0
- package/dist/exchanges/probable/fetchPositions.js +33 -0
- package/dist/exchanges/probable/fetchTrades.d.ts +10 -0
- package/dist/exchanges/probable/fetchTrades.js +36 -0
- package/dist/exchanges/probable/index.d.ts +35 -0
- package/dist/exchanges/probable/index.js +317 -0
- package/dist/exchanges/probable/utils.d.ts +9 -0
- package/dist/exchanges/probable/utils.js +106 -0
- package/dist/exchanges/probable/websocket.d.ts +27 -0
- package/dist/exchanges/probable/websocket.js +102 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -2
- package/dist/server/app.js +10 -1
- package/dist/server/utils/lock-file.js +1 -0
- package/dist/utils/error-mapper.js +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProbableExchange = void 0;
|
|
4
|
+
const BaseExchange_1 = require("../../BaseExchange");
|
|
5
|
+
const fetchMarkets_1 = require("./fetchMarkets");
|
|
6
|
+
const fetchEvents_1 = require("./fetchEvents");
|
|
7
|
+
const fetchOrderBook_1 = require("./fetchOrderBook");
|
|
8
|
+
const fetchOHLCV_1 = require("./fetchOHLCV");
|
|
9
|
+
const fetchPositions_1 = require("./fetchPositions");
|
|
10
|
+
const fetchTrades_1 = require("./fetchTrades");
|
|
11
|
+
const auth_1 = require("./auth");
|
|
12
|
+
const websocket_1 = require("./websocket");
|
|
13
|
+
const errors_1 = require("./errors");
|
|
14
|
+
const errors_2 = require("../../errors");
|
|
15
|
+
const clob_1 = require("@prob/clob");
|
|
16
|
+
const BSC_USDT_ADDRESS = '0x55d398326f99059fF775485246999027B3197955';
|
|
17
|
+
class ProbableExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
18
|
+
auth;
|
|
19
|
+
ws;
|
|
20
|
+
wsConfig;
|
|
21
|
+
constructor(credentials, wsConfig) {
|
|
22
|
+
super(credentials);
|
|
23
|
+
this.wsConfig = wsConfig;
|
|
24
|
+
if (credentials?.privateKey && credentials?.apiKey && credentials?.apiSecret && credentials?.passphrase) {
|
|
25
|
+
this.auth = new auth_1.ProbableAuth(credentials);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
get name() {
|
|
29
|
+
return 'Probable';
|
|
30
|
+
}
|
|
31
|
+
ensureAuth() {
|
|
32
|
+
if (!this.auth) {
|
|
33
|
+
throw new errors_2.AuthenticationError('Trading operations require authentication. ' +
|
|
34
|
+
'Initialize ProbableExchange with credentials: new ProbableExchange({ privateKey: "0x...", apiKey: "...", apiSecret: "...", passphrase: "..." })', 'Probable');
|
|
35
|
+
}
|
|
36
|
+
return this.auth;
|
|
37
|
+
}
|
|
38
|
+
// --------------------------------------------------------------------------
|
|
39
|
+
// Market Data (read-only, no auth needed)
|
|
40
|
+
// --------------------------------------------------------------------------
|
|
41
|
+
async fetchMarketsImpl(params) {
|
|
42
|
+
return (0, fetchMarkets_1.fetchMarkets)(params);
|
|
43
|
+
}
|
|
44
|
+
async fetchEventsImpl(params) {
|
|
45
|
+
return (0, fetchEvents_1.fetchEvents)(params);
|
|
46
|
+
}
|
|
47
|
+
async getEventById(id) {
|
|
48
|
+
return (0, fetchEvents_1.fetchEventById)(id);
|
|
49
|
+
}
|
|
50
|
+
async getEventBySlug(slug) {
|
|
51
|
+
return (0, fetchEvents_1.fetchEventBySlug)(slug);
|
|
52
|
+
}
|
|
53
|
+
async fetchOrderBook(id) {
|
|
54
|
+
return (0, fetchOrderBook_1.fetchOrderBook)(id);
|
|
55
|
+
}
|
|
56
|
+
async fetchOHLCV(id, params) {
|
|
57
|
+
return (0, fetchOHLCV_1.fetchOHLCV)(id, params);
|
|
58
|
+
}
|
|
59
|
+
// --------------------------------------------------------------------------
|
|
60
|
+
// Trading Methods
|
|
61
|
+
// --------------------------------------------------------------------------
|
|
62
|
+
async createOrder(params) {
|
|
63
|
+
try {
|
|
64
|
+
const auth = this.ensureAuth();
|
|
65
|
+
const client = auth.getClobClient();
|
|
66
|
+
const side = params.side.toLowerCase() === 'buy' ? clob_1.OrderSide.Buy : clob_1.OrderSide.Sell;
|
|
67
|
+
let unsignedOrder;
|
|
68
|
+
if (params.type === 'market') {
|
|
69
|
+
unsignedOrder = await client.createMarketOrder({
|
|
70
|
+
tokenId: params.outcomeId,
|
|
71
|
+
size: params.amount,
|
|
72
|
+
side,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (!params.price) {
|
|
77
|
+
throw new Error('Price is required for limit orders');
|
|
78
|
+
}
|
|
79
|
+
unsignedOrder = await client.createLimitOrder({
|
|
80
|
+
tokenId: params.outcomeId,
|
|
81
|
+
price: params.price,
|
|
82
|
+
size: params.amount,
|
|
83
|
+
side,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (params.fee !== undefined && params.fee !== null) {
|
|
87
|
+
unsignedOrder.feeRateBps = BigInt(params.fee);
|
|
88
|
+
}
|
|
89
|
+
const response = await client.postOrder(unsignedOrder);
|
|
90
|
+
// postOrder returns PostOrderResponse which can be success or error
|
|
91
|
+
if (response && 'code' in response && response.code !== undefined) {
|
|
92
|
+
throw new Error(response.msg || 'Order placement failed');
|
|
93
|
+
}
|
|
94
|
+
const orderResponse = response;
|
|
95
|
+
return {
|
|
96
|
+
id: String(orderResponse.orderId || orderResponse.id || ''),
|
|
97
|
+
marketId: params.marketId,
|
|
98
|
+
outcomeId: params.outcomeId,
|
|
99
|
+
side: params.side,
|
|
100
|
+
type: params.type,
|
|
101
|
+
price: params.price || parseFloat(orderResponse.price || '0'),
|
|
102
|
+
amount: params.amount,
|
|
103
|
+
status: 'open',
|
|
104
|
+
filled: parseFloat(orderResponse.executedQty || '0'),
|
|
105
|
+
remaining: params.amount - parseFloat(orderResponse.executedQty || '0'),
|
|
106
|
+
fee: params.fee,
|
|
107
|
+
timestamp: orderResponse.time || Date.now(),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
throw errors_1.probableErrorMapper.mapError(error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Cancel an order.
|
|
116
|
+
* The Probable SDK requires both orderId and tokenId for cancellation.
|
|
117
|
+
* Pass a compound key as "orderId:tokenId" to provide both values.
|
|
118
|
+
*/
|
|
119
|
+
async cancelOrder(orderId) {
|
|
120
|
+
try {
|
|
121
|
+
const auth = this.ensureAuth();
|
|
122
|
+
const client = auth.getClobClient();
|
|
123
|
+
const [actualOrderId, tokenId] = parseCompoundId(orderId);
|
|
124
|
+
if (!tokenId) {
|
|
125
|
+
throw new Error('Probable cancelOrder requires a compound ID in the format "orderId:tokenId". ' +
|
|
126
|
+
'The tokenId (outcomeId) is required by the Probable SDK.');
|
|
127
|
+
}
|
|
128
|
+
await client.cancelOrder({
|
|
129
|
+
orderId: actualOrderId,
|
|
130
|
+
tokenId,
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
id: actualOrderId,
|
|
134
|
+
marketId: 'unknown',
|
|
135
|
+
outcomeId: tokenId,
|
|
136
|
+
side: 'buy',
|
|
137
|
+
type: 'limit',
|
|
138
|
+
amount: 0,
|
|
139
|
+
status: 'cancelled',
|
|
140
|
+
filled: 0,
|
|
141
|
+
remaining: 0,
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw errors_1.probableErrorMapper.mapError(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Fetch a single order by ID.
|
|
151
|
+
* Pass a compound key as "orderId:tokenId" since the SDK requires both.
|
|
152
|
+
*/
|
|
153
|
+
async fetchOrder(orderId) {
|
|
154
|
+
try {
|
|
155
|
+
const auth = this.ensureAuth();
|
|
156
|
+
const client = auth.getClobClient();
|
|
157
|
+
const [actualOrderId, tokenId] = parseCompoundId(orderId);
|
|
158
|
+
if (!tokenId) {
|
|
159
|
+
throw new Error('Probable fetchOrder requires a compound ID in the format "orderId:tokenId".');
|
|
160
|
+
}
|
|
161
|
+
const order = await client.getOrder({
|
|
162
|
+
orderId: actualOrderId,
|
|
163
|
+
tokenId,
|
|
164
|
+
});
|
|
165
|
+
if (!order || ('code' in order)) {
|
|
166
|
+
throw new Error(order?.msg || 'Order not found');
|
|
167
|
+
}
|
|
168
|
+
const o = order;
|
|
169
|
+
return {
|
|
170
|
+
id: String(o.orderId || o.id),
|
|
171
|
+
marketId: o.symbol || 'unknown',
|
|
172
|
+
outcomeId: o.tokenId || tokenId,
|
|
173
|
+
side: (o.side || '').toLowerCase(),
|
|
174
|
+
type: o.type === 'LIMIT' || o.timeInForce === 'GTC' ? 'limit' : 'market',
|
|
175
|
+
price: parseFloat(o.price || '0'),
|
|
176
|
+
amount: parseFloat(o.origQty || '0'),
|
|
177
|
+
status: mapOrderStatus(o.status),
|
|
178
|
+
filled: parseFloat(o.executedQty || '0'),
|
|
179
|
+
remaining: parseFloat(o.origQty || '0') - parseFloat(o.executedQty || '0'),
|
|
180
|
+
timestamp: o.time || o.updateTime || Date.now(),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
throw errors_1.probableErrorMapper.mapError(error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async fetchOpenOrders(marketId) {
|
|
188
|
+
try {
|
|
189
|
+
const auth = this.ensureAuth();
|
|
190
|
+
const client = auth.getClobClient();
|
|
191
|
+
const params = {};
|
|
192
|
+
if (marketId) {
|
|
193
|
+
params.eventId = marketId;
|
|
194
|
+
}
|
|
195
|
+
const orders = await client.getOpenOrders(params);
|
|
196
|
+
const orderList = Array.isArray(orders) ? orders : orders?.data || [];
|
|
197
|
+
return orderList.map((o) => ({
|
|
198
|
+
id: String(o.orderId || o.id),
|
|
199
|
+
marketId: o.symbol || 'unknown',
|
|
200
|
+
outcomeId: o.tokenId || '',
|
|
201
|
+
side: (o.side || '').toLowerCase(),
|
|
202
|
+
type: 'limit',
|
|
203
|
+
price: parseFloat(o.price || '0'),
|
|
204
|
+
amount: parseFloat(o.origQty || '0'),
|
|
205
|
+
status: 'open',
|
|
206
|
+
filled: parseFloat(o.executedQty || '0'),
|
|
207
|
+
remaining: parseFloat(o.origQty || '0') - parseFloat(o.executedQty || '0'),
|
|
208
|
+
timestamp: o.time || o.updateTime || Date.now(),
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
throw errors_1.probableErrorMapper.mapError(error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async fetchPositions() {
|
|
216
|
+
try {
|
|
217
|
+
const auth = this.ensureAuth();
|
|
218
|
+
const address = auth.getAddress();
|
|
219
|
+
return (0, fetchPositions_1.fetchPositions)(address);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
throw errors_1.probableErrorMapper.mapError(error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async fetchBalance() {
|
|
226
|
+
try {
|
|
227
|
+
const auth = this.ensureAuth();
|
|
228
|
+
let total = 0;
|
|
229
|
+
try {
|
|
230
|
+
const { createPublicClient, http, parseAbi, formatUnits } = require('viem');
|
|
231
|
+
const { bsc } = require('viem/chains');
|
|
232
|
+
const publicClient = createPublicClient({
|
|
233
|
+
chain: bsc,
|
|
234
|
+
transport: http(),
|
|
235
|
+
});
|
|
236
|
+
const balance = await publicClient.readContract({
|
|
237
|
+
address: BSC_USDT_ADDRESS,
|
|
238
|
+
abi: parseAbi(['function balanceOf(address) view returns (uint256)']),
|
|
239
|
+
functionName: 'balanceOf',
|
|
240
|
+
args: [auth.getAddress()],
|
|
241
|
+
});
|
|
242
|
+
total = parseFloat(formatUnits(balance, 18));
|
|
243
|
+
}
|
|
244
|
+
catch (chainError) {
|
|
245
|
+
// On-chain check failed, return 0
|
|
246
|
+
}
|
|
247
|
+
// Calculate locked from open BUY orders
|
|
248
|
+
let locked = 0;
|
|
249
|
+
try {
|
|
250
|
+
const openOrders = await this.fetchOpenOrders();
|
|
251
|
+
for (const order of openOrders) {
|
|
252
|
+
if (order.side === 'buy' && order.price) {
|
|
253
|
+
locked += order.remaining * order.price;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// If we can't fetch orders, locked stays 0
|
|
259
|
+
}
|
|
260
|
+
return [{
|
|
261
|
+
currency: 'USDT',
|
|
262
|
+
total,
|
|
263
|
+
available: total - locked,
|
|
264
|
+
locked,
|
|
265
|
+
}];
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
throw errors_1.probableErrorMapper.mapError(error);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async fetchTrades(id, params) {
|
|
272
|
+
const auth = this.ensureAuth();
|
|
273
|
+
const client = auth.getClobClient();
|
|
274
|
+
return (0, fetchTrades_1.fetchTrades)(id, params, client);
|
|
275
|
+
}
|
|
276
|
+
// --------------------------------------------------------------------------
|
|
277
|
+
// WebSocket Streaming (public, no auth needed)
|
|
278
|
+
// --------------------------------------------------------------------------
|
|
279
|
+
async watchOrderBook(id, limit) {
|
|
280
|
+
if (!this.ws) {
|
|
281
|
+
this.ws = new websocket_1.ProbableWebSocket(this.wsConfig);
|
|
282
|
+
}
|
|
283
|
+
return this.ws.watchOrderBook(id);
|
|
284
|
+
}
|
|
285
|
+
async close() {
|
|
286
|
+
if (this.ws) {
|
|
287
|
+
await this.ws.close();
|
|
288
|
+
this.ws = undefined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
exports.ProbableExchange = ProbableExchange;
|
|
293
|
+
/**
|
|
294
|
+
* Parse a compound ID in the format "orderId:tokenId".
|
|
295
|
+
* Returns [orderId, tokenId] where tokenId may be undefined.
|
|
296
|
+
*/
|
|
297
|
+
function parseCompoundId(compoundId) {
|
|
298
|
+
const colonIndex = compoundId.indexOf(':');
|
|
299
|
+
if (colonIndex === -1) {
|
|
300
|
+
return [compoundId, undefined];
|
|
301
|
+
}
|
|
302
|
+
return [compoundId.substring(0, colonIndex), compoundId.substring(colonIndex + 1)];
|
|
303
|
+
}
|
|
304
|
+
function mapOrderStatus(status) {
|
|
305
|
+
if (!status)
|
|
306
|
+
return 'open';
|
|
307
|
+
const lower = status.toLowerCase();
|
|
308
|
+
if (lower === 'new' || lower === 'open' || lower === 'partially_filled')
|
|
309
|
+
return 'open';
|
|
310
|
+
if (lower === 'filled' || lower === 'trade')
|
|
311
|
+
return 'filled';
|
|
312
|
+
if (lower === 'canceled' || lower === 'cancelled' || lower === 'expired')
|
|
313
|
+
return 'cancelled';
|
|
314
|
+
if (lower === 'rejected')
|
|
315
|
+
return 'rejected';
|
|
316
|
+
return 'open';
|
|
317
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { UnifiedMarket, UnifiedEvent } from '../../types';
|
|
2
|
+
export declare const BASE_URL = "https://market-api.probable.markets";
|
|
3
|
+
export declare const CLOB_BASE_URL = "https://api.probable.markets/public/api/v1";
|
|
4
|
+
export declare const SEARCH_PATH = "/public/api/v1/public-search/";
|
|
5
|
+
export declare const EVENTS_PATH = "/public/api/v1/events/";
|
|
6
|
+
export declare const MARKETS_PATH = "/public/api/v1/markets/";
|
|
7
|
+
export declare function mapMarketToUnified(market: any, event?: any): UnifiedMarket | null;
|
|
8
|
+
export declare function mapEventToUnified(event: any): UnifiedEvent | null;
|
|
9
|
+
export declare function enrichMarketsWithPrices(markets: UnifiedMarket[]): Promise<void>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MARKETS_PATH = exports.EVENTS_PATH = exports.SEARCH_PATH = exports.CLOB_BASE_URL = exports.BASE_URL = void 0;
|
|
7
|
+
exports.mapMarketToUnified = mapMarketToUnified;
|
|
8
|
+
exports.mapEventToUnified = mapEventToUnified;
|
|
9
|
+
exports.enrichMarketsWithPrices = enrichMarketsWithPrices;
|
|
10
|
+
const market_utils_1 = require("../../utils/market-utils");
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
exports.BASE_URL = 'https://market-api.probable.markets';
|
|
13
|
+
exports.CLOB_BASE_URL = 'https://api.probable.markets/public/api/v1';
|
|
14
|
+
exports.SEARCH_PATH = '/public/api/v1/public-search/';
|
|
15
|
+
exports.EVENTS_PATH = '/public/api/v1/events/';
|
|
16
|
+
exports.MARKETS_PATH = '/public/api/v1/markets/';
|
|
17
|
+
function mapMarketToUnified(market, event) {
|
|
18
|
+
if (!market)
|
|
19
|
+
return null;
|
|
20
|
+
const outcomes = [];
|
|
21
|
+
// Probable API provides tokens array with token_id and outcome label.
|
|
22
|
+
// The outcomes field is a JSON string like '["Yes","No"]'.
|
|
23
|
+
// Prices are not included in the search response.
|
|
24
|
+
if (market.tokens && Array.isArray(market.tokens)) {
|
|
25
|
+
for (const token of market.tokens) {
|
|
26
|
+
outcomes.push({
|
|
27
|
+
outcomeId: String(token.token_id),
|
|
28
|
+
marketId: String(market.id),
|
|
29
|
+
label: token.outcome || '',
|
|
30
|
+
price: 0,
|
|
31
|
+
priceChange24h: 0,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const um = {
|
|
36
|
+
marketId: String(market.id),
|
|
37
|
+
title: market.question || market.title || '',
|
|
38
|
+
description: market.description || '',
|
|
39
|
+
outcomes,
|
|
40
|
+
resolutionDate: market.endDate ? new Date(market.endDate) : new Date(),
|
|
41
|
+
volume24h: Number(market.volume24hr || 0),
|
|
42
|
+
volume: Number(market.volume || 0),
|
|
43
|
+
liquidity: Number(market.liquidity || 0),
|
|
44
|
+
openInterest: 0,
|
|
45
|
+
url: `https://probable.markets/markets/${market.market_slug || market.slug || market.id}`,
|
|
46
|
+
image: market.icon || event?.icon || event?.image || undefined,
|
|
47
|
+
category: event?.category || market.category || undefined,
|
|
48
|
+
tags: market.tags || event?.tags || [],
|
|
49
|
+
};
|
|
50
|
+
(0, market_utils_1.addBinaryOutcomes)(um);
|
|
51
|
+
return um;
|
|
52
|
+
}
|
|
53
|
+
function mapEventToUnified(event) {
|
|
54
|
+
if (!event)
|
|
55
|
+
return null;
|
|
56
|
+
const markets = [];
|
|
57
|
+
if (event.markets && Array.isArray(event.markets)) {
|
|
58
|
+
for (const market of event.markets) {
|
|
59
|
+
const mapped = mapMarketToUnified(market, event);
|
|
60
|
+
if (mapped)
|
|
61
|
+
markets.push(mapped);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
id: String(event.id),
|
|
66
|
+
title: event.title || '',
|
|
67
|
+
description: event.description || '',
|
|
68
|
+
slug: event.slug || '',
|
|
69
|
+
markets,
|
|
70
|
+
url: `https://probable.markets/events/${event.slug || event.id}`,
|
|
71
|
+
image: event.icon || event.image || undefined,
|
|
72
|
+
category: event.category || undefined,
|
|
73
|
+
tags: event.tags || [],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function enrichMarketsWithPrices(markets) {
|
|
77
|
+
const outcomes = [];
|
|
78
|
+
for (const market of markets) {
|
|
79
|
+
for (const outcome of market.outcomes) {
|
|
80
|
+
if (outcome.outcomeId)
|
|
81
|
+
outcomes.push(outcome);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (outcomes.length === 0)
|
|
85
|
+
return;
|
|
86
|
+
const results = await Promise.allSettled(outcomes.map(async (outcome) => {
|
|
87
|
+
const response = await axios_1.default.get(`${exports.CLOB_BASE_URL}/midpoint`, {
|
|
88
|
+
params: { token_id: outcome.outcomeId },
|
|
89
|
+
});
|
|
90
|
+
return { outcomeId: outcome.outcomeId, mid: Number(response.data?.mid ?? 0) };
|
|
91
|
+
}));
|
|
92
|
+
const priceMap = {};
|
|
93
|
+
for (const result of results) {
|
|
94
|
+
if (result.status === 'fulfilled') {
|
|
95
|
+
priceMap[result.value.outcomeId] = result.value.mid;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const market of markets) {
|
|
99
|
+
for (const outcome of market.outcomes) {
|
|
100
|
+
const price = priceMap[outcome.outcomeId];
|
|
101
|
+
if (price !== undefined)
|
|
102
|
+
outcome.price = price;
|
|
103
|
+
}
|
|
104
|
+
(0, market_utils_1.addBinaryOutcomes)(market);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { OrderBook } from '../../types';
|
|
2
|
+
export interface ProbableWebSocketConfig {
|
|
3
|
+
/** WebSocket URL (default: wss://ws.probable.markets/public/api/v1) */
|
|
4
|
+
wsUrl?: string;
|
|
5
|
+
/** Base URL for the CLOB client (default: https://api.probable.markets/public/api/v1) */
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
/** Chain ID (default: 56 for BSC mainnet) */
|
|
8
|
+
chainId?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Probable WebSocket implementation for real-time order book streaming.
|
|
12
|
+
* Uses the @prob/clob SDK's subscribePublicStream (no auth required).
|
|
13
|
+
* Follows CCXT Pro-style async pattern with watchOrderBook().
|
|
14
|
+
*/
|
|
15
|
+
export declare class ProbableWebSocket {
|
|
16
|
+
private client?;
|
|
17
|
+
private config;
|
|
18
|
+
private orderBookResolvers;
|
|
19
|
+
private orderBooks;
|
|
20
|
+
private subscriptions;
|
|
21
|
+
constructor(config?: ProbableWebSocketConfig);
|
|
22
|
+
private ensureClient;
|
|
23
|
+
watchOrderBook(tokenId: string): Promise<OrderBook>;
|
|
24
|
+
private handleOrderBookUpdate;
|
|
25
|
+
private resolveOrderBook;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProbableWebSocket = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Probable WebSocket implementation for real-time order book streaming.
|
|
6
|
+
* Uses the @prob/clob SDK's subscribePublicStream (no auth required).
|
|
7
|
+
* Follows CCXT Pro-style async pattern with watchOrderBook().
|
|
8
|
+
*/
|
|
9
|
+
class ProbableWebSocket {
|
|
10
|
+
client;
|
|
11
|
+
config;
|
|
12
|
+
orderBookResolvers = new Map();
|
|
13
|
+
orderBooks = new Map();
|
|
14
|
+
subscriptions = new Map();
|
|
15
|
+
constructor(config = {}) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
async ensureClient() {
|
|
19
|
+
if (this.client)
|
|
20
|
+
return this.client;
|
|
21
|
+
const chainId = this.config.chainId || parseInt(process.env.PROBABLE_CHAIN_ID || '56', 10);
|
|
22
|
+
const wsUrl = this.config.wsUrl || process.env.PROBABLE_WS_URL || 'wss://ws.probable.markets/public/api/v1';
|
|
23
|
+
const baseUrl = this.config.baseUrl || process.env.PROBABLE_BASE_URL || 'https://api.probable.markets/public/api/v1';
|
|
24
|
+
// Dynamically import @prob/clob using eval to bypass TS compilation to require()
|
|
25
|
+
// which forces native import() usage, resolving ESM/CJS issues
|
|
26
|
+
const { createClobClient: createClient } = await eval('import("@prob/clob")');
|
|
27
|
+
// For public streams, always provide baseUrl, wsUrl, and chainId
|
|
28
|
+
this.client = createClient({
|
|
29
|
+
baseUrl,
|
|
30
|
+
wsUrl,
|
|
31
|
+
chainId,
|
|
32
|
+
});
|
|
33
|
+
return this.client;
|
|
34
|
+
}
|
|
35
|
+
async watchOrderBook(tokenId) {
|
|
36
|
+
const client = await this.ensureClient();
|
|
37
|
+
// Subscribe if not already subscribed
|
|
38
|
+
if (!this.subscriptions.has(tokenId)) {
|
|
39
|
+
const sub = client.subscribePublicStream([`book:${tokenId}`], (data) => {
|
|
40
|
+
this.handleOrderBookUpdate(tokenId, data);
|
|
41
|
+
});
|
|
42
|
+
this.subscriptions.set(tokenId, sub);
|
|
43
|
+
}
|
|
44
|
+
// Return a promise that resolves on the next orderbook update
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
if (!this.orderBookResolvers.has(tokenId)) {
|
|
47
|
+
this.orderBookResolvers.set(tokenId, []);
|
|
48
|
+
}
|
|
49
|
+
this.orderBookResolvers.get(tokenId).push({ resolve, reject });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
handleOrderBookUpdate(tokenId, data) {
|
|
53
|
+
const bids = (data.bids || []).map((b) => ({
|
|
54
|
+
price: parseFloat(b.price),
|
|
55
|
+
size: parseFloat(b.size),
|
|
56
|
+
})).sort((a, b) => b.price - a.price);
|
|
57
|
+
const asks = (data.asks || []).map((a) => ({
|
|
58
|
+
price: parseFloat(a.price),
|
|
59
|
+
size: parseFloat(a.size),
|
|
60
|
+
})).sort((a, b) => a.price - b.price);
|
|
61
|
+
let timestamp = Date.now();
|
|
62
|
+
if (data.timestamp) {
|
|
63
|
+
const parsed = typeof data.timestamp === 'number' ? data.timestamp : new Date(data.timestamp).getTime();
|
|
64
|
+
if (!isNaN(parsed)) {
|
|
65
|
+
timestamp = parsed;
|
|
66
|
+
if (timestamp < 10000000000) {
|
|
67
|
+
timestamp *= 1000;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const orderBook = { bids, asks, timestamp };
|
|
72
|
+
this.orderBooks.set(tokenId, orderBook);
|
|
73
|
+
this.resolveOrderBook(tokenId, orderBook);
|
|
74
|
+
}
|
|
75
|
+
resolveOrderBook(tokenId, orderBook) {
|
|
76
|
+
const resolvers = this.orderBookResolvers.get(tokenId);
|
|
77
|
+
if (resolvers && resolvers.length > 0) {
|
|
78
|
+
resolvers.forEach(r => r.resolve(orderBook));
|
|
79
|
+
this.orderBookResolvers.set(tokenId, []);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async close() {
|
|
83
|
+
// Unsubscribe from all streams
|
|
84
|
+
for (const [tokenId, sub] of this.subscriptions) {
|
|
85
|
+
try {
|
|
86
|
+
sub.unsubscribe();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Ignore cleanup errors
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.subscriptions.clear();
|
|
93
|
+
// Reject all pending resolvers
|
|
94
|
+
this.orderBookResolvers.forEach((resolvers, tokenId) => {
|
|
95
|
+
resolvers.forEach(r => r.reject(new Error(`WebSocket closed for ${tokenId}`)));
|
|
96
|
+
});
|
|
97
|
+
this.orderBookResolvers.clear();
|
|
98
|
+
this.orderBooks.clear();
|
|
99
|
+
this.client = undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.ProbableWebSocket = ProbableWebSocket;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,18 +5,22 @@ export * from './errors';
|
|
|
5
5
|
export * from './exchanges/polymarket';
|
|
6
6
|
export * from './exchanges/limitless';
|
|
7
7
|
export * from './exchanges/kalshi';
|
|
8
|
+
export * from './exchanges/probable';
|
|
8
9
|
export * from './server/app';
|
|
9
10
|
export * from './server/utils/port-manager';
|
|
10
11
|
export * from './server/utils/lock-file';
|
|
11
12
|
import { PolymarketExchange } from './exchanges/polymarket';
|
|
12
13
|
import { LimitlessExchange } from './exchanges/limitless';
|
|
13
14
|
import { KalshiExchange } from './exchanges/kalshi';
|
|
15
|
+
import { ProbableExchange } from './exchanges/probable';
|
|
14
16
|
declare const pmxt: {
|
|
15
17
|
Polymarket: typeof PolymarketExchange;
|
|
16
18
|
Limitless: typeof LimitlessExchange;
|
|
17
19
|
Kalshi: typeof KalshiExchange;
|
|
20
|
+
Probable: typeof ProbableExchange;
|
|
18
21
|
};
|
|
19
22
|
export declare const Polymarket: typeof PolymarketExchange;
|
|
20
23
|
export declare const Limitless: typeof LimitlessExchange;
|
|
21
24
|
export declare const Kalshi: typeof KalshiExchange;
|
|
25
|
+
export declare const Probable: typeof ProbableExchange;
|
|
22
26
|
export default pmxt;
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.Kalshi = exports.Limitless = exports.Polymarket = void 0;
|
|
17
|
+
exports.Probable = exports.Kalshi = exports.Limitless = exports.Polymarket = void 0;
|
|
18
18
|
__exportStar(require("./BaseExchange"), exports);
|
|
19
19
|
__exportStar(require("./types"), exports);
|
|
20
20
|
__exportStar(require("./utils/math"), exports);
|
|
@@ -22,18 +22,22 @@ __exportStar(require("./errors"), exports);
|
|
|
22
22
|
__exportStar(require("./exchanges/polymarket"), exports);
|
|
23
23
|
__exportStar(require("./exchanges/limitless"), exports);
|
|
24
24
|
__exportStar(require("./exchanges/kalshi"), exports);
|
|
25
|
+
__exportStar(require("./exchanges/probable"), exports);
|
|
25
26
|
__exportStar(require("./server/app"), exports);
|
|
26
27
|
__exportStar(require("./server/utils/port-manager"), exports);
|
|
27
28
|
__exportStar(require("./server/utils/lock-file"), exports);
|
|
28
29
|
const polymarket_1 = require("./exchanges/polymarket");
|
|
29
30
|
const limitless_1 = require("./exchanges/limitless");
|
|
30
31
|
const kalshi_1 = require("./exchanges/kalshi");
|
|
32
|
+
const probable_1 = require("./exchanges/probable");
|
|
31
33
|
const pmxt = {
|
|
32
34
|
Polymarket: polymarket_1.PolymarketExchange,
|
|
33
35
|
Limitless: limitless_1.LimitlessExchange,
|
|
34
|
-
Kalshi: kalshi_1.KalshiExchange
|
|
36
|
+
Kalshi: kalshi_1.KalshiExchange,
|
|
37
|
+
Probable: probable_1.ProbableExchange
|
|
35
38
|
};
|
|
36
39
|
exports.Polymarket = polymarket_1.PolymarketExchange;
|
|
37
40
|
exports.Limitless = limitless_1.LimitlessExchange;
|
|
38
41
|
exports.Kalshi = kalshi_1.KalshiExchange;
|
|
42
|
+
exports.Probable = probable_1.ProbableExchange;
|
|
39
43
|
exports.default = pmxt;
|
package/dist/server/app.js
CHANGED
|
@@ -9,12 +9,14 @@ const cors_1 = __importDefault(require("cors"));
|
|
|
9
9
|
const polymarket_1 = require("../exchanges/polymarket");
|
|
10
10
|
const limitless_1 = require("../exchanges/limitless");
|
|
11
11
|
const kalshi_1 = require("../exchanges/kalshi");
|
|
12
|
+
const probable_1 = require("../exchanges/probable");
|
|
12
13
|
const errors_1 = require("../errors");
|
|
13
14
|
// Singleton instances for local usage (when no credentials provided)
|
|
14
15
|
const defaultExchanges = {
|
|
15
16
|
polymarket: null,
|
|
16
17
|
limitless: null,
|
|
17
|
-
kalshi: null
|
|
18
|
+
kalshi: null,
|
|
19
|
+
probable: null
|
|
18
20
|
};
|
|
19
21
|
async function startServer(port, accessToken) {
|
|
20
22
|
const app = (0, express_1.default)();
|
|
@@ -132,6 +134,13 @@ function createExchange(name, credentials) {
|
|
|
132
134
|
apiKey: credentials?.apiKey || process.env.KALSHI_API_KEY,
|
|
133
135
|
privateKey: credentials?.privateKey || process.env.KALSHI_PRIVATE_KEY
|
|
134
136
|
});
|
|
137
|
+
case 'probable':
|
|
138
|
+
return new probable_1.ProbableExchange({
|
|
139
|
+
apiKey: credentials?.apiKey || process.env.PROBABLE_API_KEY,
|
|
140
|
+
apiSecret: credentials?.apiSecret || process.env.PROBABLE_API_SECRET,
|
|
141
|
+
passphrase: credentials?.passphrase || process.env.PROBABLE_PASSPHRASE,
|
|
142
|
+
privateKey: credentials?.privateKey || process.env.PROBABLE_PRIVATE_KEY,
|
|
143
|
+
});
|
|
135
144
|
default:
|
|
136
145
|
throw new Error(`Unknown exchange: ${name}`);
|
|
137
146
|
}
|
|
@@ -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
|
class LockFile {
|
|
41
|
+
lockPath;
|
|
41
42
|
constructor() {
|
|
42
43
|
this.lockPath = path.join(os.homedir(), '.pmxt', 'server.lock');
|
|
43
44
|
}
|