Skip to content

Arete documentation

React SDK

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 .md to the URL or sending Accept: text/markdown.

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.

For an app that combines stacks, programs, and protocols in one session, start with Compose a session in React.

Protocol hooks accept a protocol client directly, including a member of session.protocols. useProtocolInstance follows canonical instance state and the optional connected live row. useWalletActions and useAllowedSteps report wallet-specific availability and rejection reasons. useProtocolParticipant reads participant state, and useProtocolEvents subscribes while mounted. Keep the client stable and close it when its owner is disposed.

These hooks read and subscribe; call the generated client’s prepare/sign/submit flow for mutations. A permitted action can still be rejected during prepare. See Build a protocol interface with React for a complete component and Protocol API reference for hook signatures.

Terminal window
npm install @usearete/react @usearete/sdk zod

React and @usearete/sdk are peer dependencies, so the application owns exactly one copy of the core SDK and @usearete/react shares it (a second copy would break the error and outcome checks mutation hooks rely on). Keep both packages on the same release. @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.

eslint.config.js
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. Discover a subscribe-ready package and install the target reported by its descriptor:

Terminal window
a4 explore catalog --query "<intent>" --kind stack --mode subscribe --json
a4 explore catalog stack <slug> --json
a4 install stack <slug> --ts \
--output ./src/generated/my-stack.ts \
--package-name @usearete/react

The generated entry point exports the stack definition, entity types, schemas, program operations, addresses, reads, and package-specific helpers. Commit generated output, but do not edit it by hand.

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 { MY_STACK } from './generated/my-stack';
const publishableKey = import.meta.env.VITE_ARETE_PUBLISHABLE_KEY;
if (!publishableKey) throw new Error('VITE_ARETE_PUBLISHABLE_KEY is required');
function Positions() {
const arete = useArete(MY_STACK);
const positions = arete.views.Position.list.use({ take: 20 });
if (arete.status === 'error') {
return <button onClick={() => void arete.retry()}>Reconnect</button>;
}
if (positions.status !== 'ready') return <p>Connecting...</p>;
if (positions.isEmpty) return <p>No matching positions.</p>;
return <pre>{JSON.stringify(positions.data, null, 2)}</pre>;
}
export default function App() {
return (
<AreteProvider autoConnect stack={MY_STACK} auth={{ publishableKey }}>
<Positions />
</AreteProvider>
);
}

MY_STACK, Position, and the available query parameters are placeholders. Use the generated import, view names, keys, and fields for the package you installed.

Generated TypeScript uses language-native camelCase even when source schemas or wire view paths use another convention. Use generated object paths rather than spelling protocol view identifiers or translating fields yourself.

Hosted Arete stacks require a publishable key for authentication and rate limiting. Local servers configured with allow_all may omit auth.

Read-only viewing does not require a wallet, but hosted browser access can still require a publishable key as shown above.

AreteProvider owns connected clients and their stores. Supported provider props are:

PropPurpose
stackDefault stack for argument-less useArete() calls
stackOptionsDefault lookup options for the provider stack
autoConnectStart the initial connection; defaults to true
autoReconnectRecover an established connection after loss; defaults to true
walletWallet adapter used by generated program operations; does not reach session members
authAuthentication options such as publishableKey
fetchCustom fetch implementation
validateFramesSet false to silence rejected-frame warnings
onFrameValidationErrorObserve frames that were not stored: schema rejections, and patches for keys the client holds no entity for
reconnectIntervalsReconnect delays in milliseconds
maxReconnectAttemptsReconnect attempt limit
maxEntriesPerViewPer-view store limit, or null for unlimited
flushIntervalMsStore update batching interval
environment'production' (default) or { kind: 'dev', url } for the project’s dev environment; see below

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.

environment points every client the provider creates (and an AreteSessionProvider below it) at the project’s dev environment through the edge a4 dev up serves. A browser reads no process variables, so pass it from your framework’s public variables:

<AreteProvider
environment={import.meta.env.VITE_ARETE_ENVIRONMENT === 'dev'
? { kind: 'dev', url: import.meta.env.VITE_ARETE_DEV_URL }
: 'production'}
>
<AreteEnvironmentBadge /> {/* renders only in dev mode */}
<App />
</AreteProvider>

The page holds no Arete key in dev mode. useAreteEnvironment() returns 'dev' or 'production', and <AreteEnvironmentBadge /> shows a small “Dev environment” badge in dev mode and nothing in production. Sends need a sign-only wallet, such as useWalletStandardWallet.

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. Lookup options accept url, httpUrl, transport, transactions, chain, and programs. Supply transactions when transaction inspection and submission use a different service from the stack HTTP read endpoint.

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(stack) options are url, httpUrl, transport, transactions, chain, and programs. Keep a supplied programs object stable with a module constant or useMemo. A session member takes only stack (see Stacks in a session).

Attached programs follow the core identity rule. A program the stack already provides (the same object, or the same packageReleaseHash) resolves to the stack’s own program. A program with the same programSpecHash that cannot be proven identical, such as one you extended with extendProgram, replaces the stack’s under that key, and the SDK logs one warning. Any other program under one of the stack’s keys fails the connection with PROGRAM_KEY_CONFLICT, reported through arete.error; attach it under a key the stack does not use.

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.

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.

