@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/index.mjs
ADDED
|
@@ -0,0 +1,1460 @@
|
|
|
1
|
+
import { httpGet, httpDelete, httpPost } from '@liberfi.io/utils';
|
|
2
|
+
import { createContext, useMemo, useState, useRef, useCallback, useContext, useEffect } from 'react';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
import { useQuery, useInfiniteQuery, useQueries, useQueryClient, useMutation } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
// src/client/client.ts
|
|
7
|
+
function buildQuery(params) {
|
|
8
|
+
const qs = new URLSearchParams();
|
|
9
|
+
for (const [key, value] of Object.entries(params)) {
|
|
10
|
+
if (value !== void 0 && value !== null) {
|
|
11
|
+
qs.set(key, String(value));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const str = qs.toString();
|
|
15
|
+
return str ? `?${str}` : "";
|
|
16
|
+
}
|
|
17
|
+
var PredictClient = class {
|
|
18
|
+
constructor(endpoint) {
|
|
19
|
+
this.endpoint = endpoint;
|
|
20
|
+
}
|
|
21
|
+
// -------------------------------------------------------------------------
|
|
22
|
+
// Events
|
|
23
|
+
// -------------------------------------------------------------------------
|
|
24
|
+
/**
|
|
25
|
+
* List prediction events with optional filtering, sorting, and pagination.
|
|
26
|
+
*
|
|
27
|
+
* Maps to `GET /api/v1/events`.
|
|
28
|
+
*
|
|
29
|
+
* @param params - Optional query parameters (filter, sort, pagination).
|
|
30
|
+
* @returns A paginated page of events.
|
|
31
|
+
*/
|
|
32
|
+
async listEvents(params) {
|
|
33
|
+
const query = buildQuery(params ?? {});
|
|
34
|
+
const url = `${this.endpoint}/api/v1/events${query}`;
|
|
35
|
+
return await httpGet(url);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Fetch a single prediction event by its slug.
|
|
39
|
+
*
|
|
40
|
+
* Maps to `GET /api/v1/events/:slug?source=...`.
|
|
41
|
+
*
|
|
42
|
+
* @param slug - Canonical event slug (e.g. "will-trump-win-2024" for Polymarket
|
|
43
|
+
* or "KXBTCD-25FEB-T68000" for DFlow).
|
|
44
|
+
* @param source - Upstream provider (`"dflow"` or `"polymarket"`).
|
|
45
|
+
* @returns The matching event.
|
|
46
|
+
* @throws When the server responds with 404 or any other non-2xx status.
|
|
47
|
+
*/
|
|
48
|
+
async getEvent(slug, source) {
|
|
49
|
+
const query = source ? buildQuery({ source }) : "";
|
|
50
|
+
const url = `${this.endpoint}/api/v1/events/${encodeURIComponent(slug)}${query}`;
|
|
51
|
+
return await httpGet(url);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fetch events similar to the given slug.
|
|
55
|
+
*
|
|
56
|
+
* Maps to `GET /api/v1/events/:slug/similar?source=...`.
|
|
57
|
+
*/
|
|
58
|
+
async getSimilarEvents(slug, source, params) {
|
|
59
|
+
const query = buildQuery({ source, ...params });
|
|
60
|
+
const url = `${this.endpoint}/api/v1/events/${encodeURIComponent(slug)}/similar${query}`;
|
|
61
|
+
return await httpGet(url);
|
|
62
|
+
}
|
|
63
|
+
// -------------------------------------------------------------------------
|
|
64
|
+
// Markets
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Fetch a single prediction market by its slug.
|
|
68
|
+
*
|
|
69
|
+
* Maps to `GET /api/v1/markets/:slug?source=...`.
|
|
70
|
+
*
|
|
71
|
+
* @param slug - Canonical market slug.
|
|
72
|
+
* @param source - Upstream provider (`"dflow"` or `"polymarket"`).
|
|
73
|
+
* @returns The matching market.
|
|
74
|
+
* @throws When the server responds with 404 or any other non-2xx status.
|
|
75
|
+
*/
|
|
76
|
+
async getMarket(slug, source) {
|
|
77
|
+
const query = source ? buildQuery({ source }) : "";
|
|
78
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}${query}`;
|
|
79
|
+
return await httpGet(url);
|
|
80
|
+
}
|
|
81
|
+
/** Maps to `GET /api/v1/markets/:slug/orderbook?source=...`. */
|
|
82
|
+
async getOrderbook(slug, source) {
|
|
83
|
+
const query = buildQuery({ source });
|
|
84
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/orderbook${query}`;
|
|
85
|
+
return await httpGet(url);
|
|
86
|
+
}
|
|
87
|
+
/** Maps to `GET /api/v1/markets/:slug/trades?source=...`. */
|
|
88
|
+
async listMarketTrades(slug, params) {
|
|
89
|
+
const query = buildQuery(params);
|
|
90
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/trades${query}`;
|
|
91
|
+
return await httpGet(url);
|
|
92
|
+
}
|
|
93
|
+
/** Maps to `GET /api/v1/markets/:slug/price-history?source=...&range=...`. */
|
|
94
|
+
async getPriceHistory(slug, source, range) {
|
|
95
|
+
const query = buildQuery({ source, range });
|
|
96
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/price-history${query}`;
|
|
97
|
+
return await httpGet(url);
|
|
98
|
+
}
|
|
99
|
+
/** Maps to `GET /api/v1/markets/:slug/candlesticks?interval=...&limit=...`. */
|
|
100
|
+
async listCandlesticks(slug, params) {
|
|
101
|
+
const query = buildQuery(params ?? {});
|
|
102
|
+
const url = `${this.endpoint}/api/v1/markets/${encodeURIComponent(slug)}/candlesticks${query}`;
|
|
103
|
+
return await httpGet(url);
|
|
104
|
+
}
|
|
105
|
+
// -------------------------------------------------------------------------
|
|
106
|
+
// Positions
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
/**
|
|
109
|
+
* Maps to `GET /api/v1/positions?source=...&user=...`.
|
|
110
|
+
*
|
|
111
|
+
* @param user - Wallet address.
|
|
112
|
+
* @param source - Provider source. Omit to aggregate all providers.
|
|
113
|
+
*/
|
|
114
|
+
async getPositions(user, source) {
|
|
115
|
+
const query = buildQuery({ source, user });
|
|
116
|
+
const url = `${this.endpoint}/api/v1/positions${query}`;
|
|
117
|
+
return await httpGet(url);
|
|
118
|
+
}
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
// Balance
|
|
121
|
+
// -------------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Get the on-chain USDC balance for a wallet.
|
|
124
|
+
*
|
|
125
|
+
* Maps to `GET /api/v1/balance?source=...&user=...`.
|
|
126
|
+
*
|
|
127
|
+
* @param source - Provider source (`"dflow"` for Solana, `"polymarket"` for Polygon).
|
|
128
|
+
* @param user - Wallet address.
|
|
129
|
+
*/
|
|
130
|
+
async getBalance(source, user) {
|
|
131
|
+
const query = buildQuery({ source, user });
|
|
132
|
+
const url = `${this.endpoint}/api/v1/balance${query}`;
|
|
133
|
+
return await httpGet(url);
|
|
134
|
+
}
|
|
135
|
+
// -------------------------------------------------------------------------
|
|
136
|
+
// Orders
|
|
137
|
+
// -------------------------------------------------------------------------
|
|
138
|
+
/** Maps to `GET /api/v1/orders?source=...&wallet_address=...`. */
|
|
139
|
+
async listOrders(params) {
|
|
140
|
+
const query = buildQuery(params);
|
|
141
|
+
const url = `${this.endpoint}/api/v1/orders${query}`;
|
|
142
|
+
return await httpGet(url);
|
|
143
|
+
}
|
|
144
|
+
/** Maps to `GET /api/v1/orders/:id?source=...`. */
|
|
145
|
+
async getOrder(id, source) {
|
|
146
|
+
const query = buildQuery({ source });
|
|
147
|
+
const url = `${this.endpoint}/api/v1/orders/${encodeURIComponent(id)}${query}`;
|
|
148
|
+
return await httpGet(url);
|
|
149
|
+
}
|
|
150
|
+
/** Maps to `DELETE /api/v1/orders/:id?source=...`. */
|
|
151
|
+
async cancelOrder(id, source) {
|
|
152
|
+
const query = buildQuery({ source });
|
|
153
|
+
const url = `${this.endpoint}/api/v1/orders/${encodeURIComponent(id)}${query}`;
|
|
154
|
+
return await httpDelete(url);
|
|
155
|
+
}
|
|
156
|
+
// -------------------------------------------------------------------------
|
|
157
|
+
// Polymarket trading
|
|
158
|
+
// -------------------------------------------------------------------------
|
|
159
|
+
/**
|
|
160
|
+
* Create a Polymarket limit order via the prediction-server proxy.
|
|
161
|
+
*
|
|
162
|
+
* Maps to `POST /api/v1/orders/polymarket`.
|
|
163
|
+
*
|
|
164
|
+
* The caller must attach Polymarket CLOB authentication headers:
|
|
165
|
+
* - `POLY_ADDRESS` — the EVM wallet address.
|
|
166
|
+
* - `POLY_SIGNATURE` — L1 signature or L2 API key.
|
|
167
|
+
* - `POLY_TIMESTAMP` — Unix milliseconds string.
|
|
168
|
+
* - `POLY_NONCE` — credential nonce (use `"0"` for in-session derived creds).
|
|
169
|
+
*
|
|
170
|
+
* @param input - Order parameters.
|
|
171
|
+
* @param headers - Polymarket CLOB auth headers (`POLY_*`).
|
|
172
|
+
*/
|
|
173
|
+
async createPolymarketOrder(input, headers) {
|
|
174
|
+
const url = `${this.endpoint}/api/v1/orders/polymarket`;
|
|
175
|
+
return await httpPost(url, input, { headers });
|
|
176
|
+
}
|
|
177
|
+
// -------------------------------------------------------------------------
|
|
178
|
+
// DFlow trading
|
|
179
|
+
// -------------------------------------------------------------------------
|
|
180
|
+
/** Maps to `POST /api/v1/orders/dflow/quote`. */
|
|
181
|
+
async createDFlowQuote(body) {
|
|
182
|
+
const url = `${this.endpoint}/api/v1/orders/dflow/quote`;
|
|
183
|
+
return await httpPost(url, body);
|
|
184
|
+
}
|
|
185
|
+
/** Maps to `POST /api/v1/orders/dflow/submit`. */
|
|
186
|
+
async submitDFlowTransaction(body) {
|
|
187
|
+
const url = `${this.endpoint}/api/v1/orders/dflow/submit`;
|
|
188
|
+
return await httpPost(url, body);
|
|
189
|
+
}
|
|
190
|
+
// -------------------------------------------------------------------------
|
|
191
|
+
// Trades by wallet
|
|
192
|
+
// -------------------------------------------------------------------------
|
|
193
|
+
/** Maps to `GET /api/v1/trades?source=...&wallet=...`. */
|
|
194
|
+
async listTrades(params) {
|
|
195
|
+
const query = buildQuery(params);
|
|
196
|
+
const url = `${this.endpoint}/api/v1/trades${query}`;
|
|
197
|
+
return await httpGet(url);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
function createPredictClient(endpoint) {
|
|
201
|
+
return new PredictClient(endpoint);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/client/ws.ts
|
|
205
|
+
var DEFAULT_RECONNECT_BASE = 1e3;
|
|
206
|
+
var DEFAULT_RECONNECT_MAX = 3e4;
|
|
207
|
+
var DEFAULT_PING_INTERVAL = 2e4;
|
|
208
|
+
var DEFAULT_PONG_TIMEOUT = 1e4;
|
|
209
|
+
var PredictWsClient = class {
|
|
210
|
+
ws = null;
|
|
211
|
+
wsUrl;
|
|
212
|
+
autoReconnect;
|
|
213
|
+
reconnectBase;
|
|
214
|
+
reconnectMax;
|
|
215
|
+
pingMs;
|
|
216
|
+
pongTimeoutMs;
|
|
217
|
+
status = "disconnected";
|
|
218
|
+
reconnectAttempts = 0;
|
|
219
|
+
reconnectTimer = null;
|
|
220
|
+
pingTimer = null;
|
|
221
|
+
pongTimer = null;
|
|
222
|
+
shouldReconnect = true;
|
|
223
|
+
listeners = {
|
|
224
|
+
connect: [],
|
|
225
|
+
disconnect: [],
|
|
226
|
+
reconnecting: [],
|
|
227
|
+
error: [],
|
|
228
|
+
status: [],
|
|
229
|
+
subscribed: [],
|
|
230
|
+
orderbook: [],
|
|
231
|
+
prices: [],
|
|
232
|
+
trades: []
|
|
233
|
+
};
|
|
234
|
+
subs = {
|
|
235
|
+
channels: /* @__PURE__ */ new Map([
|
|
236
|
+
["orderbook", /* @__PURE__ */ new Set()],
|
|
237
|
+
["prices", /* @__PURE__ */ new Set()],
|
|
238
|
+
["trades", /* @__PURE__ */ new Set()]
|
|
239
|
+
])
|
|
240
|
+
};
|
|
241
|
+
constructor(config) {
|
|
242
|
+
this.wsUrl = config.wsUrl;
|
|
243
|
+
this.autoReconnect = config.autoReconnect ?? true;
|
|
244
|
+
this.reconnectBase = config.reconnectIntervalBase ?? DEFAULT_RECONNECT_BASE;
|
|
245
|
+
this.reconnectMax = config.reconnectMaxInterval ?? DEFAULT_RECONNECT_MAX;
|
|
246
|
+
this.pingMs = config.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
247
|
+
this.pongTimeoutMs = config.pongTimeout ?? DEFAULT_PONG_TIMEOUT;
|
|
248
|
+
if (config.autoConnect !== false) {
|
|
249
|
+
this.connect();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// -------------------------------------------------------------------------
|
|
253
|
+
// Connection management
|
|
254
|
+
// -------------------------------------------------------------------------
|
|
255
|
+
connect() {
|
|
256
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
this.shouldReconnect = true;
|
|
260
|
+
this.setStatus("connecting");
|
|
261
|
+
try {
|
|
262
|
+
this.ws = new WebSocket(this.wsUrl);
|
|
263
|
+
this.ws.onopen = () => {
|
|
264
|
+
this.reconnectAttempts = 0;
|
|
265
|
+
this.setStatus("connected");
|
|
266
|
+
this.emit("connect", void 0);
|
|
267
|
+
this.restoreSubscriptions();
|
|
268
|
+
this.startPing();
|
|
269
|
+
};
|
|
270
|
+
this.ws.onmessage = (event) => {
|
|
271
|
+
this.handleMessage(event.data);
|
|
272
|
+
};
|
|
273
|
+
this.ws.onerror = () => {
|
|
274
|
+
};
|
|
275
|
+
this.ws.onclose = (event) => {
|
|
276
|
+
this.stopPing();
|
|
277
|
+
this.setStatus("disconnected");
|
|
278
|
+
this.emit("disconnect", { code: event.code, reason: event.reason });
|
|
279
|
+
if (this.shouldReconnect && this.autoReconnect) {
|
|
280
|
+
this.scheduleReconnect();
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
} catch {
|
|
284
|
+
if (this.shouldReconnect && this.autoReconnect) {
|
|
285
|
+
this.scheduleReconnect();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
disconnect() {
|
|
290
|
+
this.shouldReconnect = false;
|
|
291
|
+
this.stopPing();
|
|
292
|
+
this.clearReconnectTimer();
|
|
293
|
+
if (this.ws) {
|
|
294
|
+
this.ws.close();
|
|
295
|
+
this.ws = null;
|
|
296
|
+
}
|
|
297
|
+
this.setStatus("disconnected");
|
|
298
|
+
}
|
|
299
|
+
/** Disconnect and clear all subscription state and listeners. */
|
|
300
|
+
destroy() {
|
|
301
|
+
this.disconnect();
|
|
302
|
+
for (const ch of this.subs.channels.values()) ch.clear();
|
|
303
|
+
for (const key of Object.keys(this.listeners)) {
|
|
304
|
+
this.listeners[key].length = 0;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
getStatus() {
|
|
308
|
+
return this.status;
|
|
309
|
+
}
|
|
310
|
+
isConnected() {
|
|
311
|
+
return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
312
|
+
}
|
|
313
|
+
// -------------------------------------------------------------------------
|
|
314
|
+
// Subscription — low-level (multi-channel, multi-slug)
|
|
315
|
+
// -------------------------------------------------------------------------
|
|
316
|
+
/**
|
|
317
|
+
* Subscribe to one or more channels for the given market slugs.
|
|
318
|
+
* The server acknowledges with a `subscribed` message.
|
|
319
|
+
*/
|
|
320
|
+
subscribe(channels, marketSlugs) {
|
|
321
|
+
for (const ch of channels) {
|
|
322
|
+
const set = this.subs.channels.get(ch);
|
|
323
|
+
for (const slug of marketSlugs) set.add(slug);
|
|
324
|
+
}
|
|
325
|
+
this.send({ type: "subscribe", channels, market_slugs: marketSlugs });
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Unsubscribe from one or more channels for the given market slugs.
|
|
329
|
+
*/
|
|
330
|
+
unsubscribe(channels, marketSlugs) {
|
|
331
|
+
for (const ch of channels) {
|
|
332
|
+
const set = this.subs.channels.get(ch);
|
|
333
|
+
for (const slug of marketSlugs) set.delete(slug);
|
|
334
|
+
}
|
|
335
|
+
this.send({ type: "unsubscribe", channels, market_slugs: marketSlugs });
|
|
336
|
+
}
|
|
337
|
+
// -------------------------------------------------------------------------
|
|
338
|
+
// Subscription — convenience (single channel)
|
|
339
|
+
// -------------------------------------------------------------------------
|
|
340
|
+
/**
|
|
341
|
+
* Subscribe to price updates for the given slugs.
|
|
342
|
+
* Returns an unsubscribe function.
|
|
343
|
+
*/
|
|
344
|
+
subscribePrices(slugs, onUpdate) {
|
|
345
|
+
this.subscribe(["prices"], slugs);
|
|
346
|
+
const off = this.on("prices", onUpdate);
|
|
347
|
+
return () => {
|
|
348
|
+
off();
|
|
349
|
+
this.unsubscribe(["prices"], slugs);
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Subscribe to orderbook snapshots for the given slugs.
|
|
354
|
+
* Returns an unsubscribe function.
|
|
355
|
+
*/
|
|
356
|
+
subscribeOrderbook(slugs, onUpdate) {
|
|
357
|
+
this.subscribe(["orderbook"], slugs);
|
|
358
|
+
const off = this.on("orderbook", onUpdate);
|
|
359
|
+
return () => {
|
|
360
|
+
off();
|
|
361
|
+
this.unsubscribe(["orderbook"], slugs);
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Subscribe to trade events for the given slugs.
|
|
366
|
+
* Returns an unsubscribe function.
|
|
367
|
+
*/
|
|
368
|
+
subscribeTrades(slugs, onUpdate) {
|
|
369
|
+
this.subscribe(["trades"], slugs);
|
|
370
|
+
const off = this.on("trades", onUpdate);
|
|
371
|
+
return () => {
|
|
372
|
+
off();
|
|
373
|
+
this.unsubscribe(["trades"], slugs);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// -------------------------------------------------------------------------
|
|
377
|
+
// Status observation
|
|
378
|
+
// -------------------------------------------------------------------------
|
|
379
|
+
/** Register a callback for connection status changes. Returns unsubscribe. */
|
|
380
|
+
onStatusChange(cb) {
|
|
381
|
+
return this.on("status", cb);
|
|
382
|
+
}
|
|
383
|
+
/** Register a callback for server-sent errors. Returns unsubscribe. */
|
|
384
|
+
onError(cb) {
|
|
385
|
+
return this.on("error", cb);
|
|
386
|
+
}
|
|
387
|
+
// -------------------------------------------------------------------------
|
|
388
|
+
// Event emitter
|
|
389
|
+
// -------------------------------------------------------------------------
|
|
390
|
+
on(event, callback) {
|
|
391
|
+
const list = this.listeners[event];
|
|
392
|
+
list.push(callback);
|
|
393
|
+
return () => this.off(event, callback);
|
|
394
|
+
}
|
|
395
|
+
off(event, callback) {
|
|
396
|
+
const list = this.listeners[event];
|
|
397
|
+
const idx = list.indexOf(callback);
|
|
398
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
399
|
+
}
|
|
400
|
+
// -------------------------------------------------------------------------
|
|
401
|
+
// Private
|
|
402
|
+
// -------------------------------------------------------------------------
|
|
403
|
+
emit(event, data) {
|
|
404
|
+
const list = this.listeners[event];
|
|
405
|
+
for (const cb of list) {
|
|
406
|
+
try {
|
|
407
|
+
cb(data);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
send(msg) {
|
|
413
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return false;
|
|
414
|
+
try {
|
|
415
|
+
this.ws.send(JSON.stringify(msg));
|
|
416
|
+
return true;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
setStatus(next) {
|
|
422
|
+
if (this.status !== next) {
|
|
423
|
+
this.status = next;
|
|
424
|
+
this.emit("status", next);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
handleMessage(raw) {
|
|
428
|
+
let msg;
|
|
429
|
+
try {
|
|
430
|
+
msg = JSON.parse(raw);
|
|
431
|
+
} catch {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (msg.type) {
|
|
435
|
+
switch (msg.type) {
|
|
436
|
+
case "pong":
|
|
437
|
+
this.clearPongTimer();
|
|
438
|
+
return;
|
|
439
|
+
case "subscribed":
|
|
440
|
+
this.emit("subscribed", {
|
|
441
|
+
channels: msg.channels,
|
|
442
|
+
market_slugs: msg.market_slugs
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
case "error":
|
|
446
|
+
this.emit("error", {
|
|
447
|
+
code: msg.code,
|
|
448
|
+
message: msg.message
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (msg.channel && msg.data) {
|
|
454
|
+
const dataMsg = msg;
|
|
455
|
+
switch (dataMsg.channel) {
|
|
456
|
+
case "orderbook":
|
|
457
|
+
this.emit("orderbook", dataMsg);
|
|
458
|
+
break;
|
|
459
|
+
case "prices":
|
|
460
|
+
this.emit("prices", dataMsg);
|
|
461
|
+
break;
|
|
462
|
+
case "trades":
|
|
463
|
+
this.emit("trades", dataMsg);
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
scheduleReconnect() {
|
|
469
|
+
if (this.reconnectTimer) return;
|
|
470
|
+
const delay = Math.min(
|
|
471
|
+
this.reconnectBase * Math.pow(2, this.reconnectAttempts),
|
|
472
|
+
this.reconnectMax
|
|
473
|
+
);
|
|
474
|
+
this.setStatus("reconnecting");
|
|
475
|
+
this.emit("reconnecting", { attempt: this.reconnectAttempts + 1, delay });
|
|
476
|
+
this.reconnectTimer = setTimeout(() => {
|
|
477
|
+
this.reconnectTimer = null;
|
|
478
|
+
this.reconnectAttempts++;
|
|
479
|
+
this.connect();
|
|
480
|
+
}, delay);
|
|
481
|
+
}
|
|
482
|
+
clearReconnectTimer() {
|
|
483
|
+
if (this.reconnectTimer) {
|
|
484
|
+
clearTimeout(this.reconnectTimer);
|
|
485
|
+
this.reconnectTimer = null;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
restoreSubscriptions() {
|
|
489
|
+
for (const [channel, slugs] of this.subs.channels.entries()) {
|
|
490
|
+
if (slugs.size > 0) {
|
|
491
|
+
this.send({
|
|
492
|
+
type: "subscribe",
|
|
493
|
+
channels: [channel],
|
|
494
|
+
market_slugs: Array.from(slugs)
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
startPing() {
|
|
500
|
+
this.stopPing();
|
|
501
|
+
this.pingTimer = setInterval(() => {
|
|
502
|
+
this.send({ type: "ping" });
|
|
503
|
+
this.armPongTimeout();
|
|
504
|
+
}, this.pingMs);
|
|
505
|
+
}
|
|
506
|
+
stopPing() {
|
|
507
|
+
if (this.pingTimer) {
|
|
508
|
+
clearInterval(this.pingTimer);
|
|
509
|
+
this.pingTimer = null;
|
|
510
|
+
}
|
|
511
|
+
this.clearPongTimer();
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Start a timer that fires if the server does not reply with pong
|
|
515
|
+
* within `pongTimeoutMs`. On timeout the socket is closed so
|
|
516
|
+
* the normal reconnect flow kicks in.
|
|
517
|
+
*/
|
|
518
|
+
armPongTimeout() {
|
|
519
|
+
this.clearPongTimer();
|
|
520
|
+
this.pongTimer = setTimeout(() => {
|
|
521
|
+
this.pongTimer = null;
|
|
522
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
523
|
+
this.ws.close(4e3, "pong timeout");
|
|
524
|
+
}
|
|
525
|
+
}, this.pongTimeoutMs);
|
|
526
|
+
}
|
|
527
|
+
clearPongTimer() {
|
|
528
|
+
if (this.pongTimer) {
|
|
529
|
+
clearTimeout(this.pongTimer);
|
|
530
|
+
this.pongTimer = null;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
function createPredictWsClient(config) {
|
|
535
|
+
return new PredictWsClient(config);
|
|
536
|
+
}
|
|
537
|
+
var PredictContext = createContext(null);
|
|
538
|
+
function PredictProvider({
|
|
539
|
+
client,
|
|
540
|
+
wsClient,
|
|
541
|
+
children
|
|
542
|
+
}) {
|
|
543
|
+
const ws = wsClient ?? null;
|
|
544
|
+
const value = useMemo(() => ({ client, wsClient: ws }), [client, ws]);
|
|
545
|
+
return /* @__PURE__ */ jsx(PredictContext.Provider, { value, children });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/utils/polymarket-hmac.ts
|
|
549
|
+
function encode(str) {
|
|
550
|
+
return new TextEncoder().encode(str);
|
|
551
|
+
}
|
|
552
|
+
function base64ToBytes(b64) {
|
|
553
|
+
const binary = atob(b64);
|
|
554
|
+
const bytes = new Uint8Array(binary.length);
|
|
555
|
+
for (let i = 0; i < binary.length; i++) {
|
|
556
|
+
bytes[i] = binary.charCodeAt(i);
|
|
557
|
+
}
|
|
558
|
+
return bytes;
|
|
559
|
+
}
|
|
560
|
+
function bytesToBase64(bytes) {
|
|
561
|
+
let binary = "";
|
|
562
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
563
|
+
binary += String.fromCharCode(bytes[i]);
|
|
564
|
+
}
|
|
565
|
+
return btoa(binary);
|
|
566
|
+
}
|
|
567
|
+
async function hmacSha256Base64(secretBase64, message) {
|
|
568
|
+
const keyBytes = base64ToBytes(secretBase64);
|
|
569
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
570
|
+
"raw",
|
|
571
|
+
keyBytes.buffer,
|
|
572
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
573
|
+
false,
|
|
574
|
+
["sign"]
|
|
575
|
+
);
|
|
576
|
+
const msgBytes = encode(message);
|
|
577
|
+
const signature = await crypto.subtle.sign(
|
|
578
|
+
"HMAC",
|
|
579
|
+
cryptoKey,
|
|
580
|
+
msgBytes.buffer
|
|
581
|
+
);
|
|
582
|
+
return bytesToBase64(new Uint8Array(signature));
|
|
583
|
+
}
|
|
584
|
+
async function buildPolymarketL2Headers(address, input) {
|
|
585
|
+
const timestamp = Math.floor(Date.now() / 1e3).toString();
|
|
586
|
+
const body = input.body ?? "";
|
|
587
|
+
const message = `${timestamp}${input.method}${input.requestPath}${body}`;
|
|
588
|
+
const signature = await hmacSha256Base64(input.secret, message);
|
|
589
|
+
return {
|
|
590
|
+
POLY_ADDRESS: address,
|
|
591
|
+
POLY_SIGNATURE: signature,
|
|
592
|
+
POLY_TIMESTAMP: timestamp,
|
|
593
|
+
POLY_API_KEY: input.apiKey,
|
|
594
|
+
POLY_PASSPHRASE: input.passphrase
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
var CLOB_AUTH_DOMAIN = {
|
|
598
|
+
name: "ClobAuthDomain",
|
|
599
|
+
version: "1",
|
|
600
|
+
chainId: 137
|
|
601
|
+
};
|
|
602
|
+
var CLOB_AUTH_TYPES = {
|
|
603
|
+
ClobAuth: [
|
|
604
|
+
{ name: "address", type: "address" },
|
|
605
|
+
{ name: "timestamp", type: "string" },
|
|
606
|
+
{ name: "nonce", type: "uint256" },
|
|
607
|
+
{ name: "message", type: "string" }
|
|
608
|
+
]
|
|
609
|
+
};
|
|
610
|
+
function buildClobAuthMessage(input) {
|
|
611
|
+
return {
|
|
612
|
+
address: input.address,
|
|
613
|
+
timestamp: input.timestamp,
|
|
614
|
+
nonce: input.nonce,
|
|
615
|
+
message: "This message attests that I control the given wallet"
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
async function derivePolymarketApiKey(address, signature, timestamp, nonce) {
|
|
619
|
+
const res = await fetch(
|
|
620
|
+
`https://clob.polymarket.com/auth/derive-api-key?nonce=${nonce}`,
|
|
621
|
+
{
|
|
622
|
+
method: "GET",
|
|
623
|
+
headers: {
|
|
624
|
+
"Content-Type": "application/json",
|
|
625
|
+
POLY_ADDRESS: address,
|
|
626
|
+
POLY_SIGNATURE: signature,
|
|
627
|
+
POLY_TIMESTAMP: timestamp,
|
|
628
|
+
POLY_NONCE: String(nonce)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
if (!res.ok) {
|
|
633
|
+
const text = await res.text().catch(() => res.statusText);
|
|
634
|
+
throw new Error(
|
|
635
|
+
`Polymarket credential exchange failed (${res.status}): ${text}`
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
return res.json();
|
|
639
|
+
}
|
|
640
|
+
var PolymarketContext = createContext(
|
|
641
|
+
null
|
|
642
|
+
);
|
|
643
|
+
function PolymarketProvider({ children }) {
|
|
644
|
+
const [credentials, setCredentials] = useState(
|
|
645
|
+
null
|
|
646
|
+
);
|
|
647
|
+
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
|
648
|
+
const [authError, setAuthError] = useState(null);
|
|
649
|
+
const lastAuthAddressRef = useRef(null);
|
|
650
|
+
const authenticate = useCallback(
|
|
651
|
+
async (signer) => {
|
|
652
|
+
if (lastAuthAddressRef.current === signer.address && credentials !== null) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
setIsAuthenticating(true);
|
|
656
|
+
setAuthError(null);
|
|
657
|
+
try {
|
|
658
|
+
const timestamp = Math.floor(Date.now() / 1e3).toString();
|
|
659
|
+
const nonce = 0;
|
|
660
|
+
const message = buildClobAuthMessage({
|
|
661
|
+
address: signer.address,
|
|
662
|
+
timestamp,
|
|
663
|
+
nonce
|
|
664
|
+
});
|
|
665
|
+
const signature = await signer.signTypedData(
|
|
666
|
+
CLOB_AUTH_DOMAIN,
|
|
667
|
+
CLOB_AUTH_TYPES,
|
|
668
|
+
message
|
|
669
|
+
);
|
|
670
|
+
const { apiKey, secret, passphrase } = await derivePolymarketApiKey(
|
|
671
|
+
signer.address,
|
|
672
|
+
signature,
|
|
673
|
+
timestamp,
|
|
674
|
+
nonce
|
|
675
|
+
);
|
|
676
|
+
lastAuthAddressRef.current = signer.address;
|
|
677
|
+
setCredentials({ apiKey, secret, passphrase, address: signer.address });
|
|
678
|
+
} catch (err) {
|
|
679
|
+
setAuthError(
|
|
680
|
+
err instanceof Error ? err : new Error("Polymarket authentication failed")
|
|
681
|
+
);
|
|
682
|
+
lastAuthAddressRef.current = null;
|
|
683
|
+
} finally {
|
|
684
|
+
setIsAuthenticating(false);
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
[credentials]
|
|
688
|
+
);
|
|
689
|
+
const clearCredentials = useCallback(() => {
|
|
690
|
+
setCredentials(null);
|
|
691
|
+
lastAuthAddressRef.current = null;
|
|
692
|
+
}, []);
|
|
693
|
+
return /* @__PURE__ */ jsx(
|
|
694
|
+
PolymarketContext.Provider,
|
|
695
|
+
{
|
|
696
|
+
value: {
|
|
697
|
+
credentials,
|
|
698
|
+
isAuthenticating,
|
|
699
|
+
authError,
|
|
700
|
+
authenticate,
|
|
701
|
+
clearCredentials
|
|
702
|
+
},
|
|
703
|
+
children
|
|
704
|
+
}
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
function usePredictClient() {
|
|
708
|
+
const context = useContext(PredictContext);
|
|
709
|
+
if (!context) {
|
|
710
|
+
throw new Error("usePredictClient must be used within a PredictProvider");
|
|
711
|
+
}
|
|
712
|
+
return context.client;
|
|
713
|
+
}
|
|
714
|
+
function eventsQueryKey(params) {
|
|
715
|
+
return ["predict", "events", params ?? {}];
|
|
716
|
+
}
|
|
717
|
+
async function fetchEvents(client, params) {
|
|
718
|
+
return client.listEvents(params);
|
|
719
|
+
}
|
|
720
|
+
function useEvents(params, queryOptions = {}) {
|
|
721
|
+
const client = usePredictClient();
|
|
722
|
+
return useQuery({
|
|
723
|
+
queryKey: eventsQueryKey(params),
|
|
724
|
+
queryFn: () => client.listEvents(params),
|
|
725
|
+
...queryOptions
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/hooks/predict/events.params.ts
|
|
730
|
+
var DEFAULT_PAGE_SIZE = 48;
|
|
731
|
+
function resolveTagSlug(selection) {
|
|
732
|
+
if (!selection) return void 0;
|
|
733
|
+
return selection.tagSlug ?? selection.categorySlug ?? void 0;
|
|
734
|
+
}
|
|
735
|
+
function resolveEventsParams(input = {}) {
|
|
736
|
+
const {
|
|
737
|
+
tagSlugSelection,
|
|
738
|
+
limit = DEFAULT_PAGE_SIZE,
|
|
739
|
+
status = "open",
|
|
740
|
+
with_markets = true,
|
|
741
|
+
source,
|
|
742
|
+
sort_by,
|
|
743
|
+
sort_asc
|
|
744
|
+
} = input;
|
|
745
|
+
const tag_slug = resolveTagSlug(tagSlugSelection);
|
|
746
|
+
return {
|
|
747
|
+
limit,
|
|
748
|
+
status,
|
|
749
|
+
with_markets,
|
|
750
|
+
...source ? { source } : {},
|
|
751
|
+
...tag_slug ? { tag_slug } : {},
|
|
752
|
+
...sort_by ? { sort_by } : {},
|
|
753
|
+
...sort_asc !== void 0 ? { sort_asc } : {}
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function infiniteEventsQueryKey(params) {
|
|
757
|
+
return ["predict", "events-infinite", params];
|
|
758
|
+
}
|
|
759
|
+
async function fetchEventsPage(client, params) {
|
|
760
|
+
return client.listEvents(params);
|
|
761
|
+
}
|
|
762
|
+
function eventQueryKey(slug, source) {
|
|
763
|
+
return ["predict", "event", slug, source];
|
|
764
|
+
}
|
|
765
|
+
async function fetchEvent(client, slug, source) {
|
|
766
|
+
return client.getEvent(slug, source);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/hooks/predict/useEvent.ts
|
|
770
|
+
function useEvent(params, queryOptions = {}) {
|
|
771
|
+
const client = usePredictClient();
|
|
772
|
+
return useQuery({
|
|
773
|
+
queryKey: eventQueryKey(params.slug, params.source),
|
|
774
|
+
queryFn: () => fetchEvent(client, params.slug, params.source),
|
|
775
|
+
enabled: Boolean(params.slug),
|
|
776
|
+
...queryOptions
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
function useInfiniteEvents(params, queryOptions = {}) {
|
|
780
|
+
const client = usePredictClient();
|
|
781
|
+
return useInfiniteQuery({
|
|
782
|
+
queryKey: infiniteEventsQueryKey(params),
|
|
783
|
+
queryFn: ({ pageParam }) => fetchEventsPage(client, {
|
|
784
|
+
...params,
|
|
785
|
+
...pageParam !== void 0 ? { cursor: pageParam } : {}
|
|
786
|
+
}),
|
|
787
|
+
initialPageParam: void 0,
|
|
788
|
+
getNextPageParam: (lastPage) => lastPage.has_more && lastPage.next_cursor ? lastPage.next_cursor : void 0,
|
|
789
|
+
...queryOptions
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
function searchEventsQueryKey(params) {
|
|
793
|
+
return ["predict", "search", params];
|
|
794
|
+
}
|
|
795
|
+
function useSearchEvents(params, queryOptions) {
|
|
796
|
+
const client = usePredictClient();
|
|
797
|
+
const apiParams = useMemo(
|
|
798
|
+
() => ({
|
|
799
|
+
search: params.keyword,
|
|
800
|
+
limit: params.limit ?? 20,
|
|
801
|
+
status: params.status ?? "open",
|
|
802
|
+
source: params.source,
|
|
803
|
+
with_markets: params.with_markets ?? false
|
|
804
|
+
}),
|
|
805
|
+
[
|
|
806
|
+
params.keyword,
|
|
807
|
+
params.limit,
|
|
808
|
+
params.status,
|
|
809
|
+
params.source,
|
|
810
|
+
params.with_markets
|
|
811
|
+
]
|
|
812
|
+
);
|
|
813
|
+
return useInfiniteQuery({
|
|
814
|
+
queryKey: searchEventsQueryKey(apiParams),
|
|
815
|
+
queryFn: ({ pageParam }) => client.listEvents({
|
|
816
|
+
...apiParams,
|
|
817
|
+
...pageParam !== void 0 ? { cursor: pageParam } : {}
|
|
818
|
+
}),
|
|
819
|
+
initialPageParam: void 0,
|
|
820
|
+
getNextPageParam: (lastPage) => lastPage.has_more && lastPage.next_cursor ? lastPage.next_cursor : void 0,
|
|
821
|
+
enabled: queryOptions?.enabled ?? !!params.keyword
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
function similarEventsQueryKey(slug, source, params) {
|
|
825
|
+
return ["predict", "similar-events", slug, source, params];
|
|
826
|
+
}
|
|
827
|
+
function useSimilarEvents(params, queryOptions = {}) {
|
|
828
|
+
const client = usePredictClient();
|
|
829
|
+
const apiParams = {
|
|
830
|
+
limit: params.limit,
|
|
831
|
+
same_source: params.same_source
|
|
832
|
+
};
|
|
833
|
+
return useQuery({
|
|
834
|
+
queryKey: similarEventsQueryKey(params.slug, params.source, apiParams),
|
|
835
|
+
queryFn: () => client.getSimilarEvents(params.slug, params.source, apiParams),
|
|
836
|
+
enabled: Boolean(params.slug),
|
|
837
|
+
staleTime: 5 * 6e4,
|
|
838
|
+
...queryOptions
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
function marketQueryKey(slug, source) {
|
|
842
|
+
return ["predict", "market", slug, source];
|
|
843
|
+
}
|
|
844
|
+
async function fetchMarket(client, slug, source) {
|
|
845
|
+
return client.getMarket(slug, source);
|
|
846
|
+
}
|
|
847
|
+
function useMarket(params, queryOptions = {}) {
|
|
848
|
+
const client = usePredictClient();
|
|
849
|
+
return useQuery({
|
|
850
|
+
queryKey: marketQueryKey(params.slug, params.source),
|
|
851
|
+
queryFn: () => fetchMarket(client, params.slug, params.source),
|
|
852
|
+
enabled: Boolean(params.slug),
|
|
853
|
+
...queryOptions
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
function priceHistoryQueryKey(slug, source, range) {
|
|
857
|
+
return ["predict", "price-history", slug, source, range];
|
|
858
|
+
}
|
|
859
|
+
function usePriceHistory(params, queryOptions = {}) {
|
|
860
|
+
const client = usePredictClient();
|
|
861
|
+
return useQuery({
|
|
862
|
+
queryKey: priceHistoryQueryKey(params.slug, params.source, params.range),
|
|
863
|
+
queryFn: () => client.getPriceHistory(params.slug, params.source, params.range),
|
|
864
|
+
enabled: Boolean(params.slug),
|
|
865
|
+
staleTime: 6e4,
|
|
866
|
+
...queryOptions
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/hooks/predict/useMarketHistory.ts
|
|
871
|
+
var ChartRange = {
|
|
872
|
+
ONE_DAY: "1d",
|
|
873
|
+
ONE_WEEK: "1w",
|
|
874
|
+
ONE_MONTH: "1m",
|
|
875
|
+
ALL: "all"
|
|
876
|
+
};
|
|
877
|
+
var RANGE_MAP = {
|
|
878
|
+
[ChartRange.ONE_DAY]: "1d",
|
|
879
|
+
[ChartRange.ONE_WEEK]: "1w",
|
|
880
|
+
[ChartRange.ONE_MONTH]: "1m",
|
|
881
|
+
[ChartRange.ALL]: "all"
|
|
882
|
+
};
|
|
883
|
+
function useMarketHistory(markets, range = ChartRange.ALL) {
|
|
884
|
+
const client = usePredictClient();
|
|
885
|
+
const apiRange = RANGE_MAP[range];
|
|
886
|
+
const queries = useQueries({
|
|
887
|
+
queries: markets.map((market) => ({
|
|
888
|
+
queryKey: priceHistoryQueryKey(market.slug, market.source, apiRange),
|
|
889
|
+
queryFn: () => client.getPriceHistory(market.slug, market.source, apiRange),
|
|
890
|
+
staleTime: 6e4,
|
|
891
|
+
enabled: Boolean(market.slug)
|
|
892
|
+
}))
|
|
893
|
+
});
|
|
894
|
+
const isLoading = queries.some((q) => q.isLoading);
|
|
895
|
+
const series = useMemo(() => {
|
|
896
|
+
return markets.map((market, idx) => {
|
|
897
|
+
const result = queries[idx];
|
|
898
|
+
const points = result?.data?.points ?? [];
|
|
899
|
+
if (points.length > 0) {
|
|
900
|
+
return {
|
|
901
|
+
marketSlug: market.slug,
|
|
902
|
+
label: market.outcomes?.[0]?.label ?? market.question,
|
|
903
|
+
data: points.map((pt) => ({
|
|
904
|
+
timestamp: pt.t * 1e3,
|
|
905
|
+
price: pt.p
|
|
906
|
+
}))
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const yesOutcome = market.outcomes?.find((o) => o.label.toLowerCase() === "yes") ?? market.outcomes?.[0];
|
|
910
|
+
const currentPrice = yesOutcome?.price ?? 0.5;
|
|
911
|
+
const now = Date.now();
|
|
912
|
+
const dayMs = 864e5;
|
|
913
|
+
return {
|
|
914
|
+
marketSlug: market.slug,
|
|
915
|
+
label: market.outcomes?.[0]?.label ?? market.question,
|
|
916
|
+
data: [
|
|
917
|
+
{ timestamp: now - 7 * dayMs, price: currentPrice },
|
|
918
|
+
{ timestamp: now, price: currentPrice }
|
|
919
|
+
]
|
|
920
|
+
};
|
|
921
|
+
});
|
|
922
|
+
}, [markets, queries]);
|
|
923
|
+
return { series, isLoading };
|
|
924
|
+
}
|
|
925
|
+
function orderbookQueryKey(slug, source) {
|
|
926
|
+
return ["predict", "orderbook", slug, source];
|
|
927
|
+
}
|
|
928
|
+
function useOrderbook(params, queryOptions = {}) {
|
|
929
|
+
const client = usePredictClient();
|
|
930
|
+
return useQuery({
|
|
931
|
+
queryKey: orderbookQueryKey(params.slug, params.source),
|
|
932
|
+
queryFn: () => client.getOrderbook(params.slug, params.source),
|
|
933
|
+
enabled: Boolean(params.slug),
|
|
934
|
+
refetchInterval: 5e3,
|
|
935
|
+
...queryOptions
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
function marketTradesQueryKey(slug, params) {
|
|
939
|
+
return ["predict", "market-trades", slug, params];
|
|
940
|
+
}
|
|
941
|
+
function useMarketTrades(params, queryOptions = {}) {
|
|
942
|
+
const client = usePredictClient();
|
|
943
|
+
const apiParams = {
|
|
944
|
+
source: params.source,
|
|
945
|
+
limit: params.limit,
|
|
946
|
+
cursor: params.cursor,
|
|
947
|
+
side: params.side
|
|
948
|
+
};
|
|
949
|
+
return useQuery({
|
|
950
|
+
queryKey: marketTradesQueryKey(params.slug, apiParams),
|
|
951
|
+
queryFn: () => client.listMarketTrades(params.slug, apiParams),
|
|
952
|
+
enabled: Boolean(params.slug),
|
|
953
|
+
...queryOptions
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
function candlesticksQueryKey(slug, params) {
|
|
957
|
+
return ["predict", "candlesticks", slug, params];
|
|
958
|
+
}
|
|
959
|
+
function useCandlesticks(params, queryOptions = {}) {
|
|
960
|
+
const client = usePredictClient();
|
|
961
|
+
const apiParams = {
|
|
962
|
+
interval: params.interval,
|
|
963
|
+
limit: params.limit
|
|
964
|
+
};
|
|
965
|
+
return useQuery({
|
|
966
|
+
queryKey: candlesticksQueryKey(params.slug, apiParams),
|
|
967
|
+
queryFn: () => client.listCandlesticks(params.slug, apiParams),
|
|
968
|
+
enabled: Boolean(params.slug),
|
|
969
|
+
staleTime: 6e4,
|
|
970
|
+
...queryOptions
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function positionsQueryKey(user, source) {
|
|
974
|
+
return ["predict", "positions", source ?? "all", user];
|
|
975
|
+
}
|
|
976
|
+
function usePositions(params, queryOptions = {}) {
|
|
977
|
+
const client = usePredictClient();
|
|
978
|
+
return useQuery({
|
|
979
|
+
queryKey: positionsQueryKey(params.user, params.source),
|
|
980
|
+
queryFn: () => client.getPositions(params.user, params.source),
|
|
981
|
+
enabled: Boolean(params.user),
|
|
982
|
+
staleTime: 1e4,
|
|
983
|
+
...queryOptions
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
function balanceQueryKey(source, user) {
|
|
987
|
+
return ["predict", "balance", source, user];
|
|
988
|
+
}
|
|
989
|
+
function useBalance(params, queryOptions = {}) {
|
|
990
|
+
const client = usePredictClient();
|
|
991
|
+
return useQuery({
|
|
992
|
+
queryKey: balanceQueryKey(params.source, params.user),
|
|
993
|
+
queryFn: () => client.getBalance(params.source, params.user),
|
|
994
|
+
enabled: Boolean(params.user && params.source),
|
|
995
|
+
staleTime: 1e4,
|
|
996
|
+
...queryOptions
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
function ordersQueryKey(params) {
|
|
1000
|
+
return ["predict", "orders", params];
|
|
1001
|
+
}
|
|
1002
|
+
function useOrders(params, queryOptions = {}) {
|
|
1003
|
+
const client = usePredictClient();
|
|
1004
|
+
return useQuery({
|
|
1005
|
+
queryKey: ordersQueryKey(params),
|
|
1006
|
+
queryFn: () => client.listOrders(params),
|
|
1007
|
+
enabled: Boolean(params.wallet_address),
|
|
1008
|
+
...queryOptions
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
function orderQueryKey(id, source) {
|
|
1012
|
+
return ["predict", "order", id, source];
|
|
1013
|
+
}
|
|
1014
|
+
function useOrder(params, queryOptions = {}) {
|
|
1015
|
+
const client = usePredictClient();
|
|
1016
|
+
return useQuery({
|
|
1017
|
+
queryKey: orderQueryKey(params.id, params.source),
|
|
1018
|
+
queryFn: () => client.getOrder(params.id, params.source),
|
|
1019
|
+
enabled: Boolean(params.id),
|
|
1020
|
+
refetchInterval: 1e3,
|
|
1021
|
+
...queryOptions
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
function useCancelOrder(mutationOptions = {}) {
|
|
1025
|
+
const client = usePredictClient();
|
|
1026
|
+
const queryClient = useQueryClient();
|
|
1027
|
+
return useMutation({
|
|
1028
|
+
mutationFn: (vars) => client.cancelOrder(vars.id, vars.source),
|
|
1029
|
+
onSuccess: () => {
|
|
1030
|
+
queryClient.invalidateQueries({ queryKey: ["predict", "orders"] });
|
|
1031
|
+
},
|
|
1032
|
+
...mutationOptions
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
function tradesQueryKey(params) {
|
|
1036
|
+
return ["predict", "trades-by-wallet", params];
|
|
1037
|
+
}
|
|
1038
|
+
function useTrades(params, queryOptions = {}) {
|
|
1039
|
+
const client = usePredictClient();
|
|
1040
|
+
return useQuery({
|
|
1041
|
+
queryKey: tradesQueryKey(params),
|
|
1042
|
+
queryFn: () => client.listTrades(params),
|
|
1043
|
+
enabled: Boolean(params.wallet),
|
|
1044
|
+
...queryOptions
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
function dflowQuoteQueryKey(params) {
|
|
1048
|
+
return ["predict", "dflow-quote", params];
|
|
1049
|
+
}
|
|
1050
|
+
function useDFlowQuote(params, queryOptions = {}) {
|
|
1051
|
+
const client = usePredictClient();
|
|
1052
|
+
const enabled = Boolean(params.inputMint) && Boolean(params.outputMint) && Boolean(params.userPublicKey) && params.amount !== "0" && params.amount !== "";
|
|
1053
|
+
return useQuery({
|
|
1054
|
+
queryKey: dflowQuoteQueryKey(params),
|
|
1055
|
+
queryFn: () => client.createDFlowQuote(params),
|
|
1056
|
+
enabled,
|
|
1057
|
+
staleTime: 1e4,
|
|
1058
|
+
...queryOptions
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
function useDFlowSubmit(mutationOptions = {}) {
|
|
1062
|
+
const client = usePredictClient();
|
|
1063
|
+
const queryClient = useQueryClient();
|
|
1064
|
+
return useMutation({
|
|
1065
|
+
mutationFn: (body) => client.submitDFlowTransaction(body),
|
|
1066
|
+
onSuccess: () => {
|
|
1067
|
+
queryClient.invalidateQueries({ queryKey: ["predict", "orders"] });
|
|
1068
|
+
queryClient.invalidateQueries({ queryKey: ["predict", "positions"] });
|
|
1069
|
+
},
|
|
1070
|
+
...mutationOptions
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
function usePredictWsClient() {
|
|
1074
|
+
const context = useContext(PredictContext);
|
|
1075
|
+
if (!context) {
|
|
1076
|
+
throw new Error("usePredictWsClient must be used within a PredictProvider");
|
|
1077
|
+
}
|
|
1078
|
+
const { wsClient } = context;
|
|
1079
|
+
const [wsStatus, setWsStatus] = useState(
|
|
1080
|
+
wsClient?.getStatus() ?? "disconnected"
|
|
1081
|
+
);
|
|
1082
|
+
useEffect(() => {
|
|
1083
|
+
if (!wsClient) {
|
|
1084
|
+
setWsStatus("disconnected");
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
setWsStatus(wsClient.getStatus());
|
|
1088
|
+
return wsClient.onStatusChange(setWsStatus);
|
|
1089
|
+
}, [wsClient]);
|
|
1090
|
+
return {
|
|
1091
|
+
wsClient,
|
|
1092
|
+
wsStatus,
|
|
1093
|
+
isWsConnected: wsStatus === "connected"
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function usePricesSubscription({
|
|
1097
|
+
wsClient,
|
|
1098
|
+
slugs,
|
|
1099
|
+
enabled = true,
|
|
1100
|
+
onUpdate
|
|
1101
|
+
}) {
|
|
1102
|
+
const [prices, setPrices] = useState(
|
|
1103
|
+
() => /* @__PURE__ */ new Map()
|
|
1104
|
+
);
|
|
1105
|
+
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
1106
|
+
const onUpdateRef = useRef(onUpdate);
|
|
1107
|
+
onUpdateRef.current = onUpdate;
|
|
1108
|
+
const slugsKey = slugs.join(",");
|
|
1109
|
+
const handleUpdate = useCallback((msg) => {
|
|
1110
|
+
setPrices((prev) => {
|
|
1111
|
+
const next = new Map(prev);
|
|
1112
|
+
next.set(msg.data.market_slug, msg.data);
|
|
1113
|
+
return next;
|
|
1114
|
+
});
|
|
1115
|
+
onUpdateRef.current?.(msg);
|
|
1116
|
+
}, []);
|
|
1117
|
+
useEffect(() => {
|
|
1118
|
+
if (!wsClient || !enabled || slugs.length === 0) {
|
|
1119
|
+
setIsSubscribed(false);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const unsub = wsClient.subscribePrices(slugs, handleUpdate);
|
|
1123
|
+
setIsSubscribed(true);
|
|
1124
|
+
return () => {
|
|
1125
|
+
unsub();
|
|
1126
|
+
setIsSubscribed(false);
|
|
1127
|
+
};
|
|
1128
|
+
}, [wsClient, enabled, slugsKey, handleUpdate]);
|
|
1129
|
+
return { prices, isSubscribed };
|
|
1130
|
+
}
|
|
1131
|
+
function useOrderbookSubscription({
|
|
1132
|
+
wsClient,
|
|
1133
|
+
slug,
|
|
1134
|
+
enabled = true,
|
|
1135
|
+
onUpdate
|
|
1136
|
+
}) {
|
|
1137
|
+
const [orderbook, setOrderbook] = useState(null);
|
|
1138
|
+
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
1139
|
+
const onUpdateRef = useRef(onUpdate);
|
|
1140
|
+
onUpdateRef.current = onUpdate;
|
|
1141
|
+
const handleUpdate = useCallback(
|
|
1142
|
+
(msg) => {
|
|
1143
|
+
if (msg.data.market_slug === slug) {
|
|
1144
|
+
setOrderbook(msg.data);
|
|
1145
|
+
onUpdateRef.current?.(msg);
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
[slug]
|
|
1149
|
+
);
|
|
1150
|
+
useEffect(() => {
|
|
1151
|
+
if (!wsClient || !enabled || !slug) {
|
|
1152
|
+
setIsSubscribed(false);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
setOrderbook(null);
|
|
1156
|
+
const unsub = wsClient.subscribeOrderbook([slug], handleUpdate);
|
|
1157
|
+
setIsSubscribed(true);
|
|
1158
|
+
return () => {
|
|
1159
|
+
unsub();
|
|
1160
|
+
setIsSubscribed(false);
|
|
1161
|
+
};
|
|
1162
|
+
}, [wsClient, enabled, slug, handleUpdate]);
|
|
1163
|
+
return { orderbook, isSubscribed };
|
|
1164
|
+
}
|
|
1165
|
+
var DEFAULT_MAX_HISTORY = 100;
|
|
1166
|
+
function useTradesSubscription({
|
|
1167
|
+
wsClient,
|
|
1168
|
+
slug,
|
|
1169
|
+
enabled = true,
|
|
1170
|
+
maxHistory = DEFAULT_MAX_HISTORY,
|
|
1171
|
+
onUpdate
|
|
1172
|
+
}) {
|
|
1173
|
+
const [trades, setTrades] = useState([]);
|
|
1174
|
+
const [isSubscribed, setIsSubscribed] = useState(false);
|
|
1175
|
+
const onUpdateRef = useRef(onUpdate);
|
|
1176
|
+
onUpdateRef.current = onUpdate;
|
|
1177
|
+
const maxRef = useRef(maxHistory);
|
|
1178
|
+
maxRef.current = maxHistory;
|
|
1179
|
+
const handleUpdate = useCallback(
|
|
1180
|
+
(msg) => {
|
|
1181
|
+
if (msg.data.market_slug === slug) {
|
|
1182
|
+
setTrades((prev) => {
|
|
1183
|
+
const next = [msg.data, ...prev];
|
|
1184
|
+
return next.length > maxRef.current ? next.slice(0, maxRef.current) : next;
|
|
1185
|
+
});
|
|
1186
|
+
onUpdateRef.current?.(msg);
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
[slug]
|
|
1190
|
+
);
|
|
1191
|
+
const clearHistory = useCallback(() => {
|
|
1192
|
+
setTrades([]);
|
|
1193
|
+
}, []);
|
|
1194
|
+
useEffect(() => {
|
|
1195
|
+
if (!wsClient || !enabled || !slug) {
|
|
1196
|
+
setIsSubscribed(false);
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
setTrades([]);
|
|
1200
|
+
const unsub = wsClient.subscribeTrades([slug], handleUpdate);
|
|
1201
|
+
setIsSubscribed(true);
|
|
1202
|
+
return () => {
|
|
1203
|
+
unsub();
|
|
1204
|
+
setIsSubscribed(false);
|
|
1205
|
+
};
|
|
1206
|
+
}, [wsClient, enabled, slug, handleUpdate]);
|
|
1207
|
+
return { trades, isSubscribed, clearHistory };
|
|
1208
|
+
}
|
|
1209
|
+
var REST_POLL_INTERVAL = 5e3;
|
|
1210
|
+
function useRealtimeOrderbook(params, queryOptions = {}) {
|
|
1211
|
+
const { wsClient } = usePredictWsClient();
|
|
1212
|
+
const queryClient = useQueryClient();
|
|
1213
|
+
const subParams = {
|
|
1214
|
+
wsClient,
|
|
1215
|
+
slug: params.slug,
|
|
1216
|
+
enabled: Boolean(params.slug)
|
|
1217
|
+
};
|
|
1218
|
+
const { isSubscribed } = useOrderbookSubscription(subParams);
|
|
1219
|
+
const qk = orderbookQueryKey(params.slug, params.source);
|
|
1220
|
+
const qkRef = useRef(qk);
|
|
1221
|
+
qkRef.current = qk;
|
|
1222
|
+
useEffect(() => {
|
|
1223
|
+
if (!wsClient || !params.slug) return;
|
|
1224
|
+
const unsub = wsClient.subscribeOrderbook([params.slug], (msg) => {
|
|
1225
|
+
const ob = {
|
|
1226
|
+
market_id: msg.data.market_slug,
|
|
1227
|
+
bids: msg.data.bids,
|
|
1228
|
+
asks: msg.data.asks,
|
|
1229
|
+
spread: msg.data.spread
|
|
1230
|
+
};
|
|
1231
|
+
queryClient.setQueryData(qkRef.current, ob);
|
|
1232
|
+
});
|
|
1233
|
+
return unsub;
|
|
1234
|
+
}, [wsClient, params.slug, queryClient]);
|
|
1235
|
+
return useOrderbook(params, {
|
|
1236
|
+
refetchInterval: isSubscribed ? false : REST_POLL_INTERVAL,
|
|
1237
|
+
...queryOptions
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// src/hooks/predict/useRealtimePrices.ts
|
|
1242
|
+
function useRealtimePrices({
|
|
1243
|
+
slugs,
|
|
1244
|
+
enabled = true,
|
|
1245
|
+
onUpdate
|
|
1246
|
+
}) {
|
|
1247
|
+
const { wsClient } = usePredictWsClient();
|
|
1248
|
+
return usePricesSubscription({
|
|
1249
|
+
wsClient,
|
|
1250
|
+
slugs,
|
|
1251
|
+
enabled,
|
|
1252
|
+
onUpdate
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
function useRealtimeTrades({
|
|
1256
|
+
slug,
|
|
1257
|
+
enabled = true,
|
|
1258
|
+
maxHistory,
|
|
1259
|
+
onUpdate,
|
|
1260
|
+
syncToQueryCache = true
|
|
1261
|
+
}) {
|
|
1262
|
+
const { wsClient } = usePredictWsClient();
|
|
1263
|
+
const queryClient = useQueryClient();
|
|
1264
|
+
const slugRef = useRef(slug);
|
|
1265
|
+
slugRef.current = slug;
|
|
1266
|
+
useEffect(() => {
|
|
1267
|
+
if (!wsClient || !slug || !enabled || !syncToQueryCache) return;
|
|
1268
|
+
const unsub = wsClient.subscribeTrades([slug], (msg) => {
|
|
1269
|
+
const trade = msg.data;
|
|
1270
|
+
const prefix = ["predict", "market-trades", slugRef.current];
|
|
1271
|
+
queryClient.setQueriesData(
|
|
1272
|
+
{ queryKey: prefix },
|
|
1273
|
+
(old) => {
|
|
1274
|
+
if (!old) return old;
|
|
1275
|
+
const syntheticTrade = {
|
|
1276
|
+
id: trade.trade_id ?? `ws-${msg.ts}`,
|
|
1277
|
+
source: "dflow",
|
|
1278
|
+
side: trade.side,
|
|
1279
|
+
outcome: trade.outcome ?? "",
|
|
1280
|
+
outcome_index: 0,
|
|
1281
|
+
price: trade.price,
|
|
1282
|
+
size: trade.size,
|
|
1283
|
+
usd_size: trade.size,
|
|
1284
|
+
timestamp: Math.floor(msg.ts / 1e3),
|
|
1285
|
+
tx_hash: "",
|
|
1286
|
+
wallet: "",
|
|
1287
|
+
type: "TRADE"
|
|
1288
|
+
};
|
|
1289
|
+
return {
|
|
1290
|
+
...old,
|
|
1291
|
+
items: [syntheticTrade, ...old.items]
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
});
|
|
1296
|
+
return unsub;
|
|
1297
|
+
}, [wsClient, slug, enabled, syncToQueryCache, queryClient]);
|
|
1298
|
+
return useTradesSubscription({
|
|
1299
|
+
wsClient,
|
|
1300
|
+
slug,
|
|
1301
|
+
enabled,
|
|
1302
|
+
maxHistory,
|
|
1303
|
+
onUpdate
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1306
|
+
function usePolymarket() {
|
|
1307
|
+
const context = useContext(PolymarketContext);
|
|
1308
|
+
if (!context) {
|
|
1309
|
+
throw new Error("usePolymarket must be used within a PolymarketProvider");
|
|
1310
|
+
}
|
|
1311
|
+
return context;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// src/utils/polymarket-order.ts
|
|
1315
|
+
var CTF_EXCHANGE_ADDRESS = "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E";
|
|
1316
|
+
var NEG_RISK_CTF_EXCHANGE_ADDRESS = "0xC5d563A36AE78145C45a50134d48A1215220f80a";
|
|
1317
|
+
var USDC_ADDRESS = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174";
|
|
1318
|
+
var POLYGON_CHAIN_ID = 137;
|
|
1319
|
+
function buildCtfExchangeDomain(negRisk = false) {
|
|
1320
|
+
return {
|
|
1321
|
+
name: "CTFExchange",
|
|
1322
|
+
version: "1",
|
|
1323
|
+
chainId: POLYGON_CHAIN_ID,
|
|
1324
|
+
verifyingContract: negRisk ? NEG_RISK_CTF_EXCHANGE_ADDRESS : CTF_EXCHANGE_ADDRESS
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
var CTF_ORDER_TYPES = {
|
|
1328
|
+
Order: [
|
|
1329
|
+
{ name: "salt", type: "uint256" },
|
|
1330
|
+
{ name: "maker", type: "address" },
|
|
1331
|
+
{ name: "signer", type: "address" },
|
|
1332
|
+
{ name: "taker", type: "address" },
|
|
1333
|
+
{ name: "tokenId", type: "uint256" },
|
|
1334
|
+
{ name: "makerAmount", type: "uint256" },
|
|
1335
|
+
{ name: "takerAmount", type: "uint256" },
|
|
1336
|
+
{ name: "expiration", type: "uint256" },
|
|
1337
|
+
{ name: "nonce", type: "uint256" },
|
|
1338
|
+
{ name: "feeRateBps", type: "uint256" },
|
|
1339
|
+
{ name: "side", type: "uint8" },
|
|
1340
|
+
{ name: "signatureType", type: "uint8" }
|
|
1341
|
+
]
|
|
1342
|
+
};
|
|
1343
|
+
var ORDER_TYPE = {
|
|
1344
|
+
GTC: 0,
|
|
1345
|
+
// Good-Till-Cancelled
|
|
1346
|
+
FOK: 1,
|
|
1347
|
+
// Fill-Or-Kill
|
|
1348
|
+
GTD: 2,
|
|
1349
|
+
// Good-Till-Date
|
|
1350
|
+
FAK: 3
|
|
1351
|
+
// Fill-And-Kill
|
|
1352
|
+
};
|
|
1353
|
+
var SIDE = { BUY: 0, SELL: 1 };
|
|
1354
|
+
var TICK_DECIMALS = {
|
|
1355
|
+
"0.1": 1,
|
|
1356
|
+
"0.01": 2,
|
|
1357
|
+
"0.001": 3,
|
|
1358
|
+
"0.0001": 4
|
|
1359
|
+
};
|
|
1360
|
+
function roundToTick(price, tickSize) {
|
|
1361
|
+
const decimals = TICK_DECIMALS[tickSize] ?? 2;
|
|
1362
|
+
return price.toFixed(decimals);
|
|
1363
|
+
}
|
|
1364
|
+
function toMicroUsdc(amount) {
|
|
1365
|
+
return BigInt(Math.round(amount * 1e6));
|
|
1366
|
+
}
|
|
1367
|
+
function buildOrderMessage(input) {
|
|
1368
|
+
const side = input.side === "BUY" ? SIDE.BUY : SIDE.SELL;
|
|
1369
|
+
const priceStr = roundToTick(input.price, input.tickSize);
|
|
1370
|
+
const priceNum = parseFloat(priceStr);
|
|
1371
|
+
const sizeShares = toMicroUsdc(input.size);
|
|
1372
|
+
const sizeUsdc = toMicroUsdc(input.size * priceNum);
|
|
1373
|
+
const makerAmount = side === SIDE.BUY ? sizeUsdc.toString() : sizeShares.toString();
|
|
1374
|
+
const takerAmount = side === SIDE.BUY ? sizeShares.toString() : sizeUsdc.toString();
|
|
1375
|
+
const maker = input.funderAddress ?? input.signerAddress;
|
|
1376
|
+
return {
|
|
1377
|
+
salt: Math.floor(Math.random() * 1e15).toString(),
|
|
1378
|
+
maker,
|
|
1379
|
+
signer: input.signerAddress,
|
|
1380
|
+
taker: "0x0000000000000000000000000000000000000000",
|
|
1381
|
+
tokenId: input.tokenId,
|
|
1382
|
+
makerAmount,
|
|
1383
|
+
takerAmount,
|
|
1384
|
+
expiration: String(input.expiration ?? 0),
|
|
1385
|
+
nonce: "0",
|
|
1386
|
+
feeRateBps: "0",
|
|
1387
|
+
side,
|
|
1388
|
+
signatureType: input.signatureType
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
function buildSignedOrder(input) {
|
|
1392
|
+
const message = buildOrderMessage(input);
|
|
1393
|
+
return {
|
|
1394
|
+
...message,
|
|
1395
|
+
signature: input.signature,
|
|
1396
|
+
orderType: input.orderType ?? "GTC"
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/hooks/polymarket/useCreatePolymarketOrder.ts
|
|
1401
|
+
function useCreatePolymarketOrder(mutationOptions = {}) {
|
|
1402
|
+
const client = usePredictClient();
|
|
1403
|
+
const { credentials, authenticate } = usePolymarket();
|
|
1404
|
+
const queryClient = useQueryClient();
|
|
1405
|
+
return useMutation({
|
|
1406
|
+
mutationFn: async ({
|
|
1407
|
+
input,
|
|
1408
|
+
signer
|
|
1409
|
+
}) => {
|
|
1410
|
+
const creds = credentials;
|
|
1411
|
+
if (!creds) {
|
|
1412
|
+
await authenticate(signer);
|
|
1413
|
+
if (!creds) {
|
|
1414
|
+
throw new Error(
|
|
1415
|
+
"Polymarket authentication failed. Cannot create order without credentials."
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const domain = buildCtfExchangeDomain(input.negRisk ?? false);
|
|
1420
|
+
const orderMessage = buildOrderMessage({
|
|
1421
|
+
...input,
|
|
1422
|
+
signerAddress: signer.address,
|
|
1423
|
+
signatureType: signer.signatureType
|
|
1424
|
+
});
|
|
1425
|
+
const signature = await signer.signTypedData(
|
|
1426
|
+
domain,
|
|
1427
|
+
CTF_ORDER_TYPES,
|
|
1428
|
+
orderMessage
|
|
1429
|
+
);
|
|
1430
|
+
const signedOrder = buildSignedOrder({
|
|
1431
|
+
...input,
|
|
1432
|
+
signerAddress: signer.address,
|
|
1433
|
+
signatureType: signer.signatureType,
|
|
1434
|
+
signature
|
|
1435
|
+
});
|
|
1436
|
+
const body = JSON.stringify(signedOrder);
|
|
1437
|
+
const headers = await buildPolymarketL2Headers(creds.address, {
|
|
1438
|
+
apiKey: creds.apiKey,
|
|
1439
|
+
secret: creds.secret,
|
|
1440
|
+
passphrase: creds.passphrase,
|
|
1441
|
+
method: "POST",
|
|
1442
|
+
requestPath: "/api/v1/orders/polymarket",
|
|
1443
|
+
body
|
|
1444
|
+
});
|
|
1445
|
+
return client.createPolymarketOrder(
|
|
1446
|
+
signedOrder,
|
|
1447
|
+
headers
|
|
1448
|
+
);
|
|
1449
|
+
},
|
|
1450
|
+
onSuccess: () => {
|
|
1451
|
+
queryClient.invalidateQueries({ queryKey: ["predict", "orders"] });
|
|
1452
|
+
queryClient.invalidateQueries({ queryKey: ["predict", "positions"] });
|
|
1453
|
+
},
|
|
1454
|
+
...mutationOptions
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
export { CLOB_AUTH_DOMAIN, CLOB_AUTH_TYPES, CTF_EXCHANGE_ADDRESS, CTF_ORDER_TYPES, ChartRange, NEG_RISK_CTF_EXCHANGE_ADDRESS, ORDER_TYPE, POLYGON_CHAIN_ID, PolymarketContext, PolymarketProvider, PredictClient, PredictContext, PredictProvider, PredictWsClient, SIDE, USDC_ADDRESS, balanceQueryKey, buildClobAuthMessage, buildCtfExchangeDomain, buildOrderMessage, buildPolymarketL2Headers, buildSignedOrder, candlesticksQueryKey, createPredictClient, createPredictWsClient, derivePolymarketApiKey, dflowQuoteQueryKey, eventQueryKey, eventsQueryKey, fetchEvent, fetchEvents, fetchEventsPage, fetchMarket, hmacSha256Base64, infiniteEventsQueryKey, marketQueryKey, marketTradesQueryKey, orderQueryKey, orderbookQueryKey, ordersQueryKey, positionsQueryKey, priceHistoryQueryKey, resolveEventsParams, resolveTagSlug, similarEventsQueryKey, tradesQueryKey, useBalance, useCancelOrder, useCandlesticks, useCreatePolymarketOrder, useDFlowQuote, useDFlowSubmit, useEvent, useEvents, useInfiniteEvents, useMarket, useMarketHistory, useMarketTrades, useOrder, useOrderbook, useOrderbookSubscription, useOrders, usePolymarket, usePositions, usePredictClient, usePredictWsClient, usePriceHistory, usePricesSubscription, useRealtimeOrderbook, useRealtimePrices, useRealtimeTrades, useSearchEvents, useSimilarEvents, useTrades, useTradesSubscription };
|
|
1459
|
+
//# sourceMappingURL=index.mjs.map
|
|
1460
|
+
//# sourceMappingURL=index.mjs.map
|