Arca/Documentation
Portal

SDK: Installation & Setup

The @arca/sdk package provides a high-level, typed, idempotent interface for building on Arca. It requires Node.js 18+ and is written in TypeScript (ES2022).

Installation

bash
npm install @arca/sdk

Package Exports

The package exports one main class and all supporting types:

typescript
import { Arca } from '@arca/sdk';
// Types are also exported for strong typing
import type {
ArcaObject,
ArcaBalance,
Operation,
TransferOptions,
PlaceOrderOptions,
RealmEvent,
// ... 70+ exported types
} from '@arca/sdk';

Initialization

The SDK supports two authentication modes. In both cases, call arca.ready() to resolve the realm before making API calls (it is also called automatically on the first API call).

typescript
// API Key — for backend services, scripts, CI
const arca = new Arca({
apiKey: 'arca_78ae7276_...',
realm: 'development', // slug or UUID
baseUrl: 'http://localhost:8080', // optional
});
await arca.ready();
// Scoped Token — for end-user frontends
const arca = Arca.fromToken(scopedJwt);
// realm is extracted from the token claims automatically
await arca.ready();

SDK: Authentication

The SDK mirrors the platform's dual-auth model. Choose the right mode for your context:

API Key (Backend)

API keys are team-scoped with full access across all realms. Use this for server-side code, scripts, and CI pipelines.

typescript
import { Arca } from '@arca/sdk';
const arca = new Arca({
apiKey: 'arca_78ae7276_...',
realm: 'development',
});
apiKeystringrequired
Full API key including prefix and secret.
realmstringrequired
Realm slug (e.g., development) or UUID.
baseUrlstring
API base URL. Default: https://api.arca.dev.

Scoped Token (Frontend)

Scoped tokens are short-lived JWTs minted by the builder's backend via POST /auth/token. They restrict access to specific paths and permissions within a single realm.

typescript
// The builder's backend mints this token and passes it to the frontend
const arca = Arca.fromToken(scopedJwt);
// The realm is extracted from the token claims automatically
await arca.ready();

If the token does not embed a realmId claim, provide it explicitly:

typescript
const arca = Arca.fromToken(scopedJwt, { realm: 'development' });
tokenstringrequired
Scoped JWT issued by POST /auth/token.
realmstring
Realm slug or UUID. Omit if the token contains a realmId claim.
baseUrlstring
API base URL. Default: https://api.arca.dev.

Realm Resolution

The SDK accepts realm slugs (e.g., development) and resolves them to UUIDs on arca.ready(). If the input is already a UUID, no resolution call is made. For scoped tokens, the realmId claim is extracted from the JWT payload directly.

SDK: Arca Objects

createDenominatedArca(opts)

Create a denominated Arca object at the given path. Idempotent — if one already exists with matching type and denomination, it is returned.

typescript
const { object, operation } = await arca.createDenominatedArca({
ref: '/wallets/main',
denomination: 'USD',
operationPath: '/op/create/wallets/main:1', // optional idempotency key
});
refstringrequired
Full Arca path.
denominationstringrequired
Currency or asset denomination (e.g., USD, BTC).
metadatastring
Optional JSON metadata string.
operationPathstring
Operation path for explicit idempotency (use the nonce API to generate).

createArca(opts)

Create an Arca object of any type at the given path.

typescript
const { object } = await arca.createArca({
ref: '/deposits/d1',
type: 'deposit',
denomination: 'USD',
});
refstringrequired
Full Arca path.
typestringrequired
One of: denominated, exchange, deposit, withdrawal, escrow.
denominationstring
Required for denominated type.
metadatastring
Optional JSON metadata string.
operationPathstring
Optional operation-level idempotency key.

getObject(path)

Get the active Arca object at a given path.

typescript
const obj: ArcaObject = await arca.getObject('/wallets/main');
console.log(obj.id, obj.status, obj.denomination);

getObjectDetail(objectId)

Get full object detail including operations, events, state deltas, and balances.

typescript
const detail = await arca.getObjectDetail(obj.id);
console.log(detail.operations.length); // all operations on this object
console.log(detail.balances); // current balances
console.log(detail.reservedBalances); // in-flight reserved amounts

listObjects(opts?)

List Arca objects in the realm, optionally filtered by path prefix.

typescript
const { objects, total } = await arca.listObjects({
prefix: '/wallets',
includeDeleted: false, // default
});

getBalances(objectId)

Get current balances for an Arca object.

typescript
const balances: ArcaBalance[] = await arca.getBalances(obj.id);
// [{ id: '...', arcaId: '...', denomination: 'USD', amount: '1000.00' }]

