> ## Documentation Index
> Fetch the complete documentation index at: https://cyphers-3138df4b.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# React Hooks Reference for Cyphers Prediction Markets SDK

> Complete reference for all React hooks in @cypher-zk/sdk/react - query hooks, mutation hooks, event subscriptions, and query-key factories.

`@cypher-zk/sdk/react` exports a `CypherProvider` component and fourteen hooks that cover every read and write operation in the protocol. The query hooks are built on TanStack Query v5 and cache results automatically. The mutation hooks wrap the high-level `client.actions.*` methods and invalidate related caches on success so your UI stays in sync without any manual work.

<Note>
  All hooks must be used inside a `<CypherProvider>`. Calling a hook outside the provider throws immediately with a descriptive error.
</Note>

## Provider setup

Construct a `CypherClient` once - typically in a top-level component or module - and pass it to `CypherProvider`. Wrap `CypherProvider` inside your TanStack `QueryClientProvider`:

```typescript theme={null}
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { CypherClient } from '@cypher-zk/sdk';
import { CypherProvider } from '@cypher-zk/sdk/react';
import { useWallet } from '@solana/wallet-adapter-react';
import { Connection } from '@solana/web3.js';

const queryClient = new QueryClient();

function Providers({ children }: { children: React.ReactNode }) {
  const wallet = useWallet();

  const client = useMemo(() => {
    if (!wallet.publicKey) return null;
    return new CypherClient({
      connection: new Connection(process.env.NEXT_PUBLIC_RPC_URL!, 'confirmed'),
      wallet: {
        publicKey: wallet.publicKey,
        signTransaction: wallet.signTransaction!,
        signAllTransactions: wallet.signAllTransactions!,
      },
      cluster: 'mainnet',
    });
  }, [wallet.publicKey]);

  if (!client) return <>{children}</>;

  return (
    <QueryClientProvider client={queryClient}>
      <CypherProvider client={client}>
        {children}
      </CypherProvider>
    </QueryClientProvider>
  );
}
```

### Accessing the client from a hook

`useCypherClient()` returns the `CypherClient` instance from context. Use it when you need direct client access inside a component, for example to build raw instructions.

```typescript theme={null}
import { useCypherClient } from '@cypher-zk/sdk/react';

function MarketDebug() {
  const client = useCypherClient();
  // client.markets.all(), client.actions.placeBet(...), etc.
}
```

***

## Query hooks

Query hooks fetch on-chain state and cache it via TanStack Query. Each hook accepts an optional second `opts` argument that maps to any standard `UseQueryOptions` field - `staleTime`, `refetchInterval`, `enabled`, etc.

Default cache stale times are tuned to Cyphers' update cadence:

| Hook                     | Default `staleTime`                                     |
| ------------------------ | ------------------------------------------------------- |
| `useGlobalState()`       | 30 s - protocol config changes only on admin operations |
| `useMarket(id)`          | 10 s - updates on bet, resolve, and claim               |
| `useMarkets()`           | 10 s                                                    |
| `useUserPositions(user)` | 5 s - updates on every new bet                          |

Override per call:

```typescript theme={null}
// Never go stale - invalidate manually when you know something changed
const { data: config } = useGlobalState({ staleTime: Infinity });

// Poll aggressively during a live event
const { data: market } = useMarket(id, { refetchInterval: 1_000 });
```

***

### `useGlobalState`

Fetches the protocol `GlobalState` account - fee rates, accepted mint, market counter, and admin key.

```typescript theme={null}
import { useGlobalState } from '@cypher-zk/sdk/react';

function FeeDisplay() {
  const { data, isLoading, error } = useGlobalState();

  if (isLoading) return <Spinner />;
  if (error)    return <ErrorBanner error={error} />;

  return <p>Protocol fee: {data.protocolFeeRate} bps</p>;
}
```

<ResponseField name="returns" type="UseQueryResult<GlobalStateAccount>">
  TanStack Query result containing the deserialized `GlobalStateAccount`.
</ResponseField>

***

### `useMarket`

Fetches a single market by its numeric ID.

<ParamField path="id" type="bigint | number" required>
  The market ID. Corresponds to `MarketAccount.marketId` on-chain.
</ParamField>

```typescript theme={null}
import { useMarket } from '@cypher-zk/sdk/react';

function MarketDetail({ marketId }: { marketId: bigint }) {
  const { data: market } = useMarket(marketId);

  return <h1>{market?.inlineQuestion ?? 'Loading…'}</h1>;
}
```

<ResponseField name="returns" type="UseQueryResult<MarketAccount | null>">
  `null` when the market PDA does not exist on-chain.
