---
name: indexer-client-integration
description: Use when integrating `@provex/indexer-client` — when querying Provex deposits/intents/events from the indexer via React hooks (useDeposits, useIntents, useOrderbook, etc.), when running headless indexer queries from a Node script, when wiring a custom indexer URL via env vars or createProvexIndexerClient, when handling indexer lag in the UI, or when pointing the client at a self-hosted indexer with a different schema. Covers the createPonderAdapter vs createProvexIndexerClient distinction, the bundled-schema design (and when to override it), the env-var defaults, and the indexer-lag null-handling pattern that's non-negotiable to avoid false-positive "expired" UI.
---

# `@provex/indexer-client` Integration

Typed client and React hooks for the Provex indexer. The indexer is a Ponder service that indexes V3 Escrow and Orchestrator events. This package bundles the indexer's schema definitions inline so you get type safety end-to-end without a separate schema dependency.

## Two entry points, one client

```ts
// Highest level — React hook adapter, defaults to hosted Provex indexer
import { createPonderAdapter } from '@provex/indexer-client'
const indexer = createPonderAdapter()

// Lower level — explicit URL and schema override
import { createProvexIndexerClient } from '@provex/indexer-client'
const client = createProvexIndexerClient({
  url: 'https://my-indexer.example.com',
  // schema: customSchema, // optional
})
```

Use `createPonderAdapter()` for normal React app integration. Use `createProvexIndexerClient` when you need a non-default URL/schema or you're running outside React (Node script, server route).

## Environment variables — read first

When `createPonderAdapter()` runs without explicit config, it reads these (Vite-style names):

| Variable | Default | Purpose |
|---|---|---|
| `VITE_INDEXER_URL` | `https://indexer.provex.com` | Main indexer base URL |
| `VITE_TOKEN_INDEXER_URL` | `https://token.provex.com` | Token-metadata indexer base URL |

For non-Vite apps, set the equivalent env var your bundler exposes, or pass `{ url }` to `createProvexIndexerClient` explicitly. The defaults point at Provex's hosted production indexers — fine for most consumers.

## Mount order

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ProvexProvider } from '@provex/react'
import { createPonderAdapter } from '@provex/indexer-client'

const queryClient = new QueryClient()
const indexer = createPonderAdapter()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <ProvexProvider config={...} indexer={indexer}>
        <YourApp />
      </ProvexProvider>
    </QueryClientProvider>
  )
}
```

The hooks live under `ProvexProvider`. Without it they throw "no client found." If you have a separate `QueryClient`, pass it via `ProvexProvider`'s `queryClient` prop.

## Canonical hooks

```tsx
import { useDeposits, useDeposit, useIntent, useOrderbook } from '@provex/indexer-client'

// Browse available deposits for a token + payment method
const { data: deposits, isLoading } = useDeposits({
  token: '0x...',
  paymentMethod: 'venmo',
})

// One deposit by escrow + localId
const { data: deposit } = useDeposit({ escrow: '0x...', localId: 1n })

// One intent by hash
const { data: intent } = useIntent({ intentHash: '0xabc...' })

// Full orderbook view (sorted, filtered)
const { data: orderbook } = useOrderbook({ ... })
```

Most hooks accept `chainId` to target a specific deployment; default behavior is the chain configured on `ProvexProvider`.

## Indexer-lag null-handling (mandatory)

The indexer trails on-chain inclusion by 2–10 seconds typically, longer during sync catchup. Hooks return `null` for entities that exist on-chain but haven't been indexed yet. **Never conflate "not indexed yet" with "expired" or "absent."**

```tsx
// ❌ wrong — treats indexer lag as "expired"
const isExpired = intent.expiryTime <= Date.now()  // throws on null intent

// ✅ correct — distinguish lag from terminal state
if (intent === null) return <Loading />
const isExpired = intent.expiryTime != null && intent.expiryTime <= Date.now()
```

Timing helpers in `@provex/utils/intent/timing` thread `null` through downstream math safely. Use them for any UI that consumes indexer-sourced expiry, signal time, or fulfillment time fields.

## Inline schema — when to override

The package bundles two schemas via tsup:
- The main Provex indexer schema (deposits, intents, events)
- The token-metadata indexer schema

Both are workspace-private in the monorepo but inlined at build time. Consumers get one tarball, one type surface.

Override the schema only if you run a custom indexer with a divergent shape:

```ts
import { createProvexIndexerClient } from '@provex/indexer-client'
import { mySchema } from './my-custom-schema'

const client = createProvexIndexerClient({
  url: 'https://my-indexer.example.com',
  schema: mySchema,
})
```

Type inference flows from the schema you pass — pass a different schema, get different `data` types out of the hooks. This is intentional. Don't override the schema "just to keep it neat" if your indexer matches Provex's shape.

## Sizing — the elephant in the bundle

`@provex/indexer-client` ships a `pg_query` WASM chunk (~7.8 MB) for query parsing. Tree-shaking won't eliminate it because the bundler can't statically prove which queries you'll run. If your app is bundle-size-sensitive:

- Lazy-load the indexer client (`React.lazy` / dynamic `import()`) so it only ships when the user actually opens a buy flow.
- Or build with code-splitting so the WASM chunk lands in a separate JS file with its own cache lifetime.

For most apps the cache benefit outweighs the initial download.

## When NOT to import from this package

- **You're hitting the indexer from Node** without React → use `createProvexIndexerClient` and skip `createPonderAdapter`. The Ponder adapter is React-Query-aware; the plain client is not.
- **You only need contract reads** (current deposit state from the chain) → use `@provex/abis` with viem `readContract` and skip the indexer entirely. Faster for single reads, no lag.
- **You want raw GraphQL** → the underlying Ponder instance exposes GraphQL at `${url}/graphql`. Hit it directly with `fetch` if you want bespoke queries the typed hooks don't cover. Keep an eye on schema drift.