ensureDeleted(opts)

Delete an Arca object by path. If the object has remaining balances, provide a sweep target. For exchange objects with open positions, set liquidatePositions: true to close them via market order first. Deletion is blocked if the object has in-flight operations.

typescript
// Simple deletion (no balance)
const { object, operation } = await arca.ensureDeleted({
ref: '/wallets/old',
});
// Deletion with balance sweep
const { object, operation } = await arca.ensureDeleted({
ref: '/wallets/old',
sweepTo: '/wallets/main',
});
// Exchange deletion with position liquidation
const { object, operation } = await arca.ensureDeleted({
ref: '/exchanges/hl1',
sweepTo: '/wallets/main',
liquidatePositions: true,
});
refstringrequired
Full Arca path to delete.
sweepTostring
Arca path to sweep remaining funds into before deletion.

SDK: Transfers & Deposits

transfer(opts)

Execute a transfer between two Arca objects. The operation path serves as the idempotency key — resubmitting the same path returns the existing result. Settlement is immediate for denominated targets or async for exchange targets.

typescript
const { operation } = await arca.transfer({
path: '/op/transfer/alice-to-bob-1',
from: '/wallets/alice',
to: '/wallets/bob',
amount: '250.00',
});
console.log(operation.state); // 'completed' for denominated→denominated
console.log(operation.outcome); // JSON with new balances
pathstringrequired
Operation path (idempotency key). Must be unique per realm.
fromstringrequired
Source Arca object path to debit.
tostringrequired
Target Arca object path to credit.
amountstringrequired
Transfer amount as a decimal string.

deposit(opts)

Initiate a deposit to a denominated Arca object. In demo realms, deposits are simulated with a configurable delay and success/failure.

typescript
const { operation } = await arca.deposit({
arcaRef: '/wallets/main',
amount: '1000.00',
durationSeconds: 5, // default: 5
willSucceed: true, // default: true
});
// operation.state === 'pending' (settlement happens asynchronously)
// Listen for deposit.completed event via events.subscribe()
arcaRefstringrequired
Target Arca object path.
amountstringrequired
Deposit amount as a decimal string.
durationSecondsnumber
Simulated processing time in seconds. Default: 5.
willSucceedboolean
Whether the simulated deposit succeeds. Default: true.

SDK: Operations & Events

getOperation(operationId)

Get operation detail by ID, including correlated events and state deltas.

typescript
const detail = await arca.getOperation(operationId);
console.log(detail.operation.type); // 'transfer', 'deposit', etc.
console.log(detail.operation.state); // 'pending', 'completed', 'failed'
console.log(detail.events); // correlated events
console.log(detail.deltas); // state deltas (before/after values)

listOperations(opts?)

List operations in the realm, optionally filtered by type.

typescript
const { operations, total } = await arca.listOperations({
type: 'transfer', // optional filter
});
typeOperationType
Filter by type: transfer, create, delete, deposit, withdrawal, swap, order, cancel.

nonce(prefix, separator?)

Reserve the next unique nonce for a path prefix. Returns a path suitable for use as an idempotency key on operations or object creation.

typescript
// Default separator (hyphen)
const { nonce, path } = await arca.nonce('/op/transfer/fund');
// { nonce: 1, path: '/op/transfer/fund-1' }
// Colon separator for create-operation nonces
const { path: opPath } = await arca.nonce('/op/create/wallets/main', ':');
// '/op/create/wallets/main:1'
// Use the reserved path as the idempotency key
await arca.transfer({ path, from: '/wallets/alice', to: '/wallets/bob', amount: '100' });
prefixstringrequired
Path prefix (e.g., /op/transfer/fund).
separatorstring
Override separator between prefix and nonce number. Default: / if prefix ends with /, otherwise -. Use ":" for operation nonces.
Reserve before you operate

Always call nonce() before the operation and store the resulting path. On retry, reuse the stored path — that is what makes the operation idempotent. Never call nonce() inline inside an operation call or inside a retry loop; each invocation produces a new unique path, turning every retry into a distinct operation.

typescript
// GOOD — reserve once, store, reuse on retry
const { path } = await arca.nonce('/op/transfer/fund');
// persist `path` in your application state if needed
await arca.transfer({ path, from: '/wallets/a', to: '/wallets/b', amount: '100' });
// BAD — new nonce on every call defeats idempotency
await arca.transfer({
path: (await arca.nonce('/op/transfer/fund')).path, // different path every time!
from: '/wallets/a',
to: '/wallets/b',
amount: '100',
});