</ResponseField>

***

### `useMarkets`

Fetches and filters all markets. Returns the full list when called with an empty object.

<ParamField path="filter" type="UseMarketsFilter">
  An optional filter object. All fields are optional and combined with AND logic.
</ParamField>

<ParamField path="filter.creator" type="PublicKey">
  Limit results to markets created by this wallet.
</ParamField>

<ParamField path="filter.state" type="number">
  Limit results to markets in this on-chain state (`MarketState.Active` = `0`, etc.).
</ParamField>

```typescript theme={null}
import { useMarkets } from '@cypher-zk/sdk/react';
import { MarketState } from '@cypher-zk/sdk';

function ActiveMarketList() {
  const { data: markets = [] } = useMarkets({ state: MarketState.Active });

  return (
    <ul>
      {markets.map(({ publicKey, account }) => (
        <li key={publicKey.toBase58()}>#{account.marketId.toString()}</li>
      ))}
    </ul>
  );
}
```

<ResponseField name="returns" type="UseQueryResult<{ publicKey: PublicKey; account: MarketAccount }[]>">
  Array of market entries, each with its on-chain address and decoded account data.
</ResponseField>

***

### `useUserPositions`

Fetches every position held by a user across all markets.

<ParamField path="user" type="PublicKey" required>
  The wallet address to look up.
</ParamField>

```typescript theme={null}
import { useUserPositions } from '@cypher-zk/sdk/react';
import { useWallet } from '@solana/wallet-adapter-react';

function MyPositions() {
  const { publicKey } = useWallet();
  const { data: positions = [] } = useUserPositions(publicKey ?? undefined);

  return <p>{positions.length} open position(s)</p>;
}
```

<ResponseField name="returns" type="UseQueryResult<{ publicKey: PublicKey; account: EncryptedPositionAccount }[]>">
  All positions the user holds. Empty array when the user has no bets.
</ResponseField>

***

### `usePosition`

Fetches a single position for a specific `(market, user, betIndex)` tuple.

<ParamField path="market" type="PublicKey" required>
  The market PDA.
</ParamField>

<ParamField path="user" type="PublicKey" required>
  The user's wallet address.
</ParamField>

<ParamField path="betIndex" type="bigint">
  Defaults to `0n`. Increment to fetch additional bets the user placed on the same market.
</ParamField>

```typescript theme={null}
import { usePosition } from '@cypher-zk/sdk/react';

function PositionCard({ market, user }: { market: PublicKey; user: PublicKey }) {
  const { data: position } = usePosition(market, user);

  if (!position) return <p>No position found.</p>;

  return <p>Claimed: {position.claimed ? 'Yes' : 'No'}</p>;
}
```

<ResponseField name="returns" type="UseQueryResult<EncryptedPositionAccount | null>">
  `null` when no position PDA exists for the given tuple.
</ResponseField>

***

### `useMarketEvents`

Subscribes to the protocol's real-time event stream over WebSocket and returns an accumulating array of `CypherEvent` objects.

<Warning>
  `useMarketEvents()` requires an active WebSocket connection. It will not work with HTTP-only RPC providers. Ensure your `Connection` is initialized with a `wss://` endpoint or a provider that supports WebSocket subscriptions.
</Warning>

```typescript theme={null}
import { useMarketEvents } from '@cypher-zk/sdk/react';

function EventFeed() {
  const events = useMarketEvents();

  return (
    <ul>
      {events.map((ev, i) => (
        <li key={i}>{ev.name} - market {ev.data.market?.toBase58()}</li>
      ))}
    </ul>
  );
}
```

<ResponseField name="returns" type="CypherEvent[]">
  Accumulated array of parsed events since the component mounted. Events are prepended (newest first).
</ResponseField>

***

## Mutation hooks

Mutation hooks wrap `client.actions.*` and return a standard TanStack Query `UseMutationResult`. Call `.mutate(variables)` or `.mutateAsync(variables)` to trigger the transaction.

<Note>
  Every mutation hook automatically invalidates the relevant query caches on success. You do not need to call `queryClient.invalidateQueries` manually unless you bypass these hooks and send instructions directly.
</Note>

The hooks that drive Arcium MPC computations (`usePlaceBet`, `useResolveMarket`, `useClaimPayout`, `useClaimRefund`) accept an optional `onProgress` callback that fires at each stage of the transaction lifecycle:

```typescript theme={null}
type ActionStage =
  | 'validating'
  | 'fetching-state'
  | 'encrypting'        // placeBet only
  | 'submitting'
  | 'awaiting-callback' // Arcium MPC actions only
  | 'refetching'
  | 'done';
```

