Arca/Documentation
Join Waitlist

React SDK

@arca-network/react is a thin, dependency-free hooks layer over @arca-network/sdk. It wraps every watch* stream as a declarative subscription hook, adds one-shot query hooks and write/mutation hooks (with full operation/order lifecycle tracking), and ships a provider that can either adopt an existing Arca instance or bootstrap and own one for you. The only peer dependencies are react (>=18) and @arca-network/sdk — no react-query, no extra runtime.

Installation

bash
npm install @arca-network/react @arca-network/sdk react

Quick start

Wrap your app in ArcaProvider, then call hooks anywhere beneath it. In instance mode you bring your own client and own its lifecycle:

tsx
import { Arca } from '@arca-network/sdk';
import { ArcaProvider, useArcaPrice, useArcaConnection } from '@arca-network/react';
const arca = Arca.fromToken(jwt, { realm: 'my-realm' });
function App() {
return (
<ArcaProvider arca={arca}>
<Banner />
<BtcPrice />
</ArcaProvider>
);
}
function BtcPrice() {
const { price, status } = useArcaPrice('hl:0:BTC');
if (status === 'loading') return <span>Loading…</span>;
return <span>BTC: {price ?? '—'}</span>;
}

Conventions. Every watch hook returns a status of 'loading' | 'connected' | 'reconnecting' | 'error'. Live streams self-heal — they report reconnecting while the socket is down and never enter error on their own (error only means the initial subscribe threw). Coin ids are canonical ("hl:0:BTC"), and money values are decimal strings.

Provider & Connection

ArcaProvider supports two modes. Instance mode (arca={instance}) shares a client you constructed and disposed yourself. Config mode (config={...}) lets the provider build the client via Arca.fromTokenProvider / Arca.fromToken, kick off ready(), and dispose() it on unmount.

tsx
// Config mode — the provider owns the client
<ArcaProvider config={{ tokenProvider: fetchToken, baseUrl, realm: 'my-realm' }}>
<App />
</ArcaProvider>

Keep the config object stable (a constant or memoized value); the provider only rebuilds when baseUrl, realm, or the presence of token/tokenProvider changes.

useArcaConnection

Track the realtime connection / session for a banner or gate. useArcaConnection() maps the SDK's WebSocket status and auth errors to a small state machine and exposes a retry().

StatusMeaning
connectingEstablishing or re-establishing the realtime connection
liveConnected and authenticated
offlineDisconnected; the SDK is retrying in the background
sessionExpiredToken refresh failed — prompt the user to re-authenticate
tsx
function Banner() {
const { status, retry } = useArcaConnection();
if (status === 'live') return null;
if (status === 'sessionExpired') return <SignInPrompt />;
return <button onClick={retry}>Reconnect ({status})</button>;
}

Watch Hooks

Each watch hook subscribes on mount, keeps state in sync with the underlying stream, and closes the stream on unmount (StrictMode-safe). They return the latest data plus a standardized status.

HookReturns
useArcaPrices(opts?){ prices, status } — all mids (or narrowed by coins)
useArcaPrice(coin){ price, status } — single-asset convenience
useArcaBalances(arcaRef?){ balances, status } — live balances Map
useArcaObject(path){ valuation, status } — one object's valuation
useArcaObjects(paths){ valuations, status } — merged Map
useArcaAggregation(sources){ aggregation, status }
useArcaExchangeState(objectId){ exchangeState, pendingIntents, status }
useArcaMaxOrderSize(opts){ activeAssetData, status } — live max-size sizing
useArcaFills(objectId){ fills, status } — merged trade feed
useArcaFunding(objectId){ payments, status } — funding events
useArcaOperations(){ operations, status }
useArcaTrades(coins){ trades, status } — public tape
useArcaTwap(exId, opId){ twap, status }
useArcaCandles(coins, intervals){ candles, status } — raw events
useArcaCandleChart(coin, interval){ candles, reachedStart, ensureRange, loadMore, status }
useArcaEquityChart(path, params){ points, status }
usePnlChart(path, params){ points, externalFlows, startingEquityUsd, status }
tsx
function AccountPanel({ objectId }: { objectId: string }) {
const { exchangeState, pendingIntents, status } = useArcaExchangeState(objectId);
if (status === 'loading') return <Spinner />;
return (
<div>
<div>Equity: {exchangeState?.marginSummary.accountValue}</div>
<div>{exchangeState?.positions?.length ?? 0} positions, {pendingIntents.length} pending</div>
</div>
);
}
function PriceChart({ market }: { market: string }) {
const { candles, loadMore, status } = useArcaCandleChart(market, '1m');
return <Candlestick data={candles} onScrollLeft={() => loadMore(200)} loading={status === 'loading'} />;
}