summary()

Get aggregate counts for the realm.

typescript
const summary = await arca.summary();
console.log(summary.objectCount); // 12
console.log(summary.operationCount); // 47
console.log(summary.eventCount); // 93

SDK: Exchange (Perps)

Trade simulated perpetual futures on exchange Arca objects. These methods wrap the sim-exchange API and follow the same idempotency contract as other operations.

createPerpsExchange(opts)

Create a perps exchange Arca object. Denomination is automatically set to USD.

typescript
const { object } = await arca.createPerpsExchange({
ref: '/exchanges/hl1',
exchangeType: 'hyperliquid', // default
});
refstringrequired
Full Arca path for the exchange object.
exchangeTypestring
Exchange provider. Currently only hyperliquid.
operationPathstring
Optional idempotency key.

getExchangeState(objectId)

Get exchange account state including equity, margin, positions, and open orders.

typescript
const state: ExchangeState = await arca.getExchangeState(objectId);
console.log(state.account.usdBalance);
console.log(state.marginSummary.accountValue);
console.log(state.positions); // SimPosition[]
console.log(state.openOrders); // SimOrder[]

updateLeverage(opts)

Set the leverage for a coin. Leverage is a per-coin setting, not per-order. Changing leverage re-margins any existing position. Rejects if insufficient margin.

typescript
await arca.updateLeverage({
objectId: exchangeId,
coin: 'BTC',
leverage: 10,
});
objectIdstringrequired
Exchange Arca object ID.
coinstringrequired
Coin to set leverage for.
leveragenumberrequired
Leverage multiplier (1 to maxLeverage).

placeOrder(opts)

Place a market or limit order. The path is the idempotency key. Orders use the leverage currently set for the coin.

typescript
const { operation } = await arca.placeOrder({
path: '/op/order/btc-buy-1',
objectId: exchangeId,
coin: 'BTC',
side: 'BUY',
orderType: 'MARKET',
size: '0.01',
});
pathstringrequired
Operation path (idempotency key).
objectIdstringrequired
Exchange Arca object ID.
coinstringrequired
Coin to trade (e.g., BTC, ETH).
side'BUY' | 'SELL'required
Order side.
orderType'MARKET' | 'LIMIT'required
Order type.
sizestringrequired
Order size as a decimal string.
pricestring
Limit price. Required for LIMIT orders.
reduceOnlyboolean
Only reduce an existing position. Default: false.
timeInForcestring
Time in force: GTC (default), IOC, ALO.

listOrders(objectId, status?)

List orders for an exchange Arca object, optionally filtered by status.

typescript
const orders: SimOrder[] = await arca.listOrders(exchangeId);
const openOrders = await arca.listOrders(exchangeId, 'OPEN');

getOrder(objectId, orderId)

Get a specific order with its fill history.

typescript
const { order, fills }: SimOrderWithFills = await arca.getOrder(exchangeId, orderId);
console.log(order.status); // 'FILLED'
console.log(fills.length); // number of fills

cancelOrder(opts)

Cancel an open order. The path is the idempotency key.

typescript
const { operation } = await arca.cancelOrder({
path: '/op/cancel/btc-1',
objectId: exchangeId,
orderId: orderId,
});
pathstringrequired
Operation path (idempotency key).
objectIdstringrequired
Exchange Arca object ID.
orderIdstringrequired
ID of the order to cancel.

listPositions(objectId)

List current positions for an exchange Arca object.

typescript
const positions: SimPosition[] = await arca.listPositions(exchangeId);
for (const pos of positions) {
console.log(pos.coin, pos.side, pos.size, pos.unrealizedPnl);
}

Market Data

Global market data endpoints — not scoped to a specific exchange object.

typescript
// Market metadata (supported assets, max leverage, etc.)
const meta: SimMetaResponse = await arca.getMarketMeta();
// { universe: [{ name: 'BTC', maxLeverage: 50, ... }, ...] }
// Current mid prices
const mids: SimMidsResponse = await arca.getMarketMids();
// { mids: { 'BTC': '98234.50', 'ETH': '2847.30', ... } }
// L2 order book
const book: SimBookResponse = await arca.getOrderBook('BTC');
// { coin: 'BTC', bids: [...], asks: [...], time: 1234567890 }

SDK: Real-time Streaming

Subscribe to real-time realm events via Server-Sent Events (SSE). The SDK manages the EventSource connection and provides client-side prefix filtering.

events.subscribe(opts, callback)

