@liberfi.io/react-predict
v0.1.4
Published
React hooks and client for prediction markets (prediction-server backend), including Polymarket and DFlow order flows
Readme
@liberfi.io/react-predict
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.
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.
Design Philosophy
- Wallet-agnostic (IoC) —
PolymarketSigneris an interface injected by the consumer. No dependency on@liberfi.io/wallet-connectoror any specific wallet library. - No
@polymarket/clob-clientdependency — EIP-712 signing and HMAC-SHA256 are implemented with the Web Crypto API to avoid ethers v5/v6 conflicts. - 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. - Dual entry points —
index.tsfor React consumers;server.tsfor SSR-safe usage in Next.js Server Components and route handlers (no React imports). - 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.
Installation
pnpm add @liberfi.io/react-predictPeer dependencies the consumer must provide:
pnpm add react react-dom @tanstack/react-queryAPI Reference
Providers
PredictProvider
Provides the PredictClient instance via React context. Place at the application root where prediction hooks are used.
import {
createPredictClient,
PredictProvider,
} from "@liberfi.io/react-predict";
const client = createPredictClient("https://prediction.example.com");
<PredictProvider client={client}>
<App />
</PredictProvider>;Props:
client: PredictClient— aPredictClientinstance.wsClient?: PredictWsClient | null— optional WebSocket client for real-time data.
PolymarketProvider
Manages in-memory Polymarket L2 credentials. Wrap around components that place Polymarket orders.
import { PolymarketProvider } from "@liberfi.io/react-predict";
<PolymarketProvider>
<TradePanel />
</PolymarketProvider>;Client
PredictClient
HTTP client for the prediction-server REST API.
import { createPredictClient } from "@liberfi.io/react-predict";
const client = createPredictClient("https://prediction.example.com");
const page = await client.listEvents({ status: "open", limit: 20 });
const event = await client.getEvent("will-trump-win-2024");Key methods:
| Method | Maps to |
| ----------------------------------------- | ----------------------------------------- |
| listEvents(params?) | GET /api/v1/events |
| getEvent(slug, source?) | GET /api/v1/events/:slug |
| getSimilarEvents(slug, source, params?) | GET /api/v1/events/:slug/similar |
| getMarket(slug, source?) | GET /api/v1/markets/:slug |
| getOrderbook(slug, source) | GET /api/v1/markets/:slug/orderbook |
| listMarketTrades(slug, params) | GET /api/v1/markets/:slug/trades |
| getPriceHistory(slug, source, range?) | GET /api/v1/markets/:slug/price-history |
| listCandlesticks(slug, params?) | GET /api/v1/markets/:slug/candlesticks |
| getPositions(source, user) | GET /api/v1/positions |
| listOrders(params) | GET /api/v1/orders |
| getOrder(id, source) | GET /api/v1/orders/:id |
| cancelOrder(id, source) | DELETE /api/v1/orders/:id |
| createPolymarketOrder(input, headers) | POST /api/v1/orders/polymarket |
| createDFlowQuote(body) | POST /api/v1/orders/dflow/quote |
| submitDFlowTransaction(body) | POST /api/v1/orders/dflow/submit |
| listTrades(params) | GET /api/v1/trades |
PredictWsClient
WebSocket client for real-time market data from prediction-server (/api/v1/ws). Supports orderbook, prices, and trades channels with auto-reconnect.
import { createPredictWsClient } from "@liberfi.io/react-predict";
const wsClient = createPredictWsClient({
wsUrl: "wss://prediction.example.com/api/v1/ws",
});
// Convenience subscription — returns unsubscribe function
const unsub = wsClient.subscribePrices(["btc-above-100k"], (msg) => {
console.log(msg.data.yes_bid);
});
// Low-level multi-channel subscription
wsClient.subscribe(["orderbook", "trades"], ["btc-above-100k"]);
// Clean up
unsub();
wsClient.disconnect();Config (PredictWsClientConfig):
| Option | Type | Default | Description |
| ----------------------- | --------- | ------- | --------------------------------------- |
| wsUrl | string | — | WebSocket URL (required) |
| autoConnect | boolean | true | Connect on construction |
| autoReconnect | boolean | true | Reconnect on unexpected close |
| reconnectIntervalBase | number | 1000 | Base delay (ms) for exponential backoff |
| reconnectMaxInterval | number | 30000 | Max reconnect delay (ms) |
| pingInterval | number | 30000 | Application-level ping interval (ms) |
Hooks — Predict (data queries)
All hooks require PredictProvider in the tree.
usePredictClient()
Returns the PredictClient from context.
useEvents(params?, queryOptions?)
GET /api/v1/events — paginated events list.
useEvent({ slug, source? }, queryOptions?)
GET /api/v1/events/:slug — single event.
useInfiniteEvents(params, queryOptions?)
Cursor-based infinite query for events. Use resolveEventsParams() to build params.
useSearchEvents({ keyword, ...options }, queryOptions?)
Infinite query with search parameter for full-text event search.
useSimilarEvents({ slug, source, limit?, same_source? }, queryOptions?)
GET /api/v1/events/:slug/similar
useMarket({ slug, source? }, queryOptions?)
GET /api/v1/markets/:slug
useMarketHistory(markets, range?)
Price history series for multiple markets. Uses ChartRange enum for range.
useOrderbook({ slug, source }, queryOptions?)
GET /api/v1/markets/:slug/orderbook — polls every 5 s.
useMarketTrades({ slug, source, ... }, queryOptions?)
GET /api/v1/markets/:slug/trades
usePriceHistory({ slug, source, range? }, queryOptions?)
GET /api/v1/markets/:slug/price-history
useCandlesticks({ slug, interval?, limit? }, queryOptions?)
GET /api/v1/markets/:slug/candlesticks
usePositions({ source, user }, queryOptions?)
GET /api/v1/positions
useOrders(params, queryOptions?)
GET /api/v1/orders
useOrder({ id, source }, queryOptions?)
GET /api/v1/orders/:id — polls every 1 s.
useCancelOrder(mutationOptions?)
DELETE /api/v1/orders/:id — invalidates orders queries on success.
useTrades(params, queryOptions?)
GET /api/v1/trades (by wallet)
useDFlowQuote(params, queryOptions?)
POST /api/v1/orders/dflow/quote
useDFlowSubmit(mutationOptions?)
POST /api/v1/orders/dflow/submit — invalidates orders and positions queries on success.
Hooks — WebSocket (low-level subscriptions)
These hooks manage pure WS subscription state and do not interact with React Query. Require PredictProvider with a wsClient prop.
usePredictWsClient()
Returns { wsClient, wsStatus, isWsConnected } from context.
usePricesSubscription({ wsClient, slugs, enabled?, onUpdate? })
Subscribe to price updates. Returns { prices: Map<slug, WsPriceEvent>, isSubscribed }.
useOrderbookSubscription({ wsClient, slug, enabled?, onUpdate? })
Subscribe to orderbook snapshots for a single market. Returns { orderbook: WsOrderbookEvent | null, isSubscribed }.
useTradesSubscription({ wsClient, slug, enabled?, maxHistory?, onUpdate? })
Subscribe to trade events. Maintains a bounded buffer (default 100). Returns { trades: WsTradeEvent[], isSubscribed, clearHistory }.
Hooks — WebSocket (high-level realtime, WS + REST merged)
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.
useRealtimeOrderbook({ slug, source }, queryOptions?)
Drop-in replacement for useOrderbook. Returns the same UseQueryResult<Orderbook>.
// Before (5s polling)
const { data } = useOrderbook({ slug, source });
// After (real-time + auto fallback)
const { data } = useRealtimeOrderbook({ slug, source });useRealtimePrices({ slugs, enabled?, onUpdate? })
Returns { prices: Map<slug, WsPriceEvent>, isSubscribed }. Falls back gracefully (empty map) when no WS client.
useRealtimeTrades({ slug, enabled?, maxHistory?, onUpdate?, syncToQueryCache? })
Returns { trades, isSubscribed, clearHistory }. When syncToQueryCache is true (default), new trades are also prepended into React Query market-trades cache entries.
Hooks — Polymarket (order placement)
All hooks require both PredictProvider and PolymarketProvider in the tree.
usePolymarket()
Returns { credentials, isAuthenticating, authError, authenticate, clearCredentials } from the Polymarket context.
useCreatePolymarketOrder(mutationOptions?)
Full Polymarket order flow — authenticate if needed, sign EIP-712 order on CTF Exchange, build HMAC L2 headers, submit via prediction-server proxy.
const { mutateAsync: createOrder, isPending } = useCreatePolymarketOrder({
onError: (err) => toast.error(err.message),
});
await createOrder({
signer: myPolymarketSigner,
input: {
tokenId: "123456789",
price: 0.55,
size: 10,
side: "BUY",
tickSize: "0.01",
negRisk: false,
},
});Types — Key Interfaces
| Type | Description |
| ----------------------- | ----------------------------------------------------------------------- |
| PolymarketSigner | IoC interface for EIP-712 signing (implement in the consumer app) |
| PolymarketCredentials | In-memory L2 apiKey / secret / passphrase |
| CreateOrderInput | Parameters for a Polymarket limit order |
| PredictEvent | Prediction event aggregate |
| PredictMarket | Tradeable market within an event |
| PredictOrder | User order record |
| PredictTrade | Trade record |
| PredictPosition | User position |
| ProviderSource | "dflow" | "polymarket" |
| OrderSide | "BUY" | "SELL" |
| PredictPage<T> | Paginated response wrapper |
| WsChannel | "orderbook" | "prices" | "trades" |
| WsConnectionStatus | "connecting" | "connected" | "disconnected" | "reconnecting" |
| WsOrderbookEvent | Full orderbook snapshot from WS |
| WsPriceEvent | Price update from WS |
| WsTradeEvent | Single trade from WS |
| WsDataMessage<T> | Envelope: { channel, market_slug, data: T, ts } |
Server-safe exports (/server)
Import from @liberfi.io/react-predict/server in Server Components and route handlers:
import {
resolveEventsParams,
infiniteEventsQueryKey,
fetchEventsPage,
fetchEvent,
createPredictClient,
} from "@liberfi.io/react-predict/server";
// Prefetch on the server
const client = createPredictClient(process.env.PREDICT_API_URL);
const params = resolveEventsParams({ status: "open" });
await queryClient.prefetchInfiniteQuery({
queryKey: infiniteEventsQueryKey(params),
queryFn: ({ pageParam }) =>
fetchEventsPage(client, { ...params, cursor: pageParam }),
initialPageParam: undefined,
});Usage Example
import {
createPredictClient,
PredictProvider,
PolymarketProvider,
useInfiniteEvents,
resolveEventsParams,
useCreatePolymarketOrder,
} from "@liberfi.io/react-predict";
const client = createPredictClient("https://prediction.example.com");
function App() {
return (
<PredictProvider client={client}>
<PolymarketProvider>
<EventList />
<TradePanel />
</PolymarketProvider>
</PredictProvider>
);
}
function EventList() {
const params = resolveEventsParams({ status: "open", limit: 20 });
const { data, hasNextPage, fetchNextPage } = useInfiniteEvents(params);
const events = data?.pages.flatMap((p) => p.items) ?? [];
return <ul>{events.map((e) => <li key={e.slug}>{e.title}</li>)}</ul>;
}
function TradePanel() {
const { mutateAsync: createOrder } = useCreatePolymarketOrder();
// implement signer using EvmWalletAdapter.getEip1193Provider()
return <button onClick={() => createOrder({ signer, input: { ... } })}>Trade</button>;
}WebSocket Real-time Example
import {
createPredictClient,
createPredictWsClient,
PredictProvider,
useRealtimeOrderbook,
useRealtimePrices,
} from "@liberfi.io/react-predict";
const client = createPredictClient("https://prediction.example.com");
const wsClient = createPredictWsClient({
wsUrl: "wss://prediction.example.com/api/v1/ws",
});
function App() {
return (
<PredictProvider client={client} wsClient={wsClient}>
<MarketView slug="btc-above-100k" source="dflow" />
</PredictProvider>
);
}
function MarketView({
slug,
source,
}: {
slug: string;
source: "dflow" | "polymarket";
}) {
// Real-time orderbook — falls back to 5s polling when WS unavailable
const { data: orderbook } = useRealtimeOrderbook({ slug, source });
// Real-time prices
const { prices } = useRealtimePrices({ slugs: [slug] });
const price = prices.get(slug);
return (
<div>
<p>Yes bid: {price?.yes_bid ?? "—"}</p>
<p>Bids: {orderbook?.bids.length ?? 0} levels</p>
</div>
);
}Future Improvements
- Server-side Polymarket credential management via
prediction-server(requires JWT auth in the server). - Cancel-order support for Polymarket (currently only DFlow cancel is available server-side).
useInfiniteOrdershook for cursor-based order history pagination.- Retry logic for failed L2 authentication (e.g. expired credentials).
- Migrate
ui-predict'sDflowPredictWsClientto usePredictWsClientfrom this package (unified protocol via prediction-server instead of direct DFlow WS).
