For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending.mdto the URL or sendingAccept: text/markdown.
React SDK
For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /sdks/react.md.
@usearete/react adds provider-managed connections and React hooks to the framework-agnostic @usearete/sdk client.
Install
Section titled “Install”npm install @usearete/react @usearete/sdk zodReact remains an application peer dependency. @usearete/react carries zustand as a normal dependency, so do not install zustand separately. Generated stack files import core SDK types and Zod schemas directly, which is why generated React consumers install all three packages above.
Arete’s fluent .use(), .useOne(), and .useMutation() methods are React
hooks. Standard Hooks linting cannot identify deeply nested member calls, so
@usearete/react/eslint-plugin.cjs exports an arete/fluent-hooks rule for
flat ESLint configurations. Enable it alongside the standard
react-hooks/rules-of-hooks rule. The React ORE template includes this setup.
import areteHooks from '@usearete/react/eslint-plugin.cjs';
export default [{ plugins: { arete: areteHooks }, rules: { 'arete/fluent-hooks': 'error' },}];An application also needs a generated stack definition. Install the deployed public ORE stack into your source tree with the CLI:
a4 install ore --ts \ --output ./src/generated/ore-stack.ts \ --package-name @usearete/reactThe generated entry point exports ORE_STREAM_STACK, entity types, schemas, program instructions, semantic transactions, addresses, reads, and ORE extensions. Commit generated output, but do not edit it by hand.
Minimal setup
Section titled “Minimal setup”Wrap the application once, then call useArete with the generated stack. The stack contains its default WebSocket and HTTP endpoints, so no URL prop is required.
import { AreteProvider, useArete } from '@usearete/react';import { ORE_STREAM_STACK } from './generated/ore-stack';
const publishableKey = import.meta.env.VITE_ARETE_PUBLISHABLE_KEY;if (!publishableKey) throw new Error('VITE_ARETE_PUBLISHABLE_KEY is required');
function CurrentRound() { const arete = useArete(ORE_STREAM_STACK); const board = arete.views.OreBoard.state.use({ address: arete.addresses.board(), }); const roundId = board.data?.state.roundId; const round = arete.views.OreRound.state.use( roundId == null ? undefined : { roundId }, );
if (arete.status === 'error') { return <button onClick={() => void arete.retry()}>Reconnect</button>; } if (board.status !== 'ready' || round.status !== 'ready') return <p>Connecting...</p>; return <p>Round {round.data?.id.roundId?.toString() ?? 'unavailable'}</p>;}
export default function App() { return ( <AreteProvider autoConnect stack={ORE_STREAM_STACK} auth={{ publishableKey }}> <CurrentRound /> </AreteProvider> );}For ORE, OreBoard/state is authoritative for the active roundId. OreRound/latest is a sorted recent-round list, not an active-round pointer. Following Board to keyed Round avoids showing a stale or future round during rollover.
Generated names and keys
Section titled “Generated names and keys”Generated TypeScript uses camelCase even though stack JSON and raw view paths use snake_case:
| Generated API | Correct shape |
|---|---|
| Board state key | { address: string } |
| Round state key | { roundId: bigint } |
| Miner state key | { authority: string } |
| Round ID field | round.id.roundId |
| Deployed SOL field | round.state.totalDeployed |
| Per-square SOL field | round.state.deployedPerSquareUi |
| Winning square field | round.results.winningSquare |
The server view identifiers remain OreBoard/state, OreRound/state, OreRound/latest, and OreMiner/state. Use the generated object paths rather than spelling those protocol identifiers in application code.
Hosted Arete stacks require a publishable key for authentication and rate limiting. Local servers configured with allow_all may omit auth.
Read-only ORE viewing does not require a wallet, but hosted browser access is still authenticated with VITE_ARETE_PUBLISHABLE_KEY as shown above.
Provider
Section titled “Provider”AreteProvider owns connected clients and their stores. Supported provider props are:
| Prop | Purpose |
|---|---|
stack | Default stack for argument-less useArete() calls |
stackOptions | Default lookup options for the provider stack |
autoConnect | Start the initial connection; defaults to true |
autoReconnect | Recover an established connection after loss; defaults to true |
wallet | Wallet adapter used by generated program operations |
auth | Authentication options such as publishableKey |
fetch | Custom fetch implementation |
validateFrames | Set false to silence rejected-frame warnings |
onFrameValidationError | Observe structured generated-schema rejections |
reconnectIntervals | Reconnect delays in milliseconds |
maxReconnectAttempts | Reconnect attempt limit |
maxEntriesPerView | Per-view store limit, or null for unlimited |
flushIntervalMs | Store update batching interval |
autoConnect and autoReconnect control separate lifecycle phases. Setting autoConnect={false} does not disable automatic recovery for a connection the app later establishes, but canRetry remains false because provider-owned replacement attempts would also stay disconnected; use the client lifecycle directly. Setting autoReconnect={false} does not prevent the provider’s initial connection.
Provider connection settings are captured when a shared client is created.
Changing them does not mutate an existing client; call retry() to replace it
using the latest provider configuration. Wallet updates are synchronized to
existing clients in place.
With a default stack declared, components call useArete() with no arguments:
<AreteProvider autoConnect stack={MY_STACK} stackOptions={{ url: 'ws://localhost:8877' }}> <App /></AreteProvider>
// Anywhere below — fully typed once the stack is registered:const arete = useArete();Passing that same stack explicitly also keeps its provider options, so local
endpoint overrides still apply: useArete(MY_STACK). Per-call options take
precedence, and a different explicit stack does not inherit these defaults.
Register the stack type once so argument-less calls are typed:
declare module '@usearete/react' { interface AreteDefaultStackRegistry { defaultStack: MyStack; }}Endpoint overrides can still go on the hook call, and always win:
const arete = useArete(MY_STACK, { url: 'ws://localhost:8877', httpUrl: 'http://localhost:8877',});useArete options are url, httpUrl, transport, and programs. Keep a supplied programs object stable with a module constant or useMemo.
Shared calls
Section titled “Shared calls”Sibling components may each call useArete(MY_STACK) instead of threading one large result object through props. Calls with the same stack object and connection options share one provider-managed client and store. Concurrent initial calls also share the same connection attempt.
Changing the stack or attached-program object identity intentionally produces a different cache key. Keep generated stack imports at module scope.
Stack-bound React API
Section titled “Stack-bound React API”Single-stack apps can avoid both repeated constants and ambient module augmentation by binding the React API once:
import { createAreteReact } from '@usearete/react';import { ORE_STREAM_STACK } from './generated/ore-stack';
export const { Provider: OreProvider, useArete: useOre,} = createAreteReact(ORE_STREAM_STACK);useOre() carries the generated stack type directly and remains explicit at each consumer. Keep the generic AreteProvider and useArete(stack) API for multi-stack applications.
useArete result
Section titled “useArete result”The hook returns:
views: generated state, list, and custom view hooksprograms: generated raw instructions and semantic operation hooksqueries: stack-level HTTP queriesread: callable one-shot extension reads with.use(...args, options?)React hooksreads: deprecated alias ofreadchain: generic Solana readsaddresses,constants,defaults, andmath: generated extension surfacesclient: connected low-level client, ornullwhile connectingstatus: preferred connection state for UI, including initial client creation and failed attemptsconnectionState,isConnected,isLoading,canRetry, anderrorsocketIssue: the latest structured WebSocket issueretry(): a deduplicated manual connection retry
Program and read hooks can be called unconditionally while the client is connecting. Submitting a disconnected mutation rejects with Arete client is not connected.
Lists and custom views
Section titled “Lists and custom views”const arete = useArete(ORE_STREAM_STACK);
const recent = arete.views.OreRound.latest.use({ take: 10 });const newest = arete.views.OreRound.latest.useOne();const allMiners = arete.views.OreMiner.list.use();List views return arrays. .useOne() and .use({ take: 1 }) return one entity or undefined.
Each parameter set is an independent protocol v2 query. Windows and filters on the same view do not share membership, while equivalent normalized queries share one reference-counted wire subscription:
const firstPage = arete.views.OreRound.latest.use({ take: 10 });const secondPage = arete.views.OreRound.latest.use({ take: 10, skip: 10 });During reconnect, each hook keeps its committed data and reports isRefreshing: true. A completed authoritative snapshot atomically replaces membership for only that query, including when the new result is empty.
State views
Section titled “State views”State views require their generated key object:
const round = arete.views.OreRound.state.use({ roundId: 335000n });const miner = arete.views.OreMiner.state.use( authority ? { authority } : undefined,);Pass undefined or null to disable a state subscription. A disabled hook returns data: undefined and isLoading: false; this is the normal wallet-disconnected pattern.
Hook options and results
Section titled “Hook options and results”Put list query configuration in the first params object: server-side key, partition, take, skip, filters, after, withSnapshot, and snapshotLimit, plus client-side schema and onSchemaValidationError. Keep enabled and initialData in the second options object. For compatibility, query fields present in the second argument are used when absent from params; params take precedence when both are supplied.
When a caller-supplied schema rejects an entity, onSchemaValidationError(diagnostic) receives { view, key?, entity, error }. The callback adds observability only: accepted entities still pass through unchanged, and rejected entities remain excluded.
Every view hook returns a discriminated result. Checking status and
isEmpty narrows data and error:
if (round.status === 'error') { console.error(round.error); // Error} else if (round.status === 'ready' && !round.isEmpty) { console.log(round.data.id.roundId); // entity, not undefined}status separates “the client has not connected yet” (connecting) from “subscribed, waiting for the first frame” (subscribing). isPending covers both, isReady marks a usable ready result, and isEmpty identifies a ready hook with no state entity or list entries. Errors may retain previously committed data, but isReady becomes false.
initialData is trusted as immediately usable ready data. State hooks accept one entity, list hooks accept an entity array, and useOne accepts one entity. The first live store update or completed empty snapshot replaces it. An empty list settles as []; an absent state entity settles as undefined. Do not seed transaction-authorizing data unless the application is willing to trust that seed.
await result.refresh() resolves after the refreshed subscription’s next complete snapshot is committed. Registration, send, and subscription failures reject, clear isRefreshing, and are exposed through error. A view refresh with no active matching subscription is a no-op.
The view hook objects themselves are also refresh targets. They refresh that view’s active subscriptions — keyed by argument for state views, all active subscriptions when omitted — and are a no-op when nothing is subscribed:
await arete.views.OreRound.state.refresh({ roundId });await arete.views.OreRound.state.refresh();await arete.views.OreRound.latest.refresh();To aggregate connection, view, and read results into loading, refreshing, and error status, use summarizeStatuses:
const streams = summarizeStatuses({ Board: board, Round: round, Miner: authority && miner });// streams.loading → ['Board'], streams.errors → ['Treasury: socket closed']// streams.refreshing → ['Round'] while committed data is being resynchronizedFalsy entries are skipped, which makes authority && miner the idiomatic way to gate a wallet-dependent source.
One-shot reads
Section titled “One-shot reads”Stack extensions can expose HTTP or RPC-backed reads. React wraps each function with an argument-keyed hook:
const preview = arete.read.solClaimPreview.use(authority);const quote = arete.read.quoteManualDeployment.use( input, { debounceMs: 300 },);The read runs again when its arguments change and exposes data, error, status, isPending, isReady, isEmpty, isLoading, isRefreshing, and refresh(). Status is 'disabled' | 'connecting' | 'loading' | 'ready' | 'refreshing' | 'error'. It is disabled while any required argument is null or undefined. Use .use(...args, { debounceMs, initialData }) for React-only behavior with options last; debounce applies to automatic argument changes, while refresh() always runs immediately. When options follow an omitted optional read argument, pass undefined in that argument’s position. The same function remains callable for imperative access: await arete.read.solClaimPreview(authority). The old arete.reads namespace remains as a deprecated alias.
ORE’s quoteManualDeployment reads current raw Miner and Automation accounts and computes an allocation preview. A quote is not a reservation and does not guarantee the Board will stay on the same round. Transaction preparation re-reads the raw Board account and rejects a stale explicit roundId before wallet approval.
Mutations
Section titled “Mutations”Generated raw instructions and semantic operations expose .useMutation():
const deploy = arete.programs.ore.transactions.mining .deployWithCheckpoint .useMutation();
await deploy.submit( { signer: authority, roundId, squares, amountPerSquare, }, { reconcile: { refresh: [ arete.views.OreBoard.state, arete.views.OreRound.state, arete.views.OreMiner.state, quote, ], }, },);Use mutation.mutate(args, options) from click and submit handlers when the
hook’s displayError state owns failure presentation. Use the rejecting
mutation.mutateAsync(args, options) or mutation.submit(args, options) for
imperative workflows that await or compose the result.
refresh accepts view hook objects (which refresh that view’s active subscriptions), view and read hook results, and plain callbacks — so a component that only writes does not need its own subscriptions just to refresh them.
The mutation prepares the operation, asks the wallet to sign, submits once, and tracks transaction receipts. Each generated operation callback updates completedReceipts, signatures, and the submitted phase before the final operation receipt resolves. Arete does not receive the wallet’s private key. The SDK does not automatically retry an ambiguous submission.
Useful result fields include phase, status, prepared, result, signatures, completedReceipts, displayError, failure, reconciliationError, isPreparing, isAwaitingWallet, and isReconciling.
See Transactions for transport, inspection, and failure-outcome details.
Parse form amounts without throwing
Section titled “Parse form amounts without throwing”safeToRawAmount(input, decimals) is re-exported by @usearete/react. Use it at form boundaries when invalid user input should become UI state rather than an exception:
import { safeToRawAmount } from '@usearete/react';
const parsed = safeToRawAmount({ ui: amountText }, 9);if (!parsed.success) { setAmountError(String(parsed.error)); return;}
const rawAmount = parsed.data;It returns { success: true, data } | { success: false, error }. Use the throwing toRawAmount variant when invalid input is a programmer error.
Stream reconciliation
Section titled “Stream reconciliation”Generated program mutation hooks reconcile by default in WebSocket mode:
- The transaction confirms on Solana.
- The hook finds the highest slot in its completed receipts.
- It waits up to 30 seconds for the connected stack’s processed-slot watermark to reach that slot.
- It refreshes the supplied hook results and waits for view snapshots and one-shot reads to complete.
The watermark proves the stack processed at least that slot. It does not guarantee that every entity changed or that a one-shot read reran; include the view hook objects, hook results, or reads that need refresh.
deploy.submit(args, { reconcile: false });deploy.submit(args, { reconcile: { timeoutMs: 10_000 } });deploy.submit(args, { reconcile: { refresh: preview.refresh } });deploy.submit(args, { reconcile: async (context) => { // A custom function replaces default processed-slot reconciliation. },});onConfirmed runs after chain confirmation. onSuccess runs after successful reconciliation, or immediately when reconciliation is disabled or skipped. A reconciliation timeout, refresh failure, or refreshed snapshot timeout settles as confirmed-unreconciled, is exposed through reconciliationError, and does not reject or roll back the confirmed transaction result.
Call retryReconciliation() after a confirmed-unreconciled result to repeat only processed-slot and refresh work. The hook reuses the confirmed receipts and original refresh targets; it never rebuilds, signs, or resubmits the transaction. canRetryReconciliation indicates when that recovery is available.
HTTP-only mutations and results without receipt slots skip default stream reconciliation.
Generated boundary
Section titled “Generated boundary”A stack build produces .arete/<Stack>.stack.json; SDK generation consumes that AST. The generated directory can contain:
<stack>-core.ts: generated entities, schemas, views, and program modules<stack>.ts: public entry point and extension wiring- extension artifacts copied from the stack’s extension source
sdk-provenance.json: canonical input, generator, extension, and artifact hashes
Regenerate after changing a stack, IDL, extension, or generator. For this monorepo’s ORE examples:
bash scripts/generate-example-sdks.shThat command rebuilds stacks/ore/.arete/OreStream.stack.json from local Arete source before emitting all example SDKs. CI runs generation twice and rejects AST, provenance, or artifact drift.
Migration from 0.3 to next
Section titled “Migration from 0.3 to next”- Legacy low-level
useViewanduseEntityhooks are removed; generatedarete.views.<Entity>.<view>.use(...)hooks are the supported subscription API. - Connection failures now report
status: 'error'; usecanRetryto decide when to show a retry action. - Mutation hooks add non-throwing
mutate()for event handlers and keep rejectingmutateAsync()/submit()for imperative composition.
Migration from 0.2 to 0.3
Section titled “Migration from 0.2 to 0.3”- Install and import
@usearete/react; legacy unscoped package examples are obsolete. - Remove the legacy provider-level WebSocket URL prop. Use generated endpoints or
useArete(stack, { url, httpUrl }). - Pass generated state key objects such as
{ roundId },{ authority }, or{ address }. Zero-argument state subscriptions are no longer supported. - Pass
undefinedornullto disable a state hook. Disabled hooks now reportisLoading: false. - Use generated camelCase fields such as
roundId,totalDeployed, andoreMetadata, not wire-format snake_case. - Generated program mutations now reconcile against the stream by default. Pass
reconcile: falsefor confirmation-only behavior. - Use
onConfirmedfor work that must happen after chain confirmation even if reconciliation later times out; reserveonSuccessfor reconciled success. - One-shot extension reads expose imperative calls and hooks together under
arete.read.<name>;arete.readsremains a deprecated alias. - Repeated
useAretecalls share a client when their stack and option identities match. Remove component-local connection ownership workarounds.
Low-level exports
Section titled “Low-level exports”@usearete/react exports useConnectionState, useAreteContext, useAsyncRead, useInstructionMutation, reconciliation helpers, and selected core SDK APIs and types. Generated view hooks are the supported React subscription API. The package is not a complete mirror of @usearete/sdk; import the core package directly for the full low-level surface.