***

### `usePlaceBet`

Places a private bet on a market. The bet amount and side are Rescue-encrypted before the transaction is submitted.

**Invalidates:** `marketKeys.detail(market)`, `positionKeys.byUser(user)`

```typescript theme={null}
import { usePlaceBet } from '@cypher-zk/sdk/react';

function BetButton({ market, side, amount }) {
  const placeBet = usePlaceBet();

  return (
    <button
      onClick={() =>
        placeBet.mutate({
          market,
          side,        // 0 = No / first outcome, 1 = Yes / second outcome, etc.
          amount,      // bigint, USDC lamports (min MIN_BET_USDC = 1_000_000n)
          onProgress: (ev) => console.log(ev.stage, ev.message),
        })
      }
      disabled={placeBet.isPending}
    >
      {placeBet.isPending ? 'Placing…' : 'Place Bet'}
    </button>
  );
}
```

***

### `useCreateMarket`

Creates a new YesNo prediction market and locks the creator bond.

**Invalidates:** `marketKeys.all()`

```typescript theme={null}
import { useCreateMarket } from '@cypher-zk/sdk/react';

function CreateMarketForm() {
  const createMarket = useCreateMarket();

  const handleSubmit = () => {
    createMarket.mutate({
      question: 'Will ETH hit $10k in 2025?',
      closeTime: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 3600),
      category: 3,          // Tech
      bondAmount: 20_000_000n, // $20 USDC (minimum)
      challengePeriod: 86_400n, // 24 hours
    });
  };

  return <button onClick={handleSubmit}>Create Market</button>;
}
```

***

### `useResolveMarket`

Resolves a market via Arcium MPC. Only the market's `resolver` wallet can call this successfully.

**Invalidates:** `marketKeys.detail(market)`

```typescript theme={null}
import { useResolveMarket } from '@cypher-zk/sdk/react';

function ResolveButton({ marketId, outcome }) {
  const resolveMarket = useResolveMarket();

  return (
    <button onClick={() => resolveMarket.mutate({ marketId, outcomeValue: outcome })}>
      Resolve Market
    </button>
  );
}
```

***

### `useClaimPayout`

Claims the winning payout for a resolved market. Any user with a winning position can call this.

**Invalidates:** `positionKeys.byUser(user)`, `marketKeys.detail(market)`

```typescript theme={null}
import { useClaimPayout } from '@cypher-zk/sdk/react';

function ClaimButton({ marketId }) {
  const claimPayout = useClaimPayout();

  return (
    <button onClick={() => claimPayout.mutate({ marketId })}>
      Claim Winnings
    </button>
  );
}
```

***

### `useClaimRefund`

Claims a refund when a market has passed its resolution deadline without resolving. Any user with an open position can call this.

**Invalidates:** `positionKeys.byUser(user)`, `marketKeys.detail(market)`

```typescript theme={null}
import { useClaimRefund } from '@cypher-zk/sdk/react';

function RefundButton({ marketId }) {
  const claimRefund = useClaimRefund();

  return (
    <button onClick={() => claimRefund.mutate({ marketId })}>
      Claim Refund
    </button>
  );
}
```

***

### `useCancelMarket`

Cancels an Active market that has zero bets. Returns the creator bond.

**Invalidates:** `marketKeys.all()`

```typescript theme={null}
import { useCancelMarket } from '@cypher-zk/sdk/react';
import { cancelEligibility } from '@cypher-zk/sdk';

function CancelButton({ market }) {
  const cancelMarket = useCancelMarket();
  const { ok, reason } = cancelEligibility(market);

  return (
    <button
      disabled={!ok}
      title={reason ?? undefined}
      onClick={() => cancelMarket.mutate({ marketId: market.marketId })}
    >
      Cancel Market
    </button>
  );
}
```

***

### `useFlagResolution` <span style={{fontSize:'0.75rem', color:'#6d45fc', fontWeight:600}}>v0.2+</span>

Flags a pending resolution as disputed during the challenge window. Anyone can call this.

**Invalidates:** `marketKeys.detail(market)`

```typescript theme={null}
import { useFlagResolution } from '@cypher-zk/sdk/react';

function FlagButton({ marketId }) {
  const flagResolution = useFlagResolution();

  return (
    <button onClick={() => flagResolution.mutate({ marketId })}>
      Flag Resolution
    </button>
  );
}
```

***

### `useFinalizeResolution` <span style={{fontSize:'0.75rem', color:'#6d45fc', fontWeight:600}}>v0.2+</span>

