@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 ADDED
@@ -0,0 +1,447 @@
1
+ # @liberfi.io/react-predict
2
+
3
+ React hooks and HTTP client for prediction markets backed by the `prediction-server` API. Supports Polymarket (EVM, CLOB-based) and DFlow (Solana-based) order flows, including L1/L2 authentication, EIP-712 order signing, and all read-side market data queries.
4
+
5
+ Consumed primarily by `@liberfi.io/ui-predict` and by Next.js prediction-market applications. The package is wallet-agnostic — it defines a `PolymarketSigner` interface rather than depending on any specific wallet library.
6
+
7
+ ## Design Philosophy
8
+
9
+ - **Wallet-agnostic (IoC)** — `PolymarketSigner` is an interface injected by the consumer. No dependency on `@liberfi.io/wallet-connector` or any specific wallet library.
10
+ - **No `@polymarket/clob-client` dependency** — EIP-712 signing and HMAC-SHA256 are implemented with the Web Crypto API to avoid ethers v5/v6 conflicts.
11
+ - **In-memory credentials** — Polymarket L2 API keys are derived with `nonce=0` (permanent, deterministic) and stored only in a React context. They disappear on page unload.
12
+ - **Dual entry points** — `index.ts` for React consumers; `server.ts` for SSR-safe usage in Next.js Server Components and route handlers (no React imports).
13
+ - **Layered realtime API** — Low-level WS subscription hooks give full control; high-level `useRealtime*` hooks merge WS + REST with automatic fallback, so consumers can upgrade from polling to real-time with a one-line change.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @liberfi.io/react-predict
19
+ ```
20
+
21
+ Peer dependencies the consumer must provide:
22
+
23
+ ```bash
24
+ pnpm add react react-dom @tanstack/react-query
25
+ ```
26
+
27
+ ## API Reference
28
+
29
+ ### Providers
30
+
31
+ #### `PredictProvider`
32
+
33
+ Provides the `PredictClient` instance via React context. Place at the application root where prediction hooks are used.
34
+
35
+ ```tsx
36
+ import {
37
+ createPredictClient,
38
+ PredictProvider,
39
+ } from "@liberfi.io/react-predict";
40
+
41
+ const client = createPredictClient("https://prediction.example.com");
42
+
43
+ <PredictProvider client={client}>
44
+ <App />
45
+ </PredictProvider>;
46
+ ```
47
+
48
+ Props:
49
+
50
+ - `client: PredictClient` — a `PredictClient` instance.
51
+ - `wsClient?: PredictWsClient | null` — optional WebSocket client for real-time data.
52
+
53
+ ---
54
+
55
+ #### `PolymarketProvider`
56
+
57
+ Manages in-memory Polymarket L2 credentials. Wrap around components that place Polymarket orders.
58
+
59
+ ```tsx
60
+ import { PolymarketProvider } from "@liberfi.io/react-predict";
61
+
62
+ <PolymarketProvider>
63
+ <TradePanel />
64
+ </PolymarketProvider>;
65
+ ```
66
+
67
+ ---
68
+
69
+ ### Client
70
+
71
+ #### `PredictClient`
72
+
73
+ HTTP client for the prediction-server REST API.
74
+
75
+ ```ts
76
+ import { createPredictClient } from "@liberfi.io/react-predict";
77
+
78
+ const client = createPredictClient("https://prediction.example.com");
79
+ const page = await client.listEvents({ status: "open", limit: 20 });
80
+ const event = await client.getEvent("will-trump-win-2024");
81
+ ```
82
+
83
+ Key methods:
84
+
85
+ | Method | Maps to |
86
+ | ----------------------------------------- | ----------------------------------------- |
87
+ | `listEvents(params?)` | `GET /api/v1/events` |
88
+ | `getEvent(slug, source?)` | `GET /api/v1/events/:slug` |
89
+ | `getSimilarEvents(slug, source, params?)` | `GET /api/v1/events/:slug/similar` |
90
+ | `getMarket(slug, source?)` | `GET /api/v1/markets/:slug` |
91
+ | `getOrderbook(slug, source)` | `GET /api/v1/markets/:slug/orderbook` |
92
+ | `listMarketTrades(slug, params)` | `GET /api/v1/markets/:slug/trades` |
93
+ | `getPriceHistory(slug, source, range?)` | `GET /api/v1/markets/:slug/price-history` |
94
+ | `listCandlesticks(slug, params?)` | `GET /api/v1/markets/:slug/candlesticks` |
95
+ | `getPositions(source, user)` | `GET /api/v1/positions` |
96
+ | `listOrders(params)` | `GET /api/v1/orders` |
97
+ | `getOrder(id, source)` | `GET /api/v1/orders/:id` |
98
+ | `cancelOrder(id, source)` | `DELETE /api/v1/orders/:id` |
99
+ | `createPolymarketOrder(input, headers)` | `POST /api/v1/orders/polymarket` |
100
+ | `createDFlowQuote(body)` | `POST /api/v1/orders/dflow/quote` |
101
+ | `submitDFlowTransaction(body)` | `POST /api/v1/orders/dflow/submit` |
102
+ | `listTrades(params)` | `GET /api/v1/trades` |
103
+
104
+ ---
105
+
106
+ #### `PredictWsClient`
107
+
108
+ WebSocket client for real-time market data from prediction-server (`/api/v1/ws`). Supports `orderbook`, `prices`, and `trades` channels with auto-reconnect.
109
+
110
+ ```ts
111
+ import { createPredictWsClient } from "@liberfi.io/react-predict";
112
+
113
+ const wsClient = createPredictWsClient({
114
+ wsUrl: "wss://prediction.example.com/api/v1/ws",
115
+ });
116
+
117
+ // Convenience subscription — returns unsubscribe function
118
+ const unsub = wsClient.subscribePrices(["btc-above-100k"], (msg) => {
119
+ console.log(msg.data.yes_bid);
120
+ });
121
+
122
+ // Low-level multi-channel subscription
123
+ wsClient.subscribe(["orderbook", "trades"], ["btc-above-100k"]);
124
+
125
+ // Clean up
126
+ unsub();
127
+ wsClient.disconnect();
128
+ ```
129
+
130
+ Config (`PredictWsClientConfig`):
131
+
132
+ | Option | Type | Default | Description |
133
+ | ----------------------- | --------- | ------- | --------------------------------------- |
134
+ | `wsUrl` | `string` | — | WebSocket URL (required) |
135
+ | `autoConnect` | `boolean` | `true` | Connect on construction |
136
+ | `autoReconnect` | `boolean` | `true` | Reconnect on unexpected close |
137
+ | `reconnectIntervalBase` | `number` | `1000` | Base delay (ms) for exponential backoff |
138
+ | `reconnectMaxInterval` | `number` | `30000` | Max reconnect delay (ms) |
139
+ | `pingInterval` | `number` | `30000` | Application-level ping interval (ms) |
140
+
141
+ ---
142
+
143
+ ### Hooks — Predict (data queries)
144
+
145
+ All hooks require `PredictProvider` in the tree.
146
+
147
+ #### `usePredictClient()`
148
+
149
+ Returns the `PredictClient` from context.
150
+
151
+ #### `useEvents(params?, queryOptions?)`
152
+
153
+ `GET /api/v1/events` — paginated events list.
154
+
155
+ #### `useEvent({ slug, source? }, queryOptions?)`
156
+
157
+ `GET /api/v1/events/:slug` — single event.
158
+
159
+ #### `useInfiniteEvents(params, queryOptions?)`
160
+
161
+ Cursor-based infinite query for events. Use `resolveEventsParams()` to build params.
162
+
163
+ #### `useSearchEvents({ keyword, ...options }, queryOptions?)`
164
+
165
+ Infinite query with `search` parameter for full-text event search.
166
+
167
+ #### `useSimilarEvents({ slug, source, limit?, same_source? }, queryOptions?)`
168
+
169
+ `GET /api/v1/events/:slug/similar`
170
+
171
+ #### `useMarket({ slug, source? }, queryOptions?)`
172
+
173
+ `GET /api/v1/markets/:slug`
174
+
175
+ #### `useMarketHistory(markets, range?)`
176
+
177
+ Price history series for multiple markets. Uses `ChartRange` enum for range.
178
+
179
+ #### `useOrderbook({ slug, source }, queryOptions?)`
180
+
181
+ `GET /api/v1/markets/:slug/orderbook` — polls every 5 s.
182
+
183
+ #### `useMarketTrades({ slug, source, ... }, queryOptions?)`
184
+
185
+ `GET /api/v1/markets/:slug/trades`
186
+
187
+ #### `usePriceHistory({ slug, source, range? }, queryOptions?)`
188
+
189
+ `GET /api/v1/markets/:slug/price-history`
190
+
191
+ #### `useCandlesticks({ slug, interval?, limit? }, queryOptions?)`
192
+
193
+ `GET /api/v1/markets/:slug/candlesticks`
194
+
195
+ #### `usePositions({ source, user }, queryOptions?)`
196
+
197
+ `GET /api/v1/positions`
198
+
199
+ #### `useOrders(params, queryOptions?)`
200
+
201
+ `GET /api/v1/orders`
202
+
203
+ #### `useOrder({ id, source }, queryOptions?)`
204
+
205
+ `GET /api/v1/orders/:id` — polls every 1 s.
206
+
207
+ #### `useCancelOrder(mutationOptions?)`
208
+
209
+ `DELETE /api/v1/orders/:id` — invalidates `orders` queries on success.
210
+
211
+ #### `useTrades(params, queryOptions?)`
212
+
213
+ `GET /api/v1/trades` (by wallet)
214
+
215
+ #### `useDFlowQuote(params, queryOptions?)`
216
+
217
+ `POST /api/v1/orders/dflow/quote`
218
+
219
+ #### `useDFlowSubmit(mutationOptions?)`
220
+
221
+ `POST /api/v1/orders/dflow/submit` — invalidates `orders` and `positions` queries on success.
222
+
223
+ ---
224
+
225
+ ### Hooks — WebSocket (low-level subscriptions)
226
+
227
+ These hooks manage pure WS subscription state and **do not interact with React Query**. Require `PredictProvider` with a `wsClient` prop.
228
+
229
+ #### `usePredictWsClient()`
230
+
231
+ Returns `{ wsClient, wsStatus, isWsConnected }` from context.
232
+
233
+ #### `usePricesSubscription({ wsClient, slugs, enabled?, onUpdate? })`
234
+
235
+ Subscribe to price updates. Returns `{ prices: Map<slug, WsPriceEvent>, isSubscribed }`.
236
+
237
+ #### `useOrderbookSubscription({ wsClient, slug, enabled?, onUpdate? })`
238
+
239
+ Subscribe to orderbook snapshots for a single market. Returns `{ orderbook: WsOrderbookEvent | null, isSubscribed }`.
240
+
241
+ #### `useTradesSubscription({ wsClient, slug, enabled?, maxHistory?, onUpdate? })`
242
+
243
+ Subscribe to trade events. Maintains a bounded buffer (default 100). Returns `{ trades: WsTradeEvent[], isSubscribed, clearHistory }`.
244
+
245
+ ---
246
+
247
+ ### Hooks — WebSocket (high-level realtime, WS + REST merged)
248
+
249
+ These hooks combine WS subscriptions with REST queries for automatic fallback. When WS is connected, they write data directly into the React Query cache via `setQueryData` (no refetch) and disable polling. When WS is unavailable, they fall back to REST polling.
250
+
251
+ #### `useRealtimeOrderbook({ slug, source }, queryOptions?)`
252
+
253
+ Drop-in replacement for `useOrderbook`. Returns the same `UseQueryResult<Orderbook>`.
254
+
255
+ ```tsx
256
+ // Before (5s polling)
257
+ const { data } = useOrderbook({ slug, source });
258
+
259
+ // After (real-time + auto fallback)
260
+ const { data } = useRealtimeOrderbook({ slug, source });
261
+ ```
262
+
263
+ #### `useRealtimePrices({ slugs, enabled?, onUpdate? })`
264
+
265
+ Returns `{ prices: Map<slug, WsPriceEvent>, isSubscribed }`. Falls back gracefully (empty map) when no WS client.
266
+
267
+ #### `useRealtimeTrades({ slug, enabled?, maxHistory?, onUpdate?, syncToQueryCache? })`
268
+
269
+ Returns `{ trades, isSubscribed, clearHistory }`. When `syncToQueryCache` is `true` (default), new trades are also prepended into React Query market-trades cache entries.
270
+
271
+ ---
272
+
273
+ ### Hooks — Polymarket (order placement)
274
+
275
+ All hooks require both `PredictProvider` and `PolymarketProvider` in the tree.
276
+
277
+ #### `usePolymarket()`
278
+
279
+ Returns `{ credentials, isAuthenticating, authError, authenticate, clearCredentials }` from the Polymarket context.
280
+
281
+ #### `useCreatePolymarketOrder(mutationOptions?)`
282
+
283
+ Full Polymarket order flow — authenticate if needed, sign EIP-712 order on CTF Exchange, build HMAC L2 headers, submit via prediction-server proxy.
284
+
285
+ ```tsx
286
+ const { mutateAsync: createOrder, isPending } = useCreatePolymarketOrder({
287
+ onError: (err) => toast.error(err.message),
288
+ });
289
+
290
+ await createOrder({
291
+ signer: myPolymarketSigner,
292
+ input: {
293
+ tokenId: "123456789",
294
+ price: 0.55,
295
+ size: 10,
296
+ side: "BUY",
297
+ tickSize: "0.01",
298
+ negRisk: false,
299
+ },
300
+ });
301
+ ```
302
+
303
+ ---
304
+
305
+ ### Types — Key Interfaces
306
+
307
+ | Type | Description |
308
+ | ----------------------- | ----------------------------------------------------------------------- |
309
+ | `PolymarketSigner` | IoC interface for EIP-712 signing (implement in the consumer app) |
310
+ | `PolymarketCredentials` | In-memory L2 `apiKey` / `secret` / `passphrase` |
311
+ | `CreateOrderInput` | Parameters for a Polymarket limit order |
312
+ | `PredictEvent` | Prediction event aggregate |
313
+ | `PredictMarket` | Tradeable market within an event |
314
+ | `PredictOrder` | User order record |
315
+ | `PredictTrade` | Trade record |
316
+ | `PredictPosition` | User position |
317
+ | `ProviderSource` | `"dflow"` \| `"polymarket"` |
318
+ | `OrderSide` | `"BUY"` \| `"SELL"` |
319
+ | `PredictPage<T>` | Paginated response wrapper |
320
+ | `WsChannel` | `"orderbook"` \| `"prices"` \| `"trades"` |
321
+ | `WsConnectionStatus` | `"connecting"` \| `"connected"` \| `"disconnected"` \| `"reconnecting"` |
322
+ | `WsOrderbookEvent` | Full orderbook snapshot from WS |
323
+ | `WsPriceEvent` | Price update from WS |
324
+ | `WsTradeEvent` | Single trade from WS |
325
+ | `WsDataMessage<T>` | Envelope: `{ channel, market_slug, data: T, ts }` |
326
+
327
+ ---
328
+
329
+ ### Server-safe exports (`/server`)
330
+
331
+ Import from `@liberfi.io/react-predict/server` in Server Components and route handlers:
332
+
333
+ ```ts
334
+ import {
335
+ resolveEventsParams,
336
+ infiniteEventsQueryKey,
337
+ fetchEventsPage,
338
+ fetchEvent,
339
+ createPredictClient,
340
+ } from "@liberfi.io/react-predict/server";
341
+
342
+ // Prefetch on the server
343
+ const client = createPredictClient(process.env.PREDICT_API_URL);
344
+ const params = resolveEventsParams({ status: "open" });
345
+ await queryClient.prefetchInfiniteQuery({
346
+ queryKey: infiniteEventsQueryKey(params),
347
+ queryFn: ({ pageParam }) =>
348
+ fetchEventsPage(client, { ...params, cursor: pageParam }),
349
+ initialPageParam: undefined,
350
+ });
351
+ ```
352
+
353
+ ---
354
+
355
+ ## Usage Example
356
+
357
+ ```tsx
358
+ import {
359
+ createPredictClient,
360
+ PredictProvider,
361
+ PolymarketProvider,
362
+ useInfiniteEvents,
363
+ resolveEventsParams,
364
+ useCreatePolymarketOrder,
365
+ } from "@liberfi.io/react-predict";
366
+
367
+ const client = createPredictClient("https://prediction.example.com");
368
+
369
+ function App() {
370
+ return (
371
+ <PredictProvider client={client}>
372
+ <PolymarketProvider>
373
+ <EventList />
374
+ <TradePanel />
375
+ </PolymarketProvider>
376
+ </PredictProvider>
377
+ );
378
+ }
379
+
380
+ function EventList() {
381
+ const params = resolveEventsParams({ status: "open", limit: 20 });
382
+ const { data, hasNextPage, fetchNextPage } = useInfiniteEvents(params);
383
+ const events = data?.pages.flatMap((p) => p.items) ?? [];
384
+ return <ul>{events.map((e) => <li key={e.slug}>{e.title}</li>)}</ul>;
385
+ }
386
+
387
+ function TradePanel() {
388
+ const { mutateAsync: createOrder } = useCreatePolymarketOrder();
389
+ // implement signer using EvmWalletAdapter.getEip1193Provider()
390
+ return <button onClick={() => createOrder({ signer, input: { ... } })}>Trade</button>;
391
+ }
392
+ ```
393
+
394
+ ### WebSocket Real-time Example
395
+
396
+ ```tsx
397
+ import {
398
+ createPredictClient,
399
+ createPredictWsClient,
400
+ PredictProvider,
401
+ useRealtimeOrderbook,
402
+ useRealtimePrices,
403
+ } from "@liberfi.io/react-predict";
404
+
405
+ const client = createPredictClient("https://prediction.example.com");
406
+ const wsClient = createPredictWsClient({
407
+ wsUrl: "wss://prediction.example.com/api/v1/ws",
408
+ });
409
+
410
+ function App() {
411
+ return (
412
+ <PredictProvider client={client} wsClient={wsClient}>
413
+ <MarketView slug="btc-above-100k" source="dflow" />
414
+ </PredictProvider>
415
+ );
416
+ }
417
+
418
+ function MarketView({
419
+ slug,
420
+ source,
421
+ }: {
422
+ slug: string;
423
+ source: "dflow" | "polymarket";
424
+ }) {
425
+ // Real-time orderbook — falls back to 5s polling when WS unavailable
426
+ const { data: orderbook } = useRealtimeOrderbook({ slug, source });
427
+
428
+ // Real-time prices
429
+ const { prices } = useRealtimePrices({ slugs: [slug] });
430
+ const price = prices.get(slug);
431
+
432
+ return (
433
+ <div>
434
+ <p>Yes bid: {price?.yes_bid ?? "—"}</p>
435
+ <p>Bids: {orderbook?.bids.length ?? 0} levels</p>
436
+ </div>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ## Future Improvements
442
+
443
+ - Server-side Polymarket credential management via `prediction-server` (requires JWT auth in the server).
444
+ - Cancel-order support for Polymarket (currently only DFlow cancel is available server-side).
445
+ - `useInfiniteOrders` hook for cursor-based order history pagination.
446
+ - Retry logic for failed L2 authentication (e.g. expired credentials).
447
+ - Migrate `ui-predict`'s `DflowPredictWsClient` to use `PredictWsClient` from this package (unified protocol via prediction-server instead of direct DFlow WS).