All query hooks are built on TanStack Query v5. They return { data, isLoading, isError, error } and handle caching, deduplication, and background refetching automatically.
useGlobalState
Fetches the protocol config: fee rates and accepted USDC mint.
import { useGlobalState } from "@cypher-zk/sdk/react";
const { data: globalState } = useGlobalState();
// globalState.protocolFeeRate - protocol fee in basis points
// globalState.lpFeeRate - LP (creator) fee in basis points
// globalState.acceptedMint - PublicKey of the accepted USDC mint
// globalState.protocolTreasury - PublicKey of the treasuryNote: Always read
globalState.acceptedMintat runtime. Don't hardcode the USDC mint address - it differs between devnet and mainnet.
Cache TTL: 30 seconds.
useMarkets
Fetches markets. Filter by creator, state, or a known list of IDs.
import { useMarkets } from "@cypher-zk/sdk/react";
import { PublicKey } from "@solana/web3.js";
// All markets — expensive at scale, prefer `{ ids }` for paginated views
const { data: markets } = useMarkets();
// Only active markets (state = 0)
const { data: active } = useMarkets({ state: 0 });
// Markets by a specific creator
const { data: mine } = useMarkets({ creator: new PublicKey("...") });
// Paginated: fetch just the page you're showing. Routes through
// `client.markets.byIds` — chunks at Solana's 100-key cap with retries.
const ids = Array.from({ length: 50 }, (_, i) => BigInt(start + i));
const { data: page } = useMarkets({ ids });Each item in the array is { publicKey: PublicKey, account: MarketAccount }.
Note: The
{ ids }filter mode was added in v0.8.8. It's the recommended path once a deployment has more than a few hundred markets —useMarkets()with no filter still callsgetProgramAccountsand returns the full payload.
Tip: Memoize the filter object when it's derived in render (e.g. from query results) so TanStack Query sees a stable input:
const filter = useMemo(() => ({ ids: marketPdas }), [marketPdas]); const opts = useMemo( () => ({ enabled: marketPdas.length > 0 }), [marketPdas.length], ); const { data: page } = useMarkets(filter, opts);
Cache TTL: 10 seconds. Pass refetchInterval: 4000 for live odds updates.
useMarketQuestions
Batch-fetches the question text for a list of markets (typically the output of useMarkets). Routes through the SDK's RPC batching layer, so it scales past Solana's 100-key getMultipleAccountsInfo cap.
import { useMarkets, useMarketQuestions } from "@cypher-zk/sdk/react";
const { data: markets } = useMarkets({ ids });
const { data: questions } = useMarketQuestions(markets ?? []);
const text =
questions?.get(market.publicKey.toBase58()) ??
market.account.inlineQuestion;Returns Map<marketPdaBase58, questionString>. Markets without a MarketQuestion PDA (legacy v1/v2 inline-question markets) are omitted from the map - fall back to account.inlineQuestion for those.
Tip: Stabilize the input array so TanStack Query keys don't churn every render:
const marketsForQuestions = useMemo(() => rawMarkets ?? [], [rawMarkets]); const { data: questions } = useMarketQuestions(marketsForQuestions);
Cache TTL: 60 seconds. Questions are immutable after market creation. Query is automatically disabled when the markets list is empty. Available since v0.8.8.
useMarket
Fetches a single market by ID.
import { useMarket } from "@cypher-zk/sdk/react";
const { data: market } = useMarket(marketId); // bigint | numberUse the enabled option to gate on prerequisites:
const { data: market } = useMarket(marketId, {
enabled: !!marketId,
refetchInterval: 5000,
});Cache TTL: 10 seconds.
useUserPositions
Fetches all positions (bets) for a wallet across all markets.
import { useUserPositions } from "@cypher-zk/sdk/react";
import { PublicKey } from "@solana/web3.js";
const { data: positions } = useUserPositions(
new PublicKey(walletAddress),
{ enabled: !!walletAddress }
);
// Each item: { publicKey: PublicKey, account: EncryptedPositionAccount }
// account.betIndex - which bet this is (0, 1, 2, ...)
// account.netAmount - stake after fees (plaintext bigint)
// account.entryOdds - locked-in odds × ODDS_SCALE (1e9)
// account.claimed - whether payout was already claimedCache TTL: 5 seconds.
usePosition
Fetches a single position by market, user, and bet index.
import { usePosition } from "@cypher-zk/sdk/react";
const { data: position } = usePosition(
marketPublicKey,
userPublicKey,
betIndex ?? 0n // defaults to 0n
);Cache TTL: 5 seconds.
Cache TTL reference
| Hook | Default TTL | Notes |
|---|---|---|
useGlobalState |
30 s | Fee rates change rarely |
useMarkets |
10 s | Pass refetchInterval for live odds |
useMarket |
10 s | Same as above |
useMarketQuestions |
60 s | Questions are immutable after market creation |
useUserPositions |
5 s | Frequently invalidated by mutations |
usePosition |
5 s | Invalidated after claim |
Manual cache invalidation
If you send raw instructions outside of mutation hooks, invalidate the cache yourself using the query key factories:
import {
marketKeys,
marketQuestionKeys,
positionKeys,
} from "@cypher-zk/sdk/react";
import { useQueryClient } from "@tanstack/react-query";
const qc = useQueryClient();
// Invalidate a specific market
qc.invalidateQueries({ queryKey: marketKeys.detail(marketId) });
// Invalidate a paginated market page
qc.invalidateQueries({ queryKey: marketKeys.byIds(ids) });
// Invalidate batch question fetch for a page of markets
qc.invalidateQueries({
queryKey: marketQuestionKeys.byMarkets(markets.map((m) => m.publicKey)),
});
// Invalidate all positions for a user
qc.invalidateQueries({ queryKey: positionKeys.byUser(userPublicKey) });