Finalizes a resolution once the challenge window has elapsed without a dispute. Anyone can call this.

**Invalidates:** `marketKeys.detail(market)`

```typescript theme={null}
import { useFinalizeResolution } from '@cypher-zk/sdk/react';

function FinalizeButton({ marketId }) {
  const finalizeResolution = useFinalizeResolution();

  return (
    <button onClick={() => finalizeResolution.mutate({ marketId })}>
      Finalize Resolution
    </button>
  );
}
```

***

### `useAdminOverrideResolution` <span style={{fontSize:'0.75rem', color:'#6d45fc', fontWeight:600}}>v0.2+</span>

Allows the Cyphers admin to override the outcome of a disputed market. Requires the admin wallet.

**Invalidates:** `marketKeys.detail(market)`

```typescript theme={null}
import { useAdminOverrideResolution } from '@cypher-zk/sdk/react';

function AdminOverridePanel({ marketId }) {
  const adminOverride = useAdminOverrideResolution();

  return (
    <button onClick={() => adminOverride.mutate({ marketId, outcomeValue: 1 })}>
      Override to YES
    </button>
  );
}
```

***

## Query key factories

Use query key factories when you need manual cache control - for example after sending a raw instruction without going through a mutation hook, or when implementing optimistic updates.

Import them from `@cypher-zk/sdk/react`:

```typescript theme={null}
import {
  globalStateKeys,
  marketKeys,
  positionKeys,
} from '@cypher-zk/sdk/react';
import { useQueryClient } from '@tanstack/react-query';

const qc = useQueryClient();

// After manually sending an instruction that affects a market:
await qc.invalidateQueries({ queryKey: marketKeys.detail(marketId) });
await qc.invalidateQueries({ queryKey: positionKeys.byUser(userPublicKey) });
```

| Factory                                         | Signature                                     | Invalidates               |
| ----------------------------------------------- | --------------------------------------------- | ------------------------- |
| `globalStateKeys.all()`                         | `() => QueryKey`                              | All global state queries  |
| `marketKeys.all()`                              | `() => QueryKey`                              | All market list queries   |
| `marketKeys.detail(id)`                         | `(bigint \| number) => QueryKey`              | Single market query       |
| `positionKeys.byUser(pubkey)`                   | `(PublicKey) => QueryKey`                     | All positions for a user  |
| `positionKeys.forMarket(pubkey)`                | `(PublicKey) => QueryKey`                     | All positions on a market |
| `positionKeys.forPair(market, user, betIndex?)` | `(PublicKey, PublicKey, bigint?) => QueryKey` | Specific position tuple   |

### Optimistic updates

For perceived snappiness, update the market bet count optimistically before the transaction confirms. Always roll back on error:

```typescript theme={null}
import { usePlaceBet, marketKeys } from '@cypher-zk/sdk/react';
import { useQueryClient } from '@tanstack/react-query';

function BetButton({ marketId, ...betParams }) {
  const qc = useQueryClient();

  const placeBet = usePlaceBet({
    onMutate: async ({ market }) => {
      await qc.cancelQueries({ queryKey: marketKeys.detail(market) });
      const previous = qc.getQueryData(marketKeys.detail(market));
      qc.setQueryData(marketKeys.detail(market), (m) =>
        m ? { ...m, totalBetsCount: m.totalBetsCount + 1n } : m
      );
      return { previous };
    },
    onError: (_err, { market }, ctx) => {
      if (ctx) qc.setQueryData(marketKeys.detail(market), ctx.previous);
    },
  });

  return <button onClick={() => placeBet.mutate(betParams)}>Bet</button>;
}
```

<Warning>
  Do not optimistically update `revealedPool0`–`revealedPool3`. These fields hold MPC-computed encrypted totals that only become correct after the Arcium callback finalizes. Any value you set will be overwritten - or worse, cause a stale-data bug if the callback is delayed.
</Warning>

### Suspense mode

The SDK hooks do not ship a built-in suspense variant. Use `useSuspenseQuery` from TanStack Query directly with the key factories:

```typescript theme={null}
import { useSuspenseQuery } from '@tanstack/react-query';
import { useCypherClient, globalStateKeys } from '@cypher-zk/sdk/react';

function ProtocolStats() {
  const client = useCypherClient();
  const { data } = useSuspenseQuery({
    queryKey: globalStateKeys.all(),
    queryFn: () => client.globalState.fetch(),
  });

  // data is non-nullable - the Suspense boundary handles the loading state
  return <p>Markets created: {data.marketCounter.toString()}</p>;
}
```
