@liberfi.io/react-predict 0.1.1
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/README.md +447 -0
- package/dist/index.d.mts +589 -0
- package/dist/index.d.ts +589 -0
- package/dist/index.js +1536 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1460 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server-DlSG7h_v.d.mts +1008 -0
- package/dist/server-DlSG7h_v.d.ts +1008 -0
- package/dist/server.d.mts +2 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +795 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +765 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +61 -0
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import '@tanstack/react-query';
|
|
2
|
+
import { createContext } from 'react';
|
|
3
|
+
import { httpGet, httpDelete, httpPost } from '@liberfi.io/utils';
|
|
4
|
+
|
|
5
|
+
// src/hooks/predict/events.params.ts
|
|
6
|
+
var DEFAULT_PAGE_SIZE = 48;
|
|
7
|
+
function resolveTagSlug(selection) {
|
|
8
|
+
if (!selection) return void 0;
|
|
9
|
+
return selection.tagSlug ?? selection.categorySlug ?? void 0;
|
|
10
|
+
}
|
|
11
|
+
function resolveEventsParams(input = {}) {
|
|
12
|
+
const {
|
|
13
|
+
tagSlugSelection,
|
|
14
|
+
limit = DEFAULT_PAGE_SIZE,
|
|
15
|
+
status = "open",
|
|
16
|
+
with_markets = true,
|
|
17
|
+
source,
|
|
18
|
+
sort_by,
|
|
19
|
+
sort_asc
|
|
20
|
+
} = input;
|
|
21
|
+
const tag_slug = resolveTagSlug(tagSlugSelection);
|
|
22
|
+
return {
|
|
23
|
+
limit,
|
|
24
|
+
status,
|
|
25
|
+
with_markets,
|
|
26
|
+
...source ? { source } : {},
|
|
27
|
+
...tag_slug ? { tag_slug } : {},
|
|
28
|
+
...sort_by ? { sort_by } : {},
|
|
29
|
+
...sort_asc !== void 0 ? { sort_asc } : {}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function infiniteEventsQueryKey(params) {
|
|
33
|
+
return ["predict", "events-infinite", params];
|
|
34
|
+
}
|
|
35
|
+
async function fetchEventsPage(client, params) {
|
|
36
|
+
return client.listEvents(params);
|
|
37
|
+
}
|
|
38
|
+
function eventQueryKey(slug, source) {
|
|
39
|
+
return ["predict", "event", slug, source];
|
|
40
|
+
}
|
|
41
|
+
async function fetchEvent(client, slug, source) {
|
|
42
|
+
return client.getEvent(slug, source);
|
|
43
|
+
}
|
|
44
|
+
createContext(null);
|
|
45
|
+
|
|
46
|
+
// src/hooks/predict/useMarket.ts
|
|
47
|
+
function marketQueryKey(slug, source) {
|
|
48
|
+
return ["predict", "market", slug, source];
|
|
49
|
+
}
|
|
50
|
+
async function fetchMarket(client, slug, source) {
|
|
51
|
+
return client.getMarket(slug, source);
|
|
52
|
+
}
|
|
53
|
+
function buildQuery(params) {
|
|
54
|
+
const qs = new URLSearchParams();
|
|
55
|
+
for (const [key, value] of Object.entries(params)) {
|
|
56
|
+
if (value !== void 0 && value !== null) {
|
|
57
|
+
qs.set(key, String(value));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const str = qs.toString();
|
|
61
|
+
return str ? `?${str}` : "";
|
|
62
|
+
}
|
|
63
|
+
var PredictClient = class {
|
|
64
|
+
constructor(endpoint) {
|
|
65
|
+
this.endpoint = endpoint;
|
|
66
|
+
}
|
|
67
|
+
// -------------------------------------------------------------------------
|
|
68
|
+
// Events
|
|
69
|
+
// -------------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* List prediction events with optional filtering, sorting, and pagination.
|
|
72
|
+
*
|
|
73
|
+
* Maps to `GET /api/v1/events`.
|
|
74
|
+
*
|
|
75
|
+
* @param params - Optional query parameters (filter, sort, pagination).
|
|
76
|
+
* @returns A paginated page of events.
|
|
77
|
+
*/
|
|
78
|
+
async listEvents(params) {
|
|
79
|
+
const query = buildQuery(params ?? {});
|
|
80
|
+
const url = `${this.endpoint}/api/v1/events${query}`;
|
|
81
|
+
return await httpGet(url);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fetch a single prediction event by its slug.
|
|
85
|
+
*
|
|
86
|
+
* Maps to `GET /api/v1/events/:slug?source=...`.
|
|
87
|
+
*
|
|
88
|
+
* @param slug - Canonical event slug (e.g. "will-trump-win-2024" for Polymarket
|
|
89
|
+
* or "KXBTCD-25FEB-T68000" for DFlow).
|
|
90
|
+
* @param source - Upstream provider (`"dflow"` or `"polymarket"`).
|
|
91
|
+
* @returns The matching event.
|
|
92
|
+
* @throws When the server responds with 404 or any other non-2xx status.
|
|
93
|
+
*/
|
|
94
|
+
async getEvent(slug, source) {
|
|
95
|
+
const query = source ? buildQuery({ source }) : "";
|
|
96
|
+
const url = `${this.endpoint}/api/v1/events/${encodeURIComponent(slug)}${query}`;
|
|
97
|
+
return await httpGet(url);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Fetch events similar to the given slug.
|
|
101
|
+
*
|
|
102
|
+
* Maps to `GET /api/v1/events/:slug/similar?source=...`.
|
|
103
|
+
*/
|
|
104
|
+
async getSimilarEvents(slug, source, params) {
|
|
105
|
+
const query = buildQuery({ source, ...params });
|
|
106
|
+
const url = `${this.endpoint}/api/v1/events/${encodeURIComponent(slug)}/similar${query}`;
|
|
107
|
+
return await httpGet(url);
|
|
108
|
+
}
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
// Markets
|
|
111
|
+
// -------------------------------------------------------------------------
|
|
112
|
+
/**
|
|
113
|
+
* Fetch a single prediction market by its slug.
|
|
114
|
+
*
|
|
115
|
+
* Maps to `GET /api/v1/markets/:slug?source=...`.
|
|
116
|
+
*
|
|
117
|
+
* @param slug - Canonical market slug.
|
|
118
|
+
* @param source - Upstream provider (`"dflow"` or `"polymarket"`).
|
|
119
|
+
* @returns The matching market.
|
|
120
|
+
* @throws When the server responds with 404 or any other non-2xx status.
|
|
121
|
+
*/
|
|
122
|
+
async getMarket(slug, source) {
|
|
123
|
+
const query = source ? buildQuery({ source }) : "";
|
|
124
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}${query}`;
|
|
125
|
+
return await httpGet(url);
|
|
126
|
+
}
|
|
127
|
+
/** Maps to `GET /api/v1/markets/:slug/orderbook?source=...`. */
|
|
128
|
+
async getOrderbook(slug, source) {
|
|
129
|
+
const query = buildQuery({ source });
|
|
130
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/orderbook${query}`;
|
|
131
|
+
return await httpGet(url);
|
|
132
|
+
}
|
|
133
|
+
/** Maps to `GET /api/v1/markets/:slug/trades?source=...`. */
|
|
134
|
+
async listMarketTrades(slug, params) {
|
|
135
|
+
const query = buildQuery(params);
|
|
136
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/trades${query}`;
|
|
137
|
+
return await httpGet(url);
|
|
138
|
+
}
|
|
139
|
+
/** Maps to `GET /api/v1/markets/:slug/price-history?source=...&range=...`. */
|
|
140
|
+
async getPriceHistory(slug, source, range) {
|
|
141
|
+
const query = buildQuery({ source, range });
|
|
142
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/price-history${query}`;
|
|
143
|
+
return await httpGet(url);
|
|
144
|
+
}
|
|
145
|
+
/** Maps to `GET /api/v1/markets/:slug/candlesticks?interval=...&limit=...`. */
|
|
146
|
+
async listCandlesticks(slug, params) {
|
|
147
|
+
const query = buildQuery(params ?? {});
|
|
148
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/candlesticks${query}`;
|
|
149
|
+
return await httpGet(url);
|
|
150
|
+
}
|
|
151
|
+
// -------------------------------------------------------------------------
|
|
152
|
+
// Positions
|
|
153
|
+
// -------------------------------------------------------------------------
|
|
154
|
+
/**
|
|
155
|
+
* Maps to `GET /api/v1/positions?source=...&user=...`.
|
|
156
|
+
*
|
|
157
|
+
* @param user - Wallet address.
|
|
158
|
+
* @param source - Provider source. Omit to aggregate all providers.
|
|
159
|
+
*/
|
|
160
|
+
async getPositions(user, source) {
|
|
161
|
+
const query = buildQuery({ source, user });
|
|
162
|
+
const url = `${this.endpoint}/api/v1/positions${query}`;
|
|
163
|
+
return await httpGet(url);
|
|
164
|
+
}
|
|
165
|
+
// -------------------------------------------------------------------------
|
|
166
|
+
// Balance
|
|
167
|
+
// -------------------------------------------------------------------------
|
|
168
|
+
/**
|
|
169
|
+
* Get the on-chain USDC balance for a wallet.
|
|
170
|
+
*
|
|
171
|
+
* Maps to `GET /api/v1/balance?source=...&user=...`.
|
|
172
|
+
*
|
|
173
|
+
* @param source - Provider source (`"dflow"` for Solana, `"polymarket"` for Polygon).
|
|
174
|
+
* @param user - Wallet address.
|
|
175
|
+
*/
|
|
176
|
+
async getBalance(source, user) {
|
|
177
|
+
const query = buildQuery({ source, user });
|
|
178
|
+
const url = `${this.endpoint}/api/v1/balance${query}`;
|
|
179
|
+
return await httpGet(url);
|
|
180
|
+
}
|
|
181
|
+
// -------------------------------------------------------------------------
|
|
182
|
+
// Orders
|
|
183
|
+
// -------------------------------------------------------------------------
|
|
184
|
+
/** Maps to `GET /api/v1/orders?source=...&wallet_address=...`. */
|
|
185
|
+
async listOrders(params) {
|
|
186
|
+
const query = buildQuery(params);
|
|
187
|
+
const url = `${this.endpoint}/api/v1/orders${query}`;
|
|
188
|
+
return await httpGet(url);
|
|
189
|
+
}
|
|
190
|
+
/** Maps to `GET /api/v1/orders/:id?source=...`. */
|
|
191
|
+
async getOrder(id, source) {
|
|
192
|
+
const query = buildQuery({ source });
|
|
193
|
+
const url = `${this.endpoint}/api/v1/orders/${encodeURIComponent(id)}${query}`;
|
|
194
|
+
return await httpGet(url);
|
|
195
|
+
}
|
|
196
|
+
/** Maps to `DELETE /api/v1/orders/:id?source=...`. */
|
|
197
|
+
async cancelOrder(id, source) {
|
|
198
|
+
const query = buildQuery({ source });
|
|
199
|
+
const url = `${this.endpoint}/api/v1/orders/${encodeURIComponent(id)}${query}`;
|
|
200
|
+
return await httpDelete(url);
|
|
201
|
+
}
|
|
202
|
+
// -------------------------------------------------------------------------
|
|
203
|
+
// Polymarket trading
|
|
204
|
+
// -------------------------------------------------------------------------
|
|
205
|
+
/**
|
|
206
|
+
* Create a Polymarket limit order via the prediction-server proxy.
|
|
207
|
+
*
|
|
208
|
+
* Maps to `POST /api/v1/orders/polymarket`.
|
|
209
|
+
*
|
|
210
|
+
* The caller must attach Polymarket CLOB authentication headers:
|
|
211
|
+
* - `POLY_ADDRESS` — the EVM wallet address.
|
|
212
|
+
* - `POLY_SIGNATURE` — L1 signature or L2 API key.
|
|
213
|
+
* - `POLY_TIMESTAMP` — Unix milliseconds string.
|
|
214
|
+
* - `POLY_NONCE` — credential nonce (use `"0"` for in-session derived creds).
|
|
215
|
+
*
|
|
216
|
+
* @param input - Order parameters.
|
|
217
|
+
* @param headers - Polymarket CLOB auth headers (`POLY_*`).
|
|
218
|
+
*/
|
|
219
|
+
async createPolymarketOrder(input, headers) {
|
|
220
|
+
const url = `${this.endpoint}/api/v1/orders/polymarket`;
|
|
221
|
+
return await httpPost(url, input, { headers });
|
|
222
|
+
}
|
|
223
|
+
// -------------------------------------------------------------------------
|
|
224
|
+
// DFlow trading
|
|
225
|
+
// -------------------------------------------------------------------------
|
|
226
|
+
/** Maps to `POST /api/v1/orders/dflow/quote`. */
|
|
227
|
+
async createDFlowQuote(body) {
|
|
228
|
+
const url = `${this.endpoint}/api/v1/orders/dflow/quote`;
|
|
229
|
+
return await httpPost(url, body);
|
|
230
|
+
}
|
|
231
|
+
/** Maps to `POST /api/v1/orders/dflow/submit`. */
|
|
232
|
+
async submitDFlowTransaction(body) {
|
|
233
|
+
const url = `${this.endpoint}/api/v1/orders/dflow/submit`;
|
|
234
|
+
return await httpPost(url, body);
|
|
235
|
+
}
|
|
236
|
+
// -------------------------------------------------------------------------
|
|
237
|
+
// Trades by wallet
|
|
238
|
+
// -------------------------------------------------------------------------
|
|
239
|
+
/** Maps to `GET /api/v1/trades?source=...&wallet=...`. */
|
|
240
|
+
async listTrades(params) {
|
|
241
|
+
const query = buildQuery(params);
|
|
242
|
+
const url = `${this.endpoint}/api/v1/trades${query}`;
|
|
243
|
+
return await httpGet(url);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
function createPredictClient(endpoint) {
|
|
247
|
+
return new PredictClient(endpoint);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/client/ws.ts
|
|
251
|
+
var DEFAULT_RECONNECT_BASE = 1e3;
|
|
252
|
+
var DEFAULT_RECONNECT_MAX = 3e4;
|
|
253
|
+
var DEFAULT_PING_INTERVAL = 2e4;
|
|
254
|
+
var DEFAULT_PONG_TIMEOUT = 1e4;
|
|
255
|
+
var PredictWsClient = class {
|
|
256
|
+
ws = null;
|
|
257
|
+
wsUrl;
|
|
258
|
+
autoReconnect;
|
|
259
|
+
reconnectBase;
|
|
260
|
+
reconnectMax;
|
|
261
|
+
pingMs;
|
|
262
|
+
pongTimeoutMs;
|
|
263
|
+
status = "disconnected";
|
|
264
|
+
reconnectAttempts = 0;
|
|
265
|
+
reconnectTimer = null;
|
|
266
|
+
pingTimer = null;
|
|
267
|
+
pongTimer = null;
|
|
268
|
+
shouldReconnect = true;
|
|
269
|
+
listeners = {
|
|
270
|
+
connect: [],
|
|
271
|
+
disconnect: [],
|
|
272
|
+
reconnecting: [],
|
|
273
|
+
error: [],
|
|
274
|
+
status: [],
|
|
275
|
+
subscribed: [],
|
|
276
|
+
orderbook: [],
|
|
277
|
+
prices: [],
|
|
278
|
+
trades: []
|
|
279
|
+
};
|
|
280
|
+
subs = {
|
|
281
|
+
channels: /* @__PURE__ */ new Map([
|
|
282
|
+
["orderbook", /* @__PURE__ */ new Set()],
|
|
283
|
+
["prices", /* @__PURE__ */ new Set()],
|
|
284
|
+
["trades", /* @__PURE__ */ new Set()]
|
|
285
|
+
])
|
|
286
|
+
};
|
|
287
|
+
constructor(config) {
|
|
288
|
+
this.wsUrl = config.wsUrl;
|
|
289
|
+
this.autoReconnect = config.autoReconnect ?? true;
|
|
290
|
+
this.reconnectBase = config.reconnectIntervalBase ?? DEFAULT_RECONNECT_BASE;
|
|
291
|
+
this.reconnectMax = config.reconnectMaxInterval ?? DEFAULT_RECONNECT_MAX;
|
|
292
|
+
this.pingMs = config.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
293
|
+
this.pongTimeoutMs = config.pongTimeout ?? DEFAULT_PONG_TIMEOUT;
|
|
294
|
+
if (config.autoConnect !== false) {
|
|
295
|
+
this.connect();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// -------------------------------------------------------------------------
|
|
299
|
+
// Connection management
|
|
300
|
+
// -------------------------------------------------------------------------
|
|
301
|
+
connect() {
|
|
302
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.shouldReconnect = true;
|
|
306
|
+
this.setStatus("connecting");
|
|
307
|
+
try {
|
|
308
|
+
this.ws = new WebSocket(this.wsUrl);
|
|
309
|
+
this.ws.onopen = () => {
|
|
310
|
+
this.reconnectAttempts = 0;
|
|
311
|
+
this.setStatus("connected");
|
|
312
|
+
this.emit("connect", void 0);
|
|
313
|
+
this.restoreSubscriptions();
|
|
314
|
+
this.startPing();
|
|
315
|
+
};
|
|
316
|
+
this.ws.onmessage = (event) => {
|
|
317
|
+
this.handleMessage(event.data);
|
|
318
|
+
};
|
|
319
|
+
this.ws.onerror = () => {
|
|
320
|
+
};
|
|
321
|
+
this.ws.onclose = (event) => {
|
|
322
|
+
this.stopPing();
|
|
323
|
+
this.setStatus("disconnected");
|
|
324
|
+
this.emit("disconnect", { code: event.code, reason: event.reason });
|
|
325
|
+
if (this.shouldReconnect && this.autoReconnect) {
|
|
326
|
+
this.scheduleReconnect();
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
} catch {
|
|
330
|
+
if (this.shouldReconnect && this.autoReconnect) {
|
|
331
|
+
this.scheduleReconnect();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
disconnect() {
|
|
336
|
+
this.shouldReconnect = false;
|
|
337
|
+
this.stopPing();
|
|
338
|
+
this.clearReconnectTimer();
|
|
339
|
+
if (this.ws) {
|
|
340
|
+
this.ws.close();
|
|
341
|
+
this.ws = null;
|
|
342
|
+
}
|
|
343
|
+
this.setStatus("disconnected");
|
|
344
|
+
}
|
|
345
|
+
/** Disconnect and clear all subscription state and listeners. */
|
|
346
|
+
destroy() {
|
|
347
|
+
this.disconnect();
|
|
348
|
+
for (const ch of this.subs.channels.values()) ch.clear();
|
|
349
|
+
for (const key of Object.keys(this.listeners)) {
|
|
350
|
+
this.listeners[key].length = 0;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
getStatus() {
|
|
354
|
+
return this.status;
|
|
355
|
+
}
|
|
356
|
+
isConnected() {
|
|
357
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
358
|
+
}
|
|
359
|
+
// -------------------------------------------------------------------------
|
|
360
|
+
// Subscription — low-level (multi-channel, multi-slug)
|
|
361
|
+
// -------------------------------------------------------------------------
|
|
362
|
+
/**
|
|
363
|
+
* Subscribe to one or more channels for the given market slugs.
|
|
364
|
+
* The server acknowledges with a `subscribed` message.
|
|
365
|
+
*/
|
|
366
|
+
subscribe(channels, marketSlugs) {
|
|
367
|
+
for (const ch of channels) {
|
|
368
|
+
const set = this.subs.channels.get(ch);
|
|
369
|
+
for (const slug of marketSlugs) set.add(slug);
|
|
370
|
+
}
|
|
371
|
+
this.send({ type: "subscribe", channels, market_slugs: marketSlugs });
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Unsubscribe from one or more channels for the given market slugs.
|
|
375
|
+
*/
|
|
376
|
+
unsubscribe(channels, marketSlugs) {
|
|
377
|
+
for (const ch of channels) {
|
|
378
|
+
const set = this.subs.channels.get(ch);
|
|
379
|
+
for (const slug of marketSlugs) set.delete(slug);
|
|
380
|
+
}
|
|
381
|
+
this.send({ type: "unsubscribe", channels, market_slugs: marketSlugs });
|
|
382
|
+
}
|
|
383
|
+
// -------------------------------------------------------------------------
|
|
384
|
+
// Subscription — convenience (single channel)
|
|
385
|
+
// -------------------------------------------------------------------------
|
|
386
|
+
/**
|
|
387
|
+
* Subscribe to price updates for the given slugs.
|
|
388
|
+
* Returns an unsubscribe function.
|
|
389
|
+
*/
|
|
390
|
+
subscribePrices(slugs, onUpdate) {
|
|
391
|
+
this.subscribe(["prices"], slugs);
|
|
392
|
+
const off = this.on("prices", onUpdate);
|
|
393
|
+
return () => {
|
|
394
|
+
off();
|
|
395
|
+
this.unsubscribe(["prices"], slugs);
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Subscribe to orderbook snapshots for the given slugs.
|
|
400
|
+
* Returns an unsubscribe function.
|
|
401
|
+
*/
|
|
402
|
+
subscribeOrderbook(slugs, onUpdate) {
|
|
403
|
+
this.subscribe(["orderbook"], slugs);
|
|
404
|
+
const off = this.on("orderbook", onUpdate);
|
|
405
|
+
return () => {
|
|
406
|
+
off();
|
|
407
|
+
this.unsubscribe(["orderbook"], slugs);
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Subscribe to trade events for the given slugs.
|
|
412
|
+
* Returns an unsubscribe function.
|
|
413
|
+
*/
|
|
414
|
+
subscribeTrades(slugs, onUpdate) {
|
|
415
|
+
this.subscribe(["trades"], slugs);
|
|
416
|
+
const off = this.on("trades", onUpdate);
|
|
417
|
+
return () => {
|
|
418
|
+
off();
|
|
419
|
+
this.unsubscribe(["trades"], slugs);
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
// -------------------------------------------------------------------------
|
|
423
|
+
// Status observation
|
|
424
|
+
// -------------------------------------------------------------------------
|
|
425
|
+
/** Register a callback for connection status changes. Returns unsubscribe. */
|
|
426
|
+
onStatusChange(cb) {
|
|
427
|
+
return this.on("status", cb);
|
|
428
|
+
}
|
|
429
|
+
/** Register a callback for server-sent errors. Returns unsubscribe. */
|
|
430
|
+
onError(cb) {
|
|
431
|
+
return this.on("error", cb);
|
|
432
|
+
}
|
|
433
|
+
// -------------------------------------------------------------------------
|
|
434
|
+
// Event emitter
|
|
435
|
+
// -------------------------------------------------------------------------
|
|
436
|
+
on(event, callback) {
|
|
437
|
+
const list = this.listeners[event];
|
|
438
|
+
list.push(callback);
|
|
439
|
+
return () => this.off(event, callback);
|
|
440
|
+
}
|
|
441
|
+
off(event, callback) {
|
|
442
|
+
const list = this.listeners[event];
|
|
443
|
+
const idx = list.indexOf(callback);
|
|
444
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
445
|
+
}
|
|
446
|
+
// -------------------------------------------------------------------------
|
|
447
|
+
// Private
|
|
448
|
+
// -------------------------------------------------------------------------
|
|
449
|
+
emit(event, data) {
|
|
450
|
+
const list = this.listeners[event];
|
|
451
|
+
for (const cb of list) {
|
|
452
|
+
try {
|
|
453
|
+
cb(data);
|
|
454
|
+
} catch (e) {
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
send(msg) {
|
|
459
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return false;
|
|
460
|
+
try {
|
|
461
|
+
this.ws.send(JSON.stringify(msg));
|
|
462
|
+
return true;
|
|
463
|
+
} catch {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
setStatus(next) {
|
|
468
|
+
if (this.status !== next) {
|
|
469
|
+
this.status = next;
|
|
470
|
+
this.emit("status", next);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
handleMessage(raw) {
|
|
474
|
+
let msg;
|
|
475
|
+
try {
|
|
476
|
+
msg = JSON.parse(raw);
|
|
477
|
+
} catch {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (msg.type) {
|
|
481
|
+
switch (msg.type) {
|
|
482
|
+
case "pong":
|
|
483
|
+
this.clearPongTimer();
|
|
484
|
+
return;
|
|
485
|
+
case "subscribed":
|
|
486
|
+
this.emit("subscribed", {
|
|
487
|
+
channels: msg.channels,
|
|
488
|
+
market_slugs: msg.market_slugs
|
|
489
|
+
});
|
|
490
|
+
return;
|
|
491
|
+
case "error":
|
|
492
|
+
this.emit("error", {
|
|
493
|
+
code: msg.code,
|
|
494
|
+
message: msg.message
|
|
495
|
+
});
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (msg.channel && msg.data) {
|
|
500
|
+
const dataMsg = msg;
|
|
501
|
+
switch (dataMsg.channel) {
|
|
502
|
+
case "orderbook":
|
|
503
|
+
this.emit("orderbook", dataMsg);
|
|
504
|
+
break;
|
|
505
|
+
case "prices":
|
|
506
|
+
this.emit("prices", dataMsg);
|
|
507
|
+
break;
|
|
508
|
+
case "trades":
|
|
509
|
+
this.emit("trades", dataMsg);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
scheduleReconnect() {
|
|
515
|
+
if (this.reconnectTimer) return;
|
|
516
|
+
const delay = Math.min(
|
|
517
|
+
this.reconnectBase * Math.pow(2, this.reconnectAttempts),
|
|
518
|
+
this.reconnectMax
|
|
519
|
+
);
|
|
520
|
+
this.setStatus("reconnecting");
|
|
521
|
+
this.emit("reconnecting", { attempt: this.reconnectAttempts + 1, delay });
|
|
522
|
+
this.reconnectTimer = setTimeout(() => {
|
|
523
|
+
this.reconnectTimer = null;
|
|
524
|
+
this.reconnectAttempts++;
|
|
525
|
+
this.connect();
|
|
526
|
+
}, delay);
|
|
527
|
+
}
|
|
528
|
+
clearReconnectTimer() {
|
|
529
|
+
if (this.reconnectTimer) {
|
|
530
|
+
clearTimeout(this.reconnectTimer);
|
|
531
|
+
this.reconnectTimer = null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
restoreSubscriptions() {
|
|
535
|
+
for (const [channel, slugs] of this.subs.channels.entries()) {
|
|
536
|
+
if (slugs.size > 0) {
|
|
537
|
+
this.send({
|
|
538
|
+
type: "subscribe",
|
|
539
|
+
channels: [channel],
|
|
540
|
+
market_slugs: Array.from(slugs)
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
startPing() {
|
|
546
|
+
this.stopPing();
|
|
547
|
+
this.pingTimer = setInterval(() => {
|
|
548
|
+
this.send({ type: "ping" });
|
|
549
|
+
this.armPongTimeout();
|
|
550
|
+
}, this.pingMs);
|
|
551
|
+
}
|
|
552
|
+
stopPing() {
|
|
553
|
+
if (this.pingTimer) {
|
|
554
|
+
clearInterval(this.pingTimer);
|
|
555
|
+
this.pingTimer = null;
|
|
556
|
+
}
|
|
557
|
+
this.clearPongTimer();
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Start a timer that fires if the server does not reply with pong
|
|
561
|
+
* within `pongTimeoutMs`. On timeout the socket is closed so
|
|
562
|
+
* the normal reconnect flow kicks in.
|
|
563
|
+
*/
|
|
564
|
+
armPongTimeout() {
|
|
565
|
+
this.clearPongTimer();
|
|
566
|
+
this.pongTimer = setTimeout(() => {
|
|
567
|
+
this.pongTimer = null;
|
|
568
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
569
|
+
this.ws.close(4e3, "pong timeout");
|
|
570
|
+
}
|
|
571
|
+
}, this.pongTimeoutMs);
|
|
572
|
+
}
|
|
573
|
+
clearPongTimer() {
|
|
574
|
+
if (this.pongTimer) {
|
|
575
|
+
clearTimeout(this.pongTimer);
|
|
576
|
+
this.pongTimer = null;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
function createPredictWsClient(config) {
|
|
581
|
+
return new PredictWsClient(config);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/utils/polymarket-hmac.ts
|
|
585
|
+
function encode(str) {
|
|
586
|
+
return new TextEncoder().encode(str);
|
|
587
|
+
}
|
|
588
|
+
function base64ToBytes(b64) {
|
|
589
|
+
const binary = atob(b64);
|
|
590
|
+
const bytes = new Uint8Array(binary.length);
|
|
591
|
+
for (let i = 0; i < binary.length; i++) {
|
|
592
|
+
bytes[i] = binary.charCodeAt(i);
|
|
593
|
+
}
|
|
594
|
+
return bytes;
|
|
595
|
+
}
|
|
596
|
+
function bytesToBase64(bytes) {
|
|
597
|
+
let binary = "";
|
|
598
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
599
|
+
binary += String.fromCharCode(bytes[i]);
|
|
600
|
+
}
|
|
601
|
+
return btoa(binary);
|
|
602
|
+
}
|
|
603
|
+
async function hmacSha256Base64(secretBase64, message) {
|
|
604
|
+
const keyBytes = base64ToBytes(secretBase64);
|
|
605
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
606
|
+
"raw",
|
|
607
|
+
keyBytes.buffer,
|
|
608
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
609
|
+
false,
|
|
610
|
+
["sign"]
|
|
611
|
+
);
|
|
612
|
+
const msgBytes = encode(message);
|
|
613
|
+
const signature = await crypto.subtle.sign(
|
|
614
|
+
"HMAC",
|
|
615
|
+
cryptoKey,
|
|
616
|
+
msgBytes.buffer
|
|
617
|
+
);
|
|
618
|
+
return bytesToBase64(new Uint8Array(signature));
|
|
619
|
+
}
|
|
620
|
+
async function buildPolymarketL2Headers(address, input) {
|
|
621
|
+
const timestamp = Math.floor(Date.now() / 1e3).toString();
|
|
622
|
+
const body = input.body ?? "";
|
|
623
|
+
const message = `${timestamp}${input.method}${input.requestPath}${body}`;
|
|
624
|
+
const signature = await hmacSha256Base64(input.secret, message);
|
|
625
|
+
return {
|
|
626
|
+
POLY_ADDRESS: address,
|
|
627
|
+
POLY_SIGNATURE: signature,
|
|
628
|
+
POLY_TIMESTAMP: timestamp,
|
|
629
|
+
POLY_API_KEY: input.apiKey,
|
|
630
|
+
POLY_PASSPHRASE: input.passphrase
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
var CLOB_AUTH_DOMAIN = {
|
|
634
|
+
name: "ClobAuthDomain",
|
|
635
|
+
version: "1",
|
|
636
|
+
chainId: 137
|
|
637
|
+
};
|
|
638
|
+
var CLOB_AUTH_TYPES = {
|
|
639
|
+
ClobAuth: [
|
|
640
|
+
{ name: "address", type: "address" },
|
|
641
|
+
{ name: "timestamp", type: "string" },
|
|
642
|
+
{ name: "nonce", type: "uint256" },
|
|
643
|
+
{ name: "message", type: "string" }
|
|
644
|
+
]
|
|
645
|
+
};
|
|
646
|
+
function buildClobAuthMessage(input) {
|
|
647
|
+
return {
|
|
648
|
+
address: input.address,
|
|
649
|
+
timestamp: input.timestamp,
|
|
650
|
+
nonce: input.nonce,
|
|
651
|
+
message: "This message attests that I control the given wallet"
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async function derivePolymarketApiKey(address, signature, timestamp, nonce) {
|
|
655
|
+
const res = await fetch(
|
|
656
|
+
`https://clob.polymarket.com/auth/derive-api-key?nonce=${nonce}`,
|
|
657
|
+
{
|
|
658
|
+
method: "GET",
|
|
659
|
+
headers: {
|
|
660
|
+
"Content-Type": "application/json",
|
|
661
|
+
POLY_ADDRESS: address,
|
|
662
|
+
POLY_SIGNATURE: signature,
|
|
663
|
+
POLY_TIMESTAMP: timestamp,
|
|
664
|
+
POLY_NONCE: String(nonce)
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
if (!res.ok) {
|
|
669
|
+
const text = await res.text().catch(() => res.statusText);
|
|
670
|
+
throw new Error(
|
|
671
|
+
`Polymarket credential exchange failed (${res.status}): ${text}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
return res.json();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/utils/polymarket-order.ts
|
|
678
|
+
var CTF_EXCHANGE_ADDRESS = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E";
|
|
679
|
+
var NEG_RISK_CTF_EXCHANGE_ADDRESS = "0xC5d563A36AE78145C45a50134d48A1215220f80a";
|
|
680
|
+
var USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
|
|
681
|
+
var POLYGON_CHAIN_ID = 137;
|
|
682
|
+
function buildCtfExchangeDomain(negRisk = false) {
|
|
683
|
+
return {
|
|
684
|
+
name: "CTFExchange",
|
|
685
|
+
version: "1",
|
|
686
|
+
chainId: POLYGON_CHAIN_ID,
|
|
687
|
+
verifyingContract: negRisk ? NEG_RISK_CTF_EXCHANGE_ADDRESS : CTF_EXCHANGE_ADDRESS
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
var CTF_ORDER_TYPES = {
|
|
691
|
+
Order: [
|
|
692
|
+
{ name: "salt", type: "uint256" },
|
|
693
|
+
{ name: "maker", type: "address" },
|
|
694
|
+
{ name: "signer", type: "address" },
|
|
695
|
+
{ name: "taker", type: "address" },
|
|
696
|
+
{ name: "tokenId", type: "uint256" },
|
|
697
|
+
{ name: "makerAmount", type: "uint256" },
|
|
698
|
+
{ name: "takerAmount", type: "uint256" },
|
|
699
|
+
{ name: "expiration", type: "uint256" },
|
|
700
|
+
{ name: "nonce", type: "uint256" },
|
|
701
|
+
{ name: "feeRateBps", type: "uint256" },
|
|
702
|
+
{ name: "side", type: "uint8" },
|
|
703
|
+
{ name: "signatureType", type: "uint8" }
|
|
704
|
+
]
|
|
705
|
+
};
|
|
706
|
+
var ORDER_TYPE = {
|
|
707
|
+
GTC: 0,
|
|
708
|
+
// Good-Till-Cancelled
|
|
709
|
+
FOK: 1,
|
|
710
|
+
// Fill-Or-Kill
|
|
711
|
+
GTD: 2,
|
|
712
|
+
// Good-Till-Date
|
|
713
|
+
FAK: 3
|
|
714
|
+
// Fill-And-Kill
|
|
715
|
+
};
|
|
716
|
+
var SIDE = { BUY: 0, SELL: 1 };
|
|
717
|
+
var TICK_DECIMALS = {
|
|
718
|
+
"0.1": 1,
|
|
719
|
+
"0.01": 2,
|
|
720
|
+
"0.001": 3,
|
|
721
|
+
"0.0001": 4
|
|
722
|
+
};
|
|
723
|
+
function roundToTick(price, tickSize) {
|
|
724
|
+
const decimals = TICK_DECIMALS[tickSize] ?? 2;
|
|
725
|
+
return price.toFixed(decimals);
|
|
726
|
+
}
|
|
727
|
+
function toMicroUsdc(amount) {
|
|
728
|
+
return BigInt(Math.round(amount * 1e6));
|
|
729
|
+
}
|
|
730
|
+
function buildOrderMessage(input) {
|
|
731
|
+
const side = input.side === "BUY" ? SIDE.BUY : SIDE.SELL;
|
|
732
|
+
const priceStr = roundToTick(input.price, input.tickSize);
|
|
733
|
+
const priceNum = parseFloat(priceStr);
|
|
734
|
+
const sizeShares = toMicroUsdc(input.size);
|
|
735
|
+
const sizeUsdc = toMicroUsdc(input.size * priceNum);
|
|
736
|
+
const makerAmount = side === SIDE.BUY ? sizeUsdc.toString() : sizeShares.toString();
|
|
737
|
+
const takerAmount = side === SIDE.BUY ? sizeShares.toString() : sizeUsdc.toString();
|
|
738
|
+
const maker = input.funderAddress ?? input.signerAddress;
|
|
739
|
+
return {
|
|
740
|
+
salt: Math.floor(Math.random() * 1e15).toString(),
|
|
741
|
+
maker,
|
|
742
|
+
signer: input.signerAddress,
|
|
743
|
+
taker: "0x0000000000000000000000000000000000000000",
|
|
744
|
+
tokenId: input.tokenId,
|
|
745
|
+
makerAmount,
|
|
746
|
+
takerAmount,
|
|
747
|
+
expiration: String(input.expiration ?? 0),
|
|
748
|
+
nonce: "0",
|
|
749
|
+
feeRateBps: "0",
|
|
750
|
+
side,
|
|
751
|
+
signatureType: input.signatureType
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function buildSignedOrder(input) {
|
|
755
|
+
const message = buildOrderMessage(input);
|
|
756
|
+
return {
|
|
757
|
+
...message,
|
|
758
|
+
signature: input.signature,
|
|
759
|
+
orderType: input.orderType ?? "GTC"
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export { CLOB_AUTH_DOMAIN, CLOB_AUTH_TYPES, CTF_EXCHANGE_ADDRESS, CTF_ORDER_TYPES, DEFAULT_PAGE_SIZE, NEG_RISK_CTF_EXCHANGE_ADDRESS, ORDER_TYPE, POLYGON_CHAIN_ID, PredictClient, PredictWsClient, SIDE, USDC_ADDRESS, buildClobAuthMessage, buildCtfExchangeDomain, buildOrderMessage, buildPolymarketL2Headers, buildSignedOrder, createPredictClient, createPredictWsClient, derivePolymarketApiKey, eventQueryKey, fetchEvent, fetchEventsPage, fetchMarket, hmacSha256Base64, infiniteEventsQueryKey, marketQueryKey, resolveEventsParams, resolveTagSlug };
|
|
764
|
+
//# sourceMappingURL=server.mjs.map
|
|
765
|
+
//# sourceMappingURL=server.mjs.map
|