Arete targets Wallet Standard wallets. Browser wallets that implement it register themselves, so the app does not list or import wallet-specific adapters. The recommended wiring, used by the React ORE template, is WalletProvider with an empty wallets list plus useSolanaWalletAdapter from @usearete/adapter-web3js/react:

Terminal window
npm install @usearete/adapter-web3js @solana/web3.js \
@solana/wallet-adapter-react @solana/wallet-adapter-react-ui
import type { ReactNode } from 'react';
import { WalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import { useSolanaWalletAdapter } from '@usearete/adapter-web3js/react';
import { AreteProvider } from '@usearete/react';
import { MY_STACK } from './generated/my-stack';
import '@solana/wallet-adapter-react-ui/styles.css';
// useSolanaWalletAdapter reads the wallet context, so AreteProvider sits
// one level below WalletProvider.
function AreteShell({ children }: { children: ReactNode }) {
const wallet = useSolanaWalletAdapter(); // undefined until a wallet connects
return (
<AreteProvider stack={MY_STACK} auth={{ publishableKey }} wallet={wallet}>
{children}
</AreteProvider>
);
}
export default function App() {
return (
// wallets={[]}: only Wallet Standard wallets, detected at runtime.
<WalletProvider wallets={[]} autoConnect>
<WalletModalProvider>
<AreteShell>
<Dashboard />
</AreteShell>
</WalletModalProvider>
</WalletProvider>
);
}

Do not add wallet-specific adapter packages to wallets; Wallet Standard detection already covers wallets that support it. The web3.js adapter builds v0 transactions, so a wallet must support version 0; see the adapter README for its peer-dependency matrix.

React apps that use @wallet-standard/react instead of the Solana wallet adapter use useWalletStandardWallet from @usearete/react/wallet-standard; see Connect a Wallet Standard wallet. Its wallet works with AreteProvider as well as with a session.

Apps that do not use React or web3.js bridge a Wallet Standard account with createWalletStandardSigner from @usearete/adapter-kit and pass the signer to that package’s createWalletAdapter:

import { createWalletAdapter, createWalletStandardSigner } from '@usearete/adapter-kit';
const signer = createWalletStandardSigner({ wallet, account, chain: 'solana:mainnet' });
const arete = createWalletAdapter({ transport: 'auto', signer });

Use a createSession session when the app combines stacks with standalone programs or protocols, or wants one wallet for all of them. Compose a session in React walks through a complete app. In short:

  • AreteSessionProvider opens the session (definition, plus createSession options without wallet) when it mounts and closes it when it unmounts. options.auth and options.fetch default to the enclosing AreteProvider’s. A new definition or new options reopen it; a new wallet prop only calls session.setWallet. Pass session={existing} instead of definition to share a session the app opened.
  • useAreteSession<typeof DEFINITION>() returns { session, status, error, ready, retry }. session is null until status is open.
  • useOperationMutation(prepare, { wallet }) runs a prepared instruction, transaction, or flow on the provider’s session. A run without a wallet fails with not-ready before anything is prepared; set allowExecutorWallet: true to sign with the session’s wallet. Failures are an OperationMutationError with a typed kind, such as wallet-rejected, wallet-feature-unsupported, wallet-chain-unsupported, wallet-version-unsupported, or wallet-modified.
  • useWalletStandardWallet(account, { chain }) from @usearete/react/wallet-standard turns a @wallet-standard/react account into a sign-only wallet. Its error is a WalletStandardSignerError whose code is sign_transaction_unsupported, chain_unsupported, or account_unavailable. This entry point needs @usearete/adapter-kit, @solana/kit, and @wallet-standard/ui-registry.
  • AreteProvider’s wallet prop does not reach session members. Set the session’s wallet on AreteSessionProvider.

A stack member of the session (session.stacks.<key>) is already connected. Pass it to useArete for the same typed views, programs, reads and mutations as useArete(stack), over the member’s connection:

const { session } = useAreteSession<typeof APP>();
const ore = useArete(session?.stacks.ore, { stack: APP.stacks.ore });
const round = ore.views.OreRound.latest.use();

Here APP is a session definition such as { stacks: { ore: ORE_STREAM_STACK } }, declared at module level.

Nothing connects a second time, and unmounting never disconnects the member: the session closes it. While the session opens the member is undefined and the result is useArete(stack)’s connecting state, with placeholder views and programs from stack (required whenever the member can be null or undefined). A reopen gives a new member, and the hooks move to it. Mutations sign with the session’s wallet (the provider’s wallet prop), and wait for the streamed slot they landed in only when the session connected the member over the WebSocket: the hooks read the member’s transport ('ws' or 'http') and its stack, so neither is an option once the member exists. See Live views from a session stack.

The hook returns:

  • views: generated state, list, and custom view hooks
  • programs: generated account, instruction, operation, flow, and mutation hooks
  • queries: stack-level HTTP queries
  • read: callable one-shot extension reads with .use(...args, options?) React hooks
  • reads: deprecated alias of read
  • chain: generic Solana reads
  • addresses, constants, defaults, and math: generated extension surfaces
  • client: connected low-level client, or null while connecting
  • status: preferred connection state for UI, including initial client creation and failed attempts
  • connectionState, isConnected, isLoading, canRetry, and error
  • socketIssue: the latest structured WebSocket issue
  • retry(): 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.

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

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 resynchronized

Falsy entries are skipped, which makes authority && miner the idiomatic way to gate a wallet-dependent source.

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.

Generated raw instructions and application 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.

phase is the field to branch UI on. These are all of its values:

PhaseMeaning
idleNothing submitted yet, or reset() was called
preparingBuilding the operation (reading accounts, deriving addresses) before the wallet is involved
awaiting-walletThe wallet is signing, and the adapter is sending and confirming the transaction
submittedA transaction’s receipt landed; a multi-transaction flow returns to awaiting-wallet for the next one
confirmedEvery transaction confirmed on chain
reconcilingWaiting for the stream to process the confirmed slot and refreshing the supplied views and reads
reconciledThe stream caught up and the refreshes completed
confirmed-unreconciledConfirmed on chain, but reconciliation timed out or failed; see reconciliationError
not-submittedFailed before anything reached the network (preparation, wallet rejection, or a definite send refusal)
submitted-unknownThe transaction may have been sent; check signature before any retry, and never resend blindly
chain-failedSolana reported an on-chain error; displayError carries the parsed program error when available

There is no separate submitting or confirming phase: awaiting-wallet covers signing, sending, and confirmation, because the wallet adapter performs all three in one call. The happy path is preparing → awaiting-wallet → submitted → confirmed → reconciling → reconciled; with reconciliation disabled or skipped it stops at confirmed. status is the coarser 'idle' | 'pending' | 'success' | 'error'.

See Transactions for transport, inspection, and failure-outcome details.

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.

Generated program mutation hooks reconcile by default in WebSocket mode:

  1. The transaction confirms on Solana.
  2. The hook finds the highest slot in its completed receipts.
  3. It waits up to 30 seconds for the connected stack’s processed-slot watermark to reach that slot.
  4. 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.

A stack build produces ProgramSpec, LiveSpec, and StackManifest artifacts under .arete/; SDK generation consumes the exact StackManifest. 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-manifest.json: stable input, extension identities, artifact inventory, and generated content hash
  • sdk-provenance.json: compiler provenance linked to the generated content

Regenerate after changing a stack, IDL, extension, or generator. For this monorepo’s ORE examples:

Terminal window
bash scripts/generate-example-sdks.sh

That command rebuilds ore.program-spec.json, OreStream.live-spec.json, and OreStream.stack-manifest.json from local Arete source before emitting all example SDKs. CI runs generation twice and rejects artifact, provenance, or output drift.

  • Legacy low-level useView and useEntity hooks are removed; generated arete.views.<Entity>.<view>.use(...) hooks are the supported subscription API.
  • Connection failures now report status: 'error'; use canRetry to decide when to show a retry action.
  • Mutation hooks add non-throwing mutate() for event handlers and keep rejecting mutateAsync() / submit() for imperative composition.
  • 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 undefined or null to disable a state hook. Disabled hooks now report isLoading: false.
  • Use generated camelCase fields such as roundId, totalDeployed, and oreMetadata, not wire-format snake_case.
  • Generated program mutations now reconcile against the stream by default. Pass reconcile: false for confirmation-only behavior.
  • Use onConfirmed for work that must happen after chain confirmation even if reconciliation later times out; reserve onSuccess for reconciled success.
  • One-shot extension reads expose imperative calls and hooks together under arete.read.<name>; arete.reads remains a deprecated alias.
  • Repeated useArete calls share a client when their stack and option identities match. Remove component-local connection ownership workarounds.

@usearete/react/testing re-exports the core testing helpers and adds two React-specific ones:

  • createAreteTestConfig(harness, props?) returns <AreteProvider> props that connect every client to a createWebSocketHarness() server, with no reconnects or batching delay. Everything else (views, reads, mutations, reconciliation) runs through the real provider and client.
  • createMutationResultFixture(phase, overrides?) returns a complete, internally consistent useMutation() result for any value in MUTATION_PHASES, so phase-specific UI can be rendered and tested without a wallet.
import { AreteProvider } from '@usearete/react';
import {
createAreteTestConfig,
createWalletFixture,
createWebSocketHarness,
} from '@usearete/react/testing';
const ws = createWebSocketHarness();
const wallet = createWalletFixture();
render(
<AreteProvider {...createAreteTestConfig(ws, { wallet })}>
<Dashboard />
</AreteProvider>,
);
// Poll with your test library's waitFor so React commits between checks.
const subscription = await waitFor(() => ws.subscription('OreRound/latest'));
ws.serve(subscription, [{ key: '1', data: round }]);

@usearete/react exports useConnectionState, useAreteContext, useAsyncRead (see Program reads with 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.