npm.io
0.3.59 • Published 16h ago

@liberfi.io/react-predict

Licence
Version
0.3.59
Deps
2
Size
1.4 MB
Vulns
0
Weekly
1.6K

@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)PolymarketSigner is an interface injected by the consumer. No dependency on @liberfi.io/wallet-connector or any specific wallet library.
  • No @polymarket/clob-client dependency — 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 pointsindex.ts for React consumers; server.ts for 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-predict

Peer dependencies the consumer must provide:

pnpm add react react-dom @tanstack/react-query

API 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 — a PredictClient instance.
  • 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
executePolymarketOrder(req) POST /api/v1/orders/polymarket/execute
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 }

Hooks — Matches (cross-platform pairs, v1.1)

/api/v1/matches/markets exposes pairs of equivalent markets across providers (e.g. Polymarket vs Kalshi). v1.1 reframes the surface as a pure information layer — the response carries an absolute YES-side gap (no fees, no arb framing) plus a signal_tag describing the quality of the pair.

useInfiniteMatchMarkets(params, queryOptions?)

Cursor-based infinite query for matched market pairs. Accepts MatchMarketParams (sort/filter/pagination + the new v1.1 signal filter, which is a comma-separated list of SignalTag values).

const { data, fetchNextPage } = useInfiniteMatchMarkets({
  sort_by: "spread",
  sort_asc: false,
  status: "active",
  signal: "liquid_gap,active_gap",
  limit: 20,
});
const matches = data?.pages.flatMap((p) => p.items) ?? [];
Key Types
Type Description
MatchMarketFlat A flattened pair. v1.1 fields: legs: MatchLeg[], signal_tag?: SignalTag, age_seconds?: number, plus spread_percent (now interpreted as the absolute YES-side gap, not arbitrage profit).
MatchLeg Single market within a pair: source, event_title, event_slug, market_id, best_price, vol_24h_usd, external_url, age_seconds.
SignalTag "liquid_gap" | "active_gap" | "stale_gap" | "stale_data". Replaces the deprecated MatchConfidenceTier.

The legacy market_a / market_b / arb_profit fields on MatchMarketFlat are still emitted for one release window but are marked @deprecated. Prefer reading from legs[0] / legs[1]. arb_profit is now always 0 — the matcher no longer applies fees.


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).
  • useInfiniteOrders hook for cursor-based order history pagination.
  • Retry logic for failed L2 authentication (e.g. expired credentials).
  • Migrate ui-predict's DflowPredictWsClient to use PredictWsClient from this package (unified protocol via prediction-server instead of direct DFlow WS).