pmxt-core 2.23.0 → 2.25.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/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- 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 +42 -8
- package/dist/exchanges/polymarket/index.js +56 -19
- package/dist/exchanges/polymarket_us/config.d.ts +18 -0
- package/dist/exchanges/polymarket_us/config.js +22 -0
- package/dist/exchanges/polymarket_us/errors.d.ts +19 -0
- package/dist/exchanges/polymarket_us/errors.js +123 -0
- package/dist/exchanges/polymarket_us/errors.test.d.ts +1 -0
- package/dist/exchanges/polymarket_us/errors.test.js +54 -0
- package/dist/exchanges/polymarket_us/index.d.ts +90 -0
- package/dist/exchanges/polymarket_us/index.js +366 -0
- package/dist/exchanges/polymarket_us/index.test.d.ts +8 -0
- package/dist/exchanges/polymarket_us/index.test.js +237 -0
- package/dist/exchanges/polymarket_us/normalizer.d.ts +55 -0
- package/dist/exchanges/polymarket_us/normalizer.js +385 -0
- package/dist/exchanges/polymarket_us/normalizer.test.d.ts +1 -0
- package/dist/exchanges/polymarket_us/normalizer.test.js +224 -0
- package/dist/exchanges/polymarket_us/price.d.ts +94 -0
- package/dist/exchanges/polymarket_us/price.js +149 -0
- package/dist/exchanges/polymarket_us/price.test.d.ts +1 -0
- package/dist/exchanges/polymarket_us/price.test.js +131 -0
- package/dist/exchanges/polymarket_us/websocket.d.ts +39 -0
- package/dist/exchanges/polymarket_us/websocket.js +181 -0
- package/dist/exchanges/polymarket_us/websocket.test.d.ts +8 -0
- package/dist/exchanges/polymarket_us/websocket.test.js +162 -0
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/exchanges/smarkets/api.d.ts +8067 -0
- package/dist/exchanges/smarkets/api.js +10698 -0
- package/dist/exchanges/smarkets/auth.d.ts +56 -0
- package/dist/exchanges/smarkets/auth.js +105 -0
- package/dist/exchanges/smarkets/config.d.ts +41 -0
- package/dist/exchanges/smarkets/config.js +47 -0
- package/dist/exchanges/smarkets/errors.d.ts +31 -0
- package/dist/exchanges/smarkets/errors.js +186 -0
- package/dist/exchanges/smarkets/fetcher.d.ts +177 -0
- package/dist/exchanges/smarkets/fetcher.js +342 -0
- package/dist/exchanges/smarkets/index.d.ts +54 -0
- package/dist/exchanges/smarkets/index.js +285 -0
- package/dist/exchanges/smarkets/normalizer.d.ts +18 -0
- package/dist/exchanges/smarkets/normalizer.js +267 -0
- package/dist/exchanges/smarkets/price.d.ts +26 -0
- package/dist/exchanges/smarkets/price.js +44 -0
- package/dist/exchanges/smarkets/price.test.d.ts +1 -0
- package/dist/exchanges/smarkets/price.test.js +50 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -1
- package/dist/server/app.js +18 -2
- package/package.json +4 -3
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SmarketsFetcher = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const errors_2 = require("../../errors");
|
|
6
|
+
const validation_1 = require("../../utils/validation");
|
|
7
|
+
// ----------------------------------------------------------------------------
|
|
8
|
+
// Fetcher
|
|
9
|
+
// ----------------------------------------------------------------------------
|
|
10
|
+
const BATCH_SIZE = 200;
|
|
11
|
+
const MAX_PAGES = 100;
|
|
12
|
+
const EVENT_ID_BATCH_SIZE = 50;
|
|
13
|
+
const MARKET_ID_BATCH_SIZE = 100;
|
|
14
|
+
class SmarketsFetcher {
|
|
15
|
+
ctx;
|
|
16
|
+
constructor(ctx) {
|
|
17
|
+
this.ctx = ctx;
|
|
18
|
+
}
|
|
19
|
+
// -- Markets (returns enriched events with nested markets/contracts) ------
|
|
20
|
+
async fetchRawMarkets(params) {
|
|
21
|
+
try {
|
|
22
|
+
if (params?.eventId) {
|
|
23
|
+
return this.fetchEnrichedEventById(params.eventId);
|
|
24
|
+
}
|
|
25
|
+
if (params?.marketId) {
|
|
26
|
+
return this.fetchEnrichedEventByMarketId(params.marketId);
|
|
27
|
+
}
|
|
28
|
+
return this.fetchAllEnrichedEvents(params);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw errors_1.smarketsErrorMapper.mapError(error);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// -- Events ---------------------------------------------------------------
|
|
35
|
+
async fetchRawEvents(params) {
|
|
36
|
+
try {
|
|
37
|
+
if (params.eventId) {
|
|
38
|
+
return this.fetchEnrichedEventById(params.eventId);
|
|
39
|
+
}
|
|
40
|
+
const stateFilter = this.mapEventStatus(params?.status || 'active');
|
|
41
|
+
const rawEvents = await this.fetchPaginatedEvents({
|
|
42
|
+
state: stateFilter,
|
|
43
|
+
type_scope: ['single_event'],
|
|
44
|
+
with_new_type: true,
|
|
45
|
+
});
|
|
46
|
+
return this.enrichEvents(rawEvents);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw errors_1.smarketsErrorMapper.mapError(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// -- OrderBook ------------------------------------------------------------
|
|
53
|
+
async fetchRawOrderBook(id) {
|
|
54
|
+
(0, validation_1.validateIdFormat)(id, 'OrderBook');
|
|
55
|
+
// get_quotes is marked as private in the spec but works without auth
|
|
56
|
+
// (returns delayed data). Use a direct HTTP call so it works without credentials.
|
|
57
|
+
const url = `https://api.smarkets.com/v3/markets/${encodeURIComponent(id)}/quotes/`;
|
|
58
|
+
const headers = {
|
|
59
|
+
...this.ctx.getHeaders(),
|
|
60
|
+
};
|
|
61
|
+
const response = await this.ctx.http.get(url, { headers });
|
|
62
|
+
const data = response.data;
|
|
63
|
+
if (!data || Object.keys(data).length === 0) {
|
|
64
|
+
throw new errors_2.NotFound(`Order book not found: ${id}`, 'Smarkets');
|
|
65
|
+
}
|
|
66
|
+
return data;
|
|
67
|
+
}
|
|
68
|
+
// -- Trades (account activity) --------------------------------------------
|
|
69
|
+
async fetchRawTradeActivity(marketId, _params) {
|
|
70
|
+
const data = await this.ctx.callApi('get_activity', {
|
|
71
|
+
market_id: [marketId],
|
|
72
|
+
source: ['order.execute', 'order.execute.confirm'],
|
|
73
|
+
limit: 100,
|
|
74
|
+
sort: '-seq,-subseq',
|
|
75
|
+
});
|
|
76
|
+
return (data.account_activity || []);
|
|
77
|
+
}
|
|
78
|
+
async fetchRawMyTradeActivity(params = {}) {
|
|
79
|
+
const queryParams = {
|
|
80
|
+
source: ['order.execute', 'order.execute.confirm'],
|
|
81
|
+
limit: params.limit || 100,
|
|
82
|
+
sort: '-seq,-subseq',
|
|
83
|
+
};
|
|
84
|
+
if (params.marketId)
|
|
85
|
+
queryParams.market_id = [params.marketId];
|
|
86
|
+
if (params.since)
|
|
87
|
+
queryParams.timestamp_min = params.since;
|
|
88
|
+
if (params.until)
|
|
89
|
+
queryParams.timestamp_max = params.until;
|
|
90
|
+
const data = await this.ctx.callApi('get_activity', queryParams);
|
|
91
|
+
return (data.account_activity || []);
|
|
92
|
+
}
|
|
93
|
+
// -- User data ------------------------------------------------------------
|
|
94
|
+
async fetchRawBalance() {
|
|
95
|
+
const data = await this.ctx.callApi('get_account');
|
|
96
|
+
return data.account;
|
|
97
|
+
}
|
|
98
|
+
async fetchRawOrders(queryParams = {}) {
|
|
99
|
+
const data = await this.ctx.callApi('get_orders', {
|
|
100
|
+
limit: 100,
|
|
101
|
+
...queryParams,
|
|
102
|
+
});
|
|
103
|
+
return data.orders || [];
|
|
104
|
+
}
|
|
105
|
+
async fetchRawOrderById(orderId) {
|
|
106
|
+
const data = await this.ctx.callApi('get_orders', {
|
|
107
|
+
id: [orderId],
|
|
108
|
+
limit: 1,
|
|
109
|
+
});
|
|
110
|
+
const orders = data.orders || [];
|
|
111
|
+
if (orders.length === 0) {
|
|
112
|
+
throw new errors_2.NotFound(`Order not found: ${orderId}`, 'Smarkets');
|
|
113
|
+
}
|
|
114
|
+
return orders[0];
|
|
115
|
+
}
|
|
116
|
+
async fetchRawPositions() {
|
|
117
|
+
// Smarkets does not have a dedicated positions endpoint.
|
|
118
|
+
// Derive positions from open/partial orders.
|
|
119
|
+
return this.fetchRawOrders({ state: ['created', 'partial'] });
|
|
120
|
+
}
|
|
121
|
+
async fetchRawClosedOrders(params = {}) {
|
|
122
|
+
const queryParams = {
|
|
123
|
+
state: ['filled', 'settled'],
|
|
124
|
+
};
|
|
125
|
+
if (params.marketId)
|
|
126
|
+
queryParams.market_id = [params.marketId];
|
|
127
|
+
if (params.limit)
|
|
128
|
+
queryParams.limit = params.limit;
|
|
129
|
+
if (params.since)
|
|
130
|
+
queryParams.created_datetime_min = params.since;
|
|
131
|
+
if (params.until)
|
|
132
|
+
queryParams.created_datetime_max = params.until;
|
|
133
|
+
return this.fetchRawOrders(queryParams);
|
|
134
|
+
}
|
|
135
|
+
// -- Private helpers ------------------------------------------------------
|
|
136
|
+
async fetchEnrichedEventById(eventId) {
|
|
137
|
+
const data = await this.ctx.callApi('get_events', { id: [eventId] });
|
|
138
|
+
const events = data.events || [];
|
|
139
|
+
if (events.length === 0)
|
|
140
|
+
return [];
|
|
141
|
+
return this.enrichEvents(events);
|
|
142
|
+
}
|
|
143
|
+
async fetchEnrichedEventByMarketId(marketId) {
|
|
144
|
+
// Step 1: Fetch contracts for this market to confirm it exists
|
|
145
|
+
const contractsData = await this.ctx.callApi('get_contracts_by_market_ids', {
|
|
146
|
+
market_ids: [marketId],
|
|
147
|
+
});
|
|
148
|
+
const contracts = contractsData.contracts || [];
|
|
149
|
+
if (contracts.length === 0)
|
|
150
|
+
return [];
|
|
151
|
+
// Step 2: Fetch volumes for this market
|
|
152
|
+
const volumeData = await this.ctx.callApi('get_volumes_by_market_ids', {
|
|
153
|
+
market_ids: [marketId],
|
|
154
|
+
});
|
|
155
|
+
const volumes = volumeData.volumes || [];
|
|
156
|
+
// Step 3: We need the event_id. The contracts have market_id but not event_id.
|
|
157
|
+
// Use the events/markets endpoint by searching for events that contain this market.
|
|
158
|
+
// The most reliable approach: fetch all active events and find the one that owns
|
|
159
|
+
// this market. But that's expensive. Instead, we can search events with the market
|
|
160
|
+
// contracts and use the get_events endpoint with filtering.
|
|
161
|
+
//
|
|
162
|
+
// Actually, the Smarkets API returns market_id on contracts but event_id on markets.
|
|
163
|
+
// We need to get the market object itself. The get_markets_by_event_ids endpoint
|
|
164
|
+
// requires event_ids. So we iterate: get events that are bettable and look for our market.
|
|
165
|
+
//
|
|
166
|
+
// Better approach: use get_events with no filter and check pagination.
|
|
167
|
+
// But the simplest correct approach is: get ALL bettable events, enrich them,
|
|
168
|
+
// and filter for the market. This is too expensive.
|
|
169
|
+
//
|
|
170
|
+
// Practical approach: The Smarkets API doesn't expose a direct market->event lookup.
|
|
171
|
+
// We paginate through recent events and check if any contain our market.
|
|
172
|
+
// For now, fetch a reasonable batch and search.
|
|
173
|
+
const rawEvents = await this.fetchPaginatedEvents({
|
|
174
|
+
state: ['new', 'upcoming', 'live'],
|
|
175
|
+
type_scope: ['single_event'],
|
|
176
|
+
with_new_type: true,
|
|
177
|
+
limit: BATCH_SIZE,
|
|
178
|
+
}, BATCH_SIZE * 3);
|
|
179
|
+
// Fetch markets for these events and find the one containing our marketId
|
|
180
|
+
const eventIds = rawEvents.map(e => e.id);
|
|
181
|
+
const allMarkets = await this.fetchMarketsByEventIds(eventIds);
|
|
182
|
+
const targetMarket = allMarkets.find(m => m.id === marketId);
|
|
183
|
+
if (!targetMarket) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
// Now we have the event_id
|
|
187
|
+
const eventData = await this.ctx.callApi('get_events', { id: [targetMarket.event_id] });
|
|
188
|
+
const events = eventData.events || [];
|
|
189
|
+
if (events.length === 0)
|
|
190
|
+
return [];
|
|
191
|
+
return [{
|
|
192
|
+
event: events[0],
|
|
193
|
+
markets: [targetMarket],
|
|
194
|
+
contracts,
|
|
195
|
+
volumes,
|
|
196
|
+
}];
|
|
197
|
+
}
|
|
198
|
+
async fetchAllEnrichedEvents(params) {
|
|
199
|
+
const stateFilter = this.mapEventStatus(params?.status || 'active');
|
|
200
|
+
const limit = params?.limit || 1000;
|
|
201
|
+
const rawEvents = await this.fetchPaginatedEvents({
|
|
202
|
+
state: stateFilter,
|
|
203
|
+
type_scope: ['single_event'],
|
|
204
|
+
with_new_type: true,
|
|
205
|
+
limit: Math.min(limit, BATCH_SIZE),
|
|
206
|
+
}, limit);
|
|
207
|
+
return this.enrichEvents(rawEvents);
|
|
208
|
+
}
|
|
209
|
+
async enrichEvents(events) {
|
|
210
|
+
if (events.length === 0)
|
|
211
|
+
return [];
|
|
212
|
+
const eventIds = events.map(e => e.id);
|
|
213
|
+
const allMarkets = await this.fetchMarketsByEventIds(eventIds);
|
|
214
|
+
const marketIds = allMarkets.map(m => m.id);
|
|
215
|
+
const [allContracts, fetchedVolumes] = await Promise.all([
|
|
216
|
+
this.fetchContractsByMarketIds(marketIds),
|
|
217
|
+
this.fetchVolumesByMarketIds(marketIds),
|
|
218
|
+
]);
|
|
219
|
+
const marketsByEvent = new Map();
|
|
220
|
+
for (const market of allMarkets) {
|
|
221
|
+
const existing = marketsByEvent.get(market.event_id) || [];
|
|
222
|
+
marketsByEvent.set(market.event_id, [...existing, market]);
|
|
223
|
+
}
|
|
224
|
+
const contractsByMarket = new Map();
|
|
225
|
+
for (const contract of allContracts) {
|
|
226
|
+
const existing = contractsByMarket.get(contract.market_id) || [];
|
|
227
|
+
contractsByMarket.set(contract.market_id, [...existing, contract]);
|
|
228
|
+
}
|
|
229
|
+
const volumesByMarket = new Map();
|
|
230
|
+
for (const volume of fetchedVolumes) {
|
|
231
|
+
volumesByMarket.set(volume.market_id, volume);
|
|
232
|
+
}
|
|
233
|
+
return events.map(event => {
|
|
234
|
+
const eventMarkets = marketsByEvent.get(event.id) || [];
|
|
235
|
+
const eventContracts = [];
|
|
236
|
+
const eventVolumes = [];
|
|
237
|
+
for (const market of eventMarkets) {
|
|
238
|
+
const mc = contractsByMarket.get(market.id) || [];
|
|
239
|
+
eventContracts.push(...mc);
|
|
240
|
+
const vol = volumesByMarket.get(market.id);
|
|
241
|
+
if (vol)
|
|
242
|
+
eventVolumes.push(vol);
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
event,
|
|
246
|
+
markets: eventMarkets,
|
|
247
|
+
contracts: eventContracts,
|
|
248
|
+
volumes: eventVolumes,
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async fetchPaginatedEvents(queryParams, targetCount) {
|
|
253
|
+
let allEvents = [];
|
|
254
|
+
let lastId;
|
|
255
|
+
let page = 0;
|
|
256
|
+
do {
|
|
257
|
+
const params = {
|
|
258
|
+
limit: BATCH_SIZE,
|
|
259
|
+
sort: 'id',
|
|
260
|
+
...queryParams,
|
|
261
|
+
};
|
|
262
|
+
if (lastId) {
|
|
263
|
+
params.pagination_last_id = lastId;
|
|
264
|
+
}
|
|
265
|
+
const data = await this.ctx.callApi('get_events', params);
|
|
266
|
+
const events = data.events || [];
|
|
267
|
+
if (events.length === 0)
|
|
268
|
+
break;
|
|
269
|
+
allEvents = [...allEvents, ...events];
|
|
270
|
+
// Check pagination: next_page is null when there are no more results
|
|
271
|
+
const nextPage = data.pagination?.next_page;
|
|
272
|
+
if (!nextPage)
|
|
273
|
+
break;
|
|
274
|
+
lastId = events[events.length - 1].id;
|
|
275
|
+
page++;
|
|
276
|
+
if (targetCount && allEvents.length >= targetCount)
|
|
277
|
+
break;
|
|
278
|
+
} while (page < MAX_PAGES);
|
|
279
|
+
return allEvents;
|
|
280
|
+
}
|
|
281
|
+
async fetchMarketsByEventIds(eventIds) {
|
|
282
|
+
const batches = this.batchArray(eventIds, EVENT_ID_BATCH_SIZE);
|
|
283
|
+
const results = await Promise.all(batches.map(async (batch) => {
|
|
284
|
+
const data = await this.ctx.callApi('get_markets_by_event_ids', {
|
|
285
|
+
event_ids: batch,
|
|
286
|
+
});
|
|
287
|
+
return (data.markets || []);
|
|
288
|
+
}));
|
|
289
|
+
return results.flat();
|
|
290
|
+
}
|
|
291
|
+
async fetchContractsByMarketIds(marketIds) {
|
|
292
|
+
if (marketIds.length === 0)
|
|
293
|
+
return [];
|
|
294
|
+
const batches = this.batchArray(marketIds, MARKET_ID_BATCH_SIZE);
|
|
295
|
+
const results = await Promise.all(batches.map(async (batch) => {
|
|
296
|
+
const data = await this.ctx.callApi('get_contracts_by_market_ids', {
|
|
297
|
+
market_ids: batch,
|
|
298
|
+
});
|
|
299
|
+
return (data.contracts || []);
|
|
300
|
+
}));
|
|
301
|
+
return results.flat();
|
|
302
|
+
}
|
|
303
|
+
async fetchVolumesByMarketIds(marketIds) {
|
|
304
|
+
if (marketIds.length === 0)
|
|
305
|
+
return [];
|
|
306
|
+
const batches = this.batchArray(marketIds, MARKET_ID_BATCH_SIZE);
|
|
307
|
+
const results = await Promise.all(batches.map(async (batch) => {
|
|
308
|
+
try {
|
|
309
|
+
const data = await this.ctx.callApi('get_volumes_by_market_ids', {
|
|
310
|
+
market_ids: batch,
|
|
311
|
+
});
|
|
312
|
+
return (data.volumes || []);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Volumes are non-critical; return empty on failure
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
}));
|
|
319
|
+
return results.flat();
|
|
320
|
+
}
|
|
321
|
+
mapEventStatus(status) {
|
|
322
|
+
switch (status) {
|
|
323
|
+
case 'active':
|
|
324
|
+
return ['new', 'upcoming', 'live'];
|
|
325
|
+
case 'closed':
|
|
326
|
+
case 'inactive':
|
|
327
|
+
return ['ended', 'settled'];
|
|
328
|
+
case 'all':
|
|
329
|
+
return ['new', 'upcoming', 'live', 'ended', 'settled', 'cancelled', 'suspended'];
|
|
330
|
+
default:
|
|
331
|
+
return ['new', 'upcoming', 'live'];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
batchArray(items, batchSize) {
|
|
335
|
+
const batches = [];
|
|
336
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
337
|
+
batches.push(items.slice(i, i + batchSize));
|
|
338
|
+
}
|
|
339
|
+
return batches;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
exports.SmarketsFetcher = SmarketsFetcher;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PredictionMarketExchange, MarketFilterParams, HistoryFilterParams, TradesParams, ExchangeCredentials, EventFetchParams, MyTradesParams, OrderHistoryParams } from '../../BaseExchange';
|
|
2
|
+
import { UnifiedMarket, UnifiedEvent, OrderBook, Trade, UserTrade, Balance, Order, Position, CreateOrderParams, BuiltOrder } from '../../types';
|
|
3
|
+
export declare class SmarketsExchange extends PredictionMarketExchange {
|
|
4
|
+
readonly has: {
|
|
5
|
+
fetchMarkets: true;
|
|
6
|
+
fetchEvents: true;
|
|
7
|
+
fetchOHLCV: false;
|
|
8
|
+
fetchOrderBook: true;
|
|
9
|
+
fetchTrades: true;
|
|
10
|
+
createOrder: true;
|
|
11
|
+
cancelOrder: true;
|
|
12
|
+
fetchOrder: true;
|
|
13
|
+
fetchOpenOrders: true;
|
|
14
|
+
fetchPositions: "emulated";
|
|
15
|
+
fetchBalance: true;
|
|
16
|
+
watchAddress: false;
|
|
17
|
+
unwatchAddress: false;
|
|
18
|
+
watchOrderBook: false;
|
|
19
|
+
watchTrades: false;
|
|
20
|
+
fetchMyTrades: true;
|
|
21
|
+
fetchClosedOrders: true;
|
|
22
|
+
fetchAllOrders: true;
|
|
23
|
+
buildOrder: true;
|
|
24
|
+
submitOrder: true;
|
|
25
|
+
};
|
|
26
|
+
private auth?;
|
|
27
|
+
private loginPromise;
|
|
28
|
+
private readonly config;
|
|
29
|
+
private readonly fetcher;
|
|
30
|
+
private readonly normalizer;
|
|
31
|
+
constructor(credentials?: ExchangeCredentials);
|
|
32
|
+
get name(): string;
|
|
33
|
+
private ensureSession;
|
|
34
|
+
private performLogin;
|
|
35
|
+
protected sign(_method: string, _path: string, _params: Record<string, any>): Record<string, string>;
|
|
36
|
+
protected mapImplicitApiError(error: any): any;
|
|
37
|
+
private requireAuth;
|
|
38
|
+
protected fetchMarketsImpl(params?: MarketFilterParams): Promise<UnifiedMarket[]>;
|
|
39
|
+
protected fetchEventsImpl(params: EventFetchParams): Promise<UnifiedEvent[]>;
|
|
40
|
+
fetchOrderBook(id: string): Promise<OrderBook>;
|
|
41
|
+
fetchTrades(id: string, params: TradesParams | HistoryFilterParams): Promise<Trade[]>;
|
|
42
|
+
fetchBalance(): Promise<Balance[]>;
|
|
43
|
+
fetchMyTrades(params?: MyTradesParams): Promise<UserTrade[]>;
|
|
44
|
+
fetchPositions(): Promise<Position[]>;
|
|
45
|
+
fetchClosedOrders(params?: OrderHistoryParams): Promise<Order[]>;
|
|
46
|
+
fetchAllOrders(params?: OrderHistoryParams): Promise<Order[]>;
|
|
47
|
+
buildOrder(params: CreateOrderParams): Promise<BuiltOrder>;
|
|
48
|
+
submitOrder(built: BuiltOrder): Promise<Order>;
|
|
49
|
+
createOrder(params: CreateOrderParams): Promise<Order>;
|
|
50
|
+
cancelOrder(orderId: string): Promise<Order>;
|
|
51
|
+
fetchOrder(orderId: string): Promise<Order>;
|
|
52
|
+
fetchOpenOrders(marketId?: string): Promise<Order[]>;
|
|
53
|
+
close(): Promise<void>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SmarketsExchange = void 0;
|
|
4
|
+
const BaseExchange_1 = require("../../BaseExchange");
|
|
5
|
+
const auth_1 = require("./auth");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
const errors_2 = require("../../errors");
|
|
8
|
+
const openapi_1 = require("../../utils/openapi");
|
|
9
|
+
const api_1 = require("./api");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const fetcher_1 = require("./fetcher");
|
|
12
|
+
const normalizer_1 = require("./normalizer");
|
|
13
|
+
const price_1 = require("./price");
|
|
14
|
+
class SmarketsExchange extends BaseExchange_1.PredictionMarketExchange {
|
|
15
|
+
has = {
|
|
16
|
+
fetchMarkets: true,
|
|
17
|
+
fetchEvents: true,
|
|
18
|
+
fetchOHLCV: false,
|
|
19
|
+
fetchOrderBook: true,
|
|
20
|
+
fetchTrades: true,
|
|
21
|
+
createOrder: true,
|
|
22
|
+
cancelOrder: true,
|
|
23
|
+
fetchOrder: true,
|
|
24
|
+
fetchOpenOrders: true,
|
|
25
|
+
fetchPositions: 'emulated',
|
|
26
|
+
fetchBalance: true,
|
|
27
|
+
watchAddress: false,
|
|
28
|
+
unwatchAddress: false,
|
|
29
|
+
watchOrderBook: false,
|
|
30
|
+
watchTrades: false,
|
|
31
|
+
fetchMyTrades: true,
|
|
32
|
+
fetchClosedOrders: true,
|
|
33
|
+
fetchAllOrders: true,
|
|
34
|
+
buildOrder: true,
|
|
35
|
+
submitOrder: true,
|
|
36
|
+
};
|
|
37
|
+
auth;
|
|
38
|
+
loginPromise = null;
|
|
39
|
+
config;
|
|
40
|
+
fetcher;
|
|
41
|
+
normalizer;
|
|
42
|
+
constructor(credentials) {
|
|
43
|
+
super(credentials);
|
|
44
|
+
this.rateLimit = 100;
|
|
45
|
+
this.config = (0, config_1.getSmarketsConfig)();
|
|
46
|
+
// Smarkets API expects repeated keys for arrays (e.g. state=new&state=live)
|
|
47
|
+
// rather than the axios default bracket format (state[]=new&state[]=live)
|
|
48
|
+
this.http.defaults.paramsSerializer = (params) => {
|
|
49
|
+
const parts = [];
|
|
50
|
+
for (const [key, value] of Object.entries(params)) {
|
|
51
|
+
if (value === undefined || value === null)
|
|
52
|
+
continue;
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
for (const item of value) {
|
|
55
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(item))}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return parts.join('&');
|
|
63
|
+
};
|
|
64
|
+
if (credentials?.apiKey && credentials?.privateKey) {
|
|
65
|
+
this.auth = new auth_1.SmarketsAuth(credentials);
|
|
66
|
+
}
|
|
67
|
+
const descriptor = (0, openapi_1.parseOpenApiSpec)(api_1.smarketsApiSpec, this.config.apiUrl);
|
|
68
|
+
this.defineImplicitApi(descriptor);
|
|
69
|
+
const ctx = {
|
|
70
|
+
http: this.http,
|
|
71
|
+
callApi: this.callApi.bind(this),
|
|
72
|
+
getHeaders: () => this.auth?.getHeaders('', '') ?? {},
|
|
73
|
+
};
|
|
74
|
+
this.fetcher = new fetcher_1.SmarketsFetcher(ctx);
|
|
75
|
+
this.normalizer = new normalizer_1.SmarketsNormalizer();
|
|
76
|
+
}
|
|
77
|
+
get name() {
|
|
78
|
+
return 'Smarkets';
|
|
79
|
+
}
|
|
80
|
+
// -------------------------------------------------------------------------
|
|
81
|
+
// Session Management
|
|
82
|
+
// -------------------------------------------------------------------------
|
|
83
|
+
async ensureSession() {
|
|
84
|
+
const auth = this.requireAuth();
|
|
85
|
+
if (auth.isAuthenticated()) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Deduplicate concurrent login attempts
|
|
89
|
+
if (this.loginPromise) {
|
|
90
|
+
return this.loginPromise;
|
|
91
|
+
}
|
|
92
|
+
this.loginPromise = this.performLogin(auth);
|
|
93
|
+
try {
|
|
94
|
+
await this.loginPromise;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
this.loginPromise = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async performLogin(auth) {
|
|
101
|
+
const response = await this.http.post(`${this.config.apiUrl}/v3/sessions/`, {
|
|
102
|
+
username: auth.getUsername(),
|
|
103
|
+
password: auth.getPassword(),
|
|
104
|
+
}, { headers: { 'Content-Type': 'application/json' } });
|
|
105
|
+
const data = response.data;
|
|
106
|
+
if (data.factor && data.factor !== 'complete') {
|
|
107
|
+
throw new errors_2.AuthenticationError(`Smarkets requires additional authentication factor: ${data.factor}. ` +
|
|
108
|
+
'MFA (TOTP/NemID) is not yet supported by pmxt.', 'Smarkets');
|
|
109
|
+
}
|
|
110
|
+
if (!data.token) {
|
|
111
|
+
throw new errors_2.AuthenticationError('Smarkets login succeeded but no session token was returned.', 'Smarkets');
|
|
112
|
+
}
|
|
113
|
+
auth.setToken(data.token, data.stop || '');
|
|
114
|
+
}
|
|
115
|
+
// -------------------------------------------------------------------------
|
|
116
|
+
// Implicit API Auth & Error Mapping
|
|
117
|
+
// -------------------------------------------------------------------------
|
|
118
|
+
sign(_method, _path, _params) {
|
|
119
|
+
const auth = this.requireAuth();
|
|
120
|
+
return auth.getHeaders(_method, _path);
|
|
121
|
+
}
|
|
122
|
+
mapImplicitApiError(error) {
|
|
123
|
+
throw errors_1.smarketsErrorMapper.mapError(error);
|
|
124
|
+
}
|
|
125
|
+
requireAuth() {
|
|
126
|
+
if (!this.auth) {
|
|
127
|
+
throw new errors_2.AuthenticationError('This operation requires authentication. ' +
|
|
128
|
+
'Initialize SmarketsExchange with credentials (apiKey and privateKey).', 'Smarkets');
|
|
129
|
+
}
|
|
130
|
+
return this.auth;
|
|
131
|
+
}
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
// Market Data (fetcher -> normalizer)
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
async fetchMarketsImpl(params) {
|
|
136
|
+
const rawEvents = await this.fetcher.fetchRawMarkets(params);
|
|
137
|
+
const allMarkets = [];
|
|
138
|
+
for (const event of rawEvents) {
|
|
139
|
+
const markets = this.normalizer.normalizeMarketsFromEvent(event);
|
|
140
|
+
allMarkets.push(...markets);
|
|
141
|
+
}
|
|
142
|
+
// Query-based search (client-side filtering)
|
|
143
|
+
if (params?.query) {
|
|
144
|
+
const lowerQuery = params.query.toLowerCase();
|
|
145
|
+
const searchIn = params?.searchIn || 'title';
|
|
146
|
+
const filtered = allMarkets.filter((market) => {
|
|
147
|
+
const titleMatch = (market.title || '').toLowerCase().includes(lowerQuery);
|
|
148
|
+
const descMatch = (market.description || '').toLowerCase().includes(lowerQuery);
|
|
149
|
+
if (searchIn === 'title')
|
|
150
|
+
return titleMatch;
|
|
151
|
+
if (searchIn === 'description')
|
|
152
|
+
return descMatch;
|
|
153
|
+
return titleMatch || descMatch;
|
|
154
|
+
});
|
|
155
|
+
return filtered.slice(0, params?.limit || 250000);
|
|
156
|
+
}
|
|
157
|
+
// Client-side sort
|
|
158
|
+
if (params?.sort === 'volume') {
|
|
159
|
+
allMarkets.sort((a, b) => b.volume24h - a.volume24h);
|
|
160
|
+
}
|
|
161
|
+
else if (params?.sort === 'liquidity') {
|
|
162
|
+
allMarkets.sort((a, b) => b.liquidity - a.liquidity);
|
|
163
|
+
}
|
|
164
|
+
const offset = params?.offset || 0;
|
|
165
|
+
const limit = params?.limit || 250000;
|
|
166
|
+
return allMarkets.slice(offset, offset + limit);
|
|
167
|
+
}
|
|
168
|
+
async fetchEventsImpl(params) {
|
|
169
|
+
const rawEvents = await this.fetcher.fetchRawEvents(params);
|
|
170
|
+
const limit = params?.limit || 10000;
|
|
171
|
+
const query = (params?.query || '').toLowerCase();
|
|
172
|
+
const filtered = query
|
|
173
|
+
? rawEvents.filter((event) => (event.event.name || '').toLowerCase().includes(query))
|
|
174
|
+
: rawEvents;
|
|
175
|
+
return filtered
|
|
176
|
+
.map((raw) => this.normalizer.normalizeEvent(raw))
|
|
177
|
+
.filter((e) => e !== null)
|
|
178
|
+
.slice(0, limit);
|
|
179
|
+
}
|
|
180
|
+
async fetchOrderBook(id) {
|
|
181
|
+
const raw = await this.fetcher.fetchRawOrderBook(id);
|
|
182
|
+
return this.normalizer.normalizeOrderBook(raw, id);
|
|
183
|
+
}
|
|
184
|
+
async fetchTrades(id, params) {
|
|
185
|
+
if ('resolution' in params && params.resolution !== undefined) {
|
|
186
|
+
console.warn('[pmxt] Warning: The "resolution" parameter is deprecated for fetchTrades() and will be ignored. ' +
|
|
187
|
+
'It will be removed in v3.0.0. Please remove it from your code.');
|
|
188
|
+
}
|
|
189
|
+
const rawActivity = await this.fetcher.fetchRawTradeActivity(id, params);
|
|
190
|
+
return rawActivity.map((raw, i) => this.normalizer.normalizeActivityTrade(raw, i));
|
|
191
|
+
}
|
|
192
|
+
// -------------------------------------------------------------------------
|
|
193
|
+
// User Data (fetcher -> normalizer)
|
|
194
|
+
// -------------------------------------------------------------------------
|
|
195
|
+
async fetchBalance() {
|
|
196
|
+
await this.ensureSession();
|
|
197
|
+
const raw = await this.fetcher.fetchRawBalance();
|
|
198
|
+
return this.normalizer.normalizeBalance(raw);
|
|
199
|
+
}
|
|
200
|
+
async fetchMyTrades(params) {
|
|
201
|
+
await this.ensureSession();
|
|
202
|
+
const rawActivity = await this.fetcher.fetchRawMyTradeActivity(params || {});
|
|
203
|
+
return rawActivity.map((raw, i) => this.normalizer.normalizeActivityUserTrade(raw, i));
|
|
204
|
+
}
|
|
205
|
+
async fetchPositions() {
|
|
206
|
+
await this.ensureSession();
|
|
207
|
+
const rawPositions = await this.fetcher.fetchRawPositions();
|
|
208
|
+
return rawPositions.map((raw) => this.normalizer.normalizePosition(raw));
|
|
209
|
+
}
|
|
210
|
+
async fetchClosedOrders(params) {
|
|
211
|
+
await this.ensureSession();
|
|
212
|
+
const rawOrders = await this.fetcher.fetchRawClosedOrders(params || {});
|
|
213
|
+
return rawOrders.map((o) => this.normalizer.normalizeOrder(o));
|
|
214
|
+
}
|
|
215
|
+
async fetchAllOrders(params) {
|
|
216
|
+
await this.ensureSession();
|
|
217
|
+
const [openOrders, closedOrders] = await Promise.all([
|
|
218
|
+
this.fetcher.fetchRawOrders({}),
|
|
219
|
+
this.fetcher.fetchRawClosedOrders(params || {}),
|
|
220
|
+
]);
|
|
221
|
+
const seen = new Set();
|
|
222
|
+
const all = [];
|
|
223
|
+
for (const o of [...openOrders, ...closedOrders]) {
|
|
224
|
+
const id = String(o.id || o.order_id);
|
|
225
|
+
if (!seen.has(id)) {
|
|
226
|
+
seen.add(id);
|
|
227
|
+
all.push(this.normalizer.normalizeOrder(o));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return all.sort((a, b) => b.timestamp - a.timestamp);
|
|
231
|
+
}
|
|
232
|
+
// -------------------------------------------------------------------------
|
|
233
|
+
// Trading
|
|
234
|
+
// -------------------------------------------------------------------------
|
|
235
|
+
async buildOrder(params) {
|
|
236
|
+
const smarketsType = params.type === 'market'
|
|
237
|
+
? 'immediate_or_cancel'
|
|
238
|
+
: 'good_til_halted';
|
|
239
|
+
const body = {
|
|
240
|
+
market_id: params.marketId,
|
|
241
|
+
contract_id: params.outcomeId,
|
|
242
|
+
side: params.side === 'buy' ? 'buy' : 'sell',
|
|
243
|
+
quantity: (0, price_1.toQuantityUnits)(params.amount),
|
|
244
|
+
type: smarketsType,
|
|
245
|
+
};
|
|
246
|
+
if (params.price !== undefined) {
|
|
247
|
+
body.price = (0, price_1.toBasisPoints)(params.price);
|
|
248
|
+
}
|
|
249
|
+
return { exchange: this.name, params, raw: body };
|
|
250
|
+
}
|
|
251
|
+
async submitOrder(built) {
|
|
252
|
+
await this.ensureSession();
|
|
253
|
+
const data = await this.callApi('create_order', built.raw);
|
|
254
|
+
return this.normalizer.normalizeCreateOrderResponse(data);
|
|
255
|
+
}
|
|
256
|
+
async createOrder(params) {
|
|
257
|
+
const built = await this.buildOrder(params);
|
|
258
|
+
return this.submitOrder(built);
|
|
259
|
+
}
|
|
260
|
+
async cancelOrder(orderId) {
|
|
261
|
+
await this.ensureSession();
|
|
262
|
+
await this.callApi('cancel_order', { order_id: orderId });
|
|
263
|
+
// cancel_order returns empty {} on success; fetch the order to get its state
|
|
264
|
+
return this.fetchOrder(orderId);
|
|
265
|
+
}
|
|
266
|
+
async fetchOrder(orderId) {
|
|
267
|
+
await this.ensureSession();
|
|
268
|
+
const data = await this.fetcher.fetchRawOrderById(orderId);
|
|
269
|
+
return this.normalizer.normalizeOrder(data);
|
|
270
|
+
}
|
|
271
|
+
async fetchOpenOrders(marketId) {
|
|
272
|
+
await this.ensureSession();
|
|
273
|
+
const queryParams = {
|
|
274
|
+
state: ['created', 'partial'],
|
|
275
|
+
};
|
|
276
|
+
if (marketId)
|
|
277
|
+
queryParams.market_id = [marketId];
|
|
278
|
+
const rawOrders = await this.fetcher.fetchRawOrders(queryParams);
|
|
279
|
+
return rawOrders.map((o) => this.normalizer.normalizeOrder(o));
|
|
280
|
+
}
|
|
281
|
+
async close() {
|
|
282
|
+
// No WebSocket or persistent connections to clean up
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
exports.SmarketsExchange = SmarketsExchange;
|