npm package: @liberfi.io/react-predict
Description: React hooks and client for prediction markets (prediction-server backend), including Polymarket and DFlow order flows
Latest version: 0.1.63

# @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 points** — `index.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

```bash
pnpm add @liberfi.io/react-predict
```

Peer dependencies the consumer must provide:

```bash
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.

```tsx
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.

```tsx
import { PolymarketProvider } from "@liberfi.io/react-predict";

<PolymarketProvider>
  <TradePanel />
</PolymarketProvider>;
```

---

### Client

#### `PredictClient`

HTTP client for the prediction-server REST API.

```ts
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.

```ts
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>`.

```tsx
// 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.

```tsx
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:

```ts
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

```tsx
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

```tsx
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).