Query Hooks

One-shot REST reads. Each returns { data, error, loading, refetch }, re-runs when its arguments change, and preserves stale data across reloads.

HookWraps
useArcaPositions(objectId)listPositions
useArcaOrders(objectId, status?)listOrders
useArcaMarketMeta()getMarketMeta
useArcaSparklines()getSparklines
useArcaObjectData(objectId)getObjectDetail
useArcaBalancesByPath(objectId)getBalances
useArcaFillsHistory(objectId, opts?)listFills
useArcaPnl(path, from, to)getPnl
useArcaEquityHistory(path, from, to)getEquityHistory
useArcaOperationsList(opts?)listOperations
useArcaCustodyStatus()getCustodyStatus
tsx
function Positions({ objectId }: { objectId: string }) {
const { data, loading, refetch } = useArcaPositions(objectId);
if (loading && !data) return <Spinner />;
return (
<>
<button onClick={refetch}>Refresh</button>
<ul>{data?.map(p => <li key={p.market}>{p.market}: {p.size}</li>)}</ul>
</>
);
}

Mutation Hooks

Write hooks expose a status lifecycle: idle → submitting → pending → success | error, where pending means the HTTP call returned but the operation is still settling (via OperationHandle.wait()). Mutations that don't return an operation handle (e.g. useUpdateLeverage, useSetPositionTpsl) skip pending. Each returns mutate / mutateAsync, plus status, data, error, predicted, reset, and boolean flags.

HookAction
usePlaceOrder()placeOrder (+ live fills & orderHandle)
useCancelOrder()cancelOrder
useClosePosition()closePosition
useUpdateLeverage()updateLeverage
useSetPositionTpsl()setPositionTpsl
useTransfer()transfer (with predicted balance changes)
useEnsureArca() / useEnsureDenominatedArca()ensureArca / ensureDenominatedArca
useFundAccount() / useDefundAccount()fundAccount / defundAccount
usePlaceTwap() / useCancelTwap()placeTwap / cancelTwap
tsx
function BuyButton({ objectId }: { objectId: string }) {
const { placeOrder, status, fills } = usePlaceOrder();
const busy = status === 'submitting' || status === 'pending';
return (
<button
disabled={busy}
onClick={() => placeOrder({ objectId, path: '/op/order/buy-1', market: 'hl:0:BTC', side: 'buy', size: '0.01' })}
>
{busy ? 'Placing…' : `Buy (${fills.length} fills)`}
</button>
);
}
function SendButton() {
const { mutate, status, predicted } = useTransfer();
return (
<button onClick={() => mutate({ path: '/op/transfer/x', from: '/a', to: '/b', amount: '10' })}>
{status === 'pending' ? 'Settling…' : 'Send'}
{predicted && <Hint changes={predicted.balanceChanges} />}
</button>
);
}

Operation & Order Handles

If you already hold an OperationHandle or OrderHandle (for example, you called a mutation method imperatively and stored the handle), track it reactively with useOperationHandle(handle) or useOrderHandle(handle). Both report the same status lifecycle; useOrderHandle additionally accumulates live fills. Pass null for "no operation yet".

tsx
function OrderTracker() {
const arca = useArca();
const [order, setOrder] = useState<OrderHandle | null>(null);
const { status, fills, data } = useOrderHandle(order);
return (
<>
<button onClick={() => setOrder(arca.placeOrder({ /* ... */ }))}>Place</button>
<div>Status: {status}{fills.length} fills</div>
{/* order.cancel() / order.resize('0.02') remain available on the handle */}
</>
);
}

Escape Hatches

The Arca class has ~100 methods; the named hooks cover the common ones. For anything else, two generic hooks make every method one line away.

useArcaQuery

tsx
const { data, loading, refetch } = useArcaQuery(
(arca) => arca.listOrders(objectId, 'open'),
[objectId],
);

useArcaMutation

tsx
const { mutate, status, predicted } = useArcaMutation(
(arca, opts: TransferOptions) => arca.transfer(opts),
);
mutate({ path: '/op/transfer/x', from: '/a', to: '/b', amount: '10' });

useArcaMutation auto-detects operation handles: if the factory returns an OperationHandle/OrderHandle it tracks the full submitting → pending → success lifecycle and surfaces predicted; if it returns a plain promise it goes submitting → success.

Access the client directly. useArca() returns the underlying Arca instance for any call that doesn't need React state — perfect inside event handlers.