typescript
// Subscribe to all events
const sub = arca.events.subscribe((event: RealmEvent) => {
console.log(event.type, event.entityId, event.entityPath);
});
// Subscribe with prefix filter (client-side)
const sub = arca.events.subscribe(
{ prefix: '/op/' },
(event) => {
console.log('Operation event:', event.type);
},
);
// Close the subscription when done
sub.close();

Event Types

The following event types are emitted by the stream:

Event TypeDescription
object.createdNew Arca object created (carries creation delta)
object.updatedObject metadata or properties changed
object.deletedObject finalized as deleted (carries status_change + deletion deltas)
object.status_changedObject status transitioned (e.g., active → deleting, or deleting → active on revert)
operation.createdNew operation created
operation.updatedOperation state changed
event.createdNew event recorded
balance.updatedBalance changed on an object
deposit.initiatedDeposit initiated, inbound hold created (carries hold_change delta)
deposit.completedDeposit settled successfully (carries balance_change + hold_change deltas)
deposit.failedDeposit failed (carries hold_change delta for inbound hold cancellation)
transfer.initiatedAsync transfer source debit recorded (carries balance_change + hold_change deltas)
transfer.outbound_releasedOutbound hold released during settlement (carries hold_change delta)
transfer.completedTransfer settled (carries balance_change + hold_change deltas)
transfer.failedTransfer failed, funds returned to source (carries reversal balance_change + hold_change deltas)
order.filledExchange order filled (carries settlement_change + position_change deltas)
order.cancelledExchange order cancelled (informational, no deltas)
sweep.completedBalance sweep during deletion (carries balance_change deltas for source + target)
withdrawal.completedExchange funds withdrawn to Arca balance (carries balance_change delta)
liquidation.completedExchange positions liquidated (carries settlement_change + position_change deltas)

RealmEvent Shape

typescript
interface RealmEvent {
realmId: string;
type: string; // e.g. 'deposit.completed'
entityId: string; // ID of the affected entity
entityPath: string; // path of the affected entity
}

Subscription Options

prefixstring
Optional path prefix to filter events client-side. Only events where entityPath starts with this prefix are delivered to the callback.

SDK: Error Handling

The SDK maps API error responses to typed error classes. All errors extend ArcaError, which provides a machine-readable code and a human-readable message.

Error Classes

ClassCodeHTTP StatusWhen
ValidationErrorVALIDATION_ERROR400Invalid input
UnauthorizedErrorUNAUTHORIZED401Missing or invalid auth
NotFoundErrorNOT_FOUND404Resource not found
ConflictErrorCONFLICT409Duplicate or invalid state
InternalErrorINTERNAL_ERROR500Unexpected server error

Usage Pattern

typescript
import { Arca, ValidationError, NotFoundError, ConflictError } from '@arca/sdk';
try {
await arca.transfer({
path: '/op/transfer/fund-1',
from: '/wallets/alice',
to: '/wallets/bob',
amount: '250.00',
});
} catch (err) {
if (err instanceof ValidationError) {
console.error('Invalid input:', err.message);
// e.g., insufficient balance, denomination mismatch
} else if (err instanceof NotFoundError) {
console.error('Object not found:', err.message);
} else if (err instanceof ConflictError) {
console.error('Conflict:', err.message);
// e.g., duplicate resource, object being deleted
} else {
throw err; // unexpected
}
}

Base Error Class

typescript
class ArcaError extends Error {
public readonly code: string; // machine-readable code
public readonly message: string; // human-readable description
}

All error classes (ValidationError, UnauthorizedError, etc.) extend ArcaError and set the appropriate code. Unknown error codes are returned as a base ArcaError instance.

SDK: Org & Team Management

The ArcaAdmin class provides methods for managing your organization, team members, and invitations.

Get Organization

typescript
const admin = new ArcaAdmin({ token });
const org = await admin.getOrg();
console.log(org.name, org.slug);

List Members

typescript
const { members, total } = await admin.listMembers();
for (const m of members) {
console.log(m.email, m.role, m.realmTypeScope);
}

Invite a Member

typescript
const invitation = await admin.inviteMember({
email: 'alice@example.com',
role: 'developer',
realmTypeScope: ['development', 'staging'],
});
console.log(invitation.inviteLink);

Update Member Role

typescript
await admin.updateMember('member-id', {
role: 'admin',
realmTypeScope: null, // null = all realm types
});

Remove Member

typescript
await admin.removeMember('member-id');

Transfer Ownership

typescript
await admin.transferOwnership('new-owner-user-id');