Arca/Documentation
Join Waitlist

Developer Preview

You are using the Arca Developer Preview.

This is an early-access release for exploring the API, SDK, and CLI. The platform is under active development — APIs, schema, and data structures may change at any time.

What to expect

DetailDescription
Weekly resetsAll data — accounts, realms, objects, API keys, and operations — is wiped on a weekly schedule. Do not rely on data persistence.
Demo realms onlyProduction realms and real-money operations are not available during the preview. All activity uses simulated environments.
APIs may changeEndpoints, request/response shapes, and data schemas may evolve between resets without prior notice.
No SLAThe preview environment may experience downtime for maintenance or deployments without advance warning.

What's available

  • REST API — Full access to all current endpoints for objects, operations, transfers, deposits, and exchange integration.
  • TypeScript SDK (@arcaresearch/sdk) — Type-safe client for Node.js and browser environments.
  • CLI — Command-line tool for scripting and interactive use.
  • Portal — Web dashboard for managing realms, exploring objects, and monitoring operations.
  • Real-time streaming — WebSocket event streams for live operation and balance updates.

What's not available yet

  • Production realms and real-money settlement
  • Webhooks
  • Named IAM policies and teams

Feedback

Your feedback shapes the platform. If you encounter issues, have feature requests, or want to share what you're building, reach out to us directly.

Arca Platform Documentation

Arca is the infrastructure layer for building financial applications. It provides a unified ledger, deterministic state management, and full explainability for every balance change — so you can build trading, payments, and portfolio products without stitching together disparate systems.

Arca exposes three equally supported interfaces. Pick whichever fits your workflow — or combine them:

InterfaceBest forLanguage
REST APIDirect HTTP integration, any languageAny (curl, Python, Go, etc.)
TypeScript SDKNode.js / browser applicationsTypeScript / JavaScript
CLITerminal workflows, scripting, testingGo binary
Base URL
https://api.arcaos.io/api/v1

For local development, the API is available at http://localhost:3052/api/v1 (proxied through the portal at http://localhost:3050/api/v1).

Pick Your Path

Why Arca

Building financial infrastructure from scratch with raw exchange APIs means months of engineering before your product handles its first real transaction. Arca gives you a production-grade ledger, real-time streaming, and custody out of the box.

What You'd Have to Build

CapabilityDIYWith Arca
Multi-account ledger with conservation invariantsMonths of engineeringensureDenominatedArca — one call
Idempotent async operations with audit trailCustom queue + dedup logicBuilt-in via operation paths and nonces
Real-time streaming balances and aggregationWebSocket infra + cache layerwatchBalances / watchAggregation
Historical equity curves and P&LTime-series DB + computation pipelinegetEquityHistory — one call
Correlated event trail across transfers and fillsCustom event system + correlation logicAutomatic via the correlation spine
Fee splitting and revenue trackingPayment logic per integrationfeeTargets configuration
Custody and on-chain settlementKey management + signing infrastructureManaged by Arca

See It in 50 Lines

This script creates two accounts, moves money between them, places a trade, and streams real-time balance updates — everything you need to evaluate whether Arca fits your product.

typescript
import Arca from '@arca-network/sdk';
const arca = new Arca({ apiKey: process.env.ARCA_API_KEY! });
// 1. Create two accounts
const wallet = await arca.ensureDenominatedArca({
ref: '/demo/wallet', denomination: 'USD',
nonce: (await arca.nonce('/demo/wallet')).nonce,
}).completion();
const exchange = await arca.ensurePerpsExchange({
ref: '/demo/exchange', exchange: 'sim',
nonce: (await arca.nonce('/demo/exchange')).nonce,
}).completion();
// 2. Fund the wallet
await arca.fundAccount({
arcaRef: '/demo/wallet', amount: '10000',
path: '/demo/fund',
}).completion();
// 3. Transfer to exchange
const { nonce: n1 } = await arca.nonce('/demo/to-exchange');
await arca.transfer({
from: wallet.arcaId, to: exchange.arcaId,
amount: '5000', nonce: n1,
}).completion();
// 4. Place a trade
const order = arca.placeOrder({
objectId: exchange.arcaId, coin: 'hl:BTC',
side: 'buy', size: '0.1',
nonce: (await arca.nonce('/demo/order')).nonce,
});
const fill = await order.waitForFill();
console.log('Filled:', fill.size, '@ $' + fill.price);
// 5. Stream real-time balances
const stream = await arca.watchBalances('/demo/');
stream.on('update', (balances) => {
for (const b of balances) {
console.log(b.path, 'settled:', b.settled, 'USD');
}
});
// Let it stream for a few seconds, then clean up
await new Promise(r => setTimeout(r, 5000));
stream.close();

Get your product live on Arca. Focus on your users and growth. Arca handles the ledger, custody, reconciliation, and real-time streaming — the infrastructure that takes months to build and years to get right.

Core Concepts

Coming from traditional web development?

  • Realm = your app's environment (like staging vs. production)
  • Arca Object = a user account that holds money
  • Transfer = a payment from one account to another
  • Operation = an async transaction the system processes
  • Nonce = a unique ID that prevents duplicate transactions
  • Path = a hierarchical address (like a file path) for organizing accounts
Your AppSDK / API callsArca APIPlatform serviceLedgerSpannerExchangesStreamingYour AppHow Arca Fits InYour app talks to the Arca API. Arca manages the ledger, connects to exchanges, and pushes real-time events back.

Builders

A Builder represents your organization. Each builder has an email, password (bcrypt-hashed), and organization name. Builders authenticate via JWT tokens to manage their resources through the portal or API.

Realms

Realms are isolated environments that scope all Arca data — objects, operations, events, and API keys. Realm types control defaults and behavior:

TypePurposeBehavior
demoQuick explorationSimulated flows, no real money, permissive defaults
developmentActive developmentSimulated flows for day-to-day building
testingAutomated testing / CISimulated flows for test suites and integration tests
stagingPre-production validationProduction-like behavior for final verification
productionLive operationsReal integrations, restrictive defaults, audit logging

Each realm gets a unique slug derived from its name (e.g., development, live-trading). Slugs are unique per builder.

API Keys

API keys provide programmatic access to Arca, scoped to a specific builder and realm. They are the authentication mechanism for SDKs and backend services (as opposed to JWT tokens, which are for portal/interactive use).

Key anatomy:

text
arca_<prefix>_<secret>
^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
visible only shown once; Arca stores SHA-256 hash

The prefix is always visible (used for identification in the portal and logs). The full key is shown only at creation time.

Signing Keys

Signing keys are optional Ed25519 keypairs that add non-repudiation to API operations. When you sign a request, Arca records the cryptographic signature on the operation, proving you authorized it with those exact parameters. Generate a keypair locally, register the public key via the portal or API, and include signature headers on your requests. See the Signing Key API reference and Platform guide for details.

Arca Objects

An Arca Object is a stateful entity on the ledger. Objects are addressed by hierarchical paths (e.g., /treasury/usd-reserve) and scoped to a realm. Each object has a type that determines its behavior:

TypeDescription
denominatedHolds a balance in a specific denomination (e.g., USD, BTC)
exchangeFacilitates swaps between denominations
depositInbound funding point
withdrawalOutbound disbursement point
escrowConditional hold of funds

Creating an Arca object atomically produces both the object record and a create operation, ensuring every state change is tracked from inception.

System-Owned Objects

Each realm contains platform-managed objects under the /_system/ and /_builder/ path prefixes. These absorb fees and reconciliation adjustments so that value is conserved exactly across every operation. System objects are visible in queries and the Explorer but cannot be created, modified, or deleted by builders.

Object Lifecycle & Deletion

Arca objects follow a status lifecycle: active deletingdeleted. Deletion is executed as a durable Temporal workflow that handles balance sweeps, exchange liquidation, and finalization. Objects with in-flight operations cannot be deleted until those operations reach a terminal state.

After deletion, the path becomes reusable — a new object can be created at the same path. Previous versions are viewable via the /versions endpoint. See the API Reference for the full deletion workflow, options (sweepToPath, liquidatePositions), and timeout behavior.

Operations

Operations represent intent — a requested action against one or more Arca objects. Each operation has a type (e.g., transfer, deposit, swap) and transitions through states: pending completed or failed. Operations form the backbone of the correlation spine: every event and state delta can be traced back to the operation that caused it. Each operation records who initiated it via actorType (one of builder, api_key, scoped_token, or system) and actorId, providing a full audit trail.

createdprocessingcompletedfailedexpiredOperations are asynchronous. Most complete in under a second.

Events

Events are immutable records of discrete occurrences within operations. Every state-mutating operation produces at least one event (e.g., object.created, transfer.completed, deposit.completed). State deltas attach exclusively to events. Events can be streamed in real time via WebSocket.

State Deltas

A State Delta captures the before/after value of a single field change on an Arca object. Delta types include balance_change, settlement_change, position_change, status_change, hold_change, creation, and deletion. Deltas are linked to the event that produced them (via eventId). The originating operation is reachable through the event's operationId, enabling full explainability of every state change.

Arca Balances

Balances track the value held by an Arca object in a given denomination. Both denominated and exchange objects have balances. Each balance exposes four fields that reflect the current state of funds, including any that are in transit:

  • settled — funds available for new operations (transfers, orders).
  • arriving — funds in transit toward this object that have not yet been confirmed by the destination (e.g., an exchange deposit awaiting venue confirmation).
  • departing — funds committed to leave this object via an outbound transfer that has not yet settled.
  • total — the sum of arriving + settled + departing. This is the complete value attributed to the object.

For ledger-to-ledger transfers (denominated → denominated), settlement is instant — arriving and departing are always 0. For transfers involving an exchange, funds pass through an in-flight phase and the arriving or departing fields will be non-zero until settlement completes.

Settlement Lifecycleinitiatedpoint ofno returnsecurearrivalcompletedfailurereversedAfter PNR, source funds are committed. Failures trigger compensating transactions.

Query balances per object via GET /api/v1/objects/:id/balances (see API Reference) or see them included in the object detail view. See the API Reference — Balance Model for presentation patterns showing in-flight funds to users.

Balance Modelarrivingin-transit inboundsettledavailable for usedepartingin-transit outboundtotal = arriving + settled + departingLedger-to-ledger transfers settle instantly (arriving/departing stay 0).Exchange transfers pass through arriving/departing until venue confirms.

Path Organization

Object paths are not just identifiers — they are your aggregation structure. The aggregation API sums balances for all objects sharing a path prefix, so the way you organize paths directly determines what rollups are possible.

Principle: Group What You Want to Aggregate

When you create multiple entities of the same kind, place them under a shared prefix. This lets you query aggregate balances across the entire group with a single API call.

ScenarioFlat (no aggregation)Grouped (recommended)
Multiple user accounts/user-1/home, /user-2/home/users/user-1/home, /users/user-2/home
Treasury accounts/usd-reserve, /operating/treasury/usd-reserve, /treasury/operating
Exchange accounts/hl-main, /hl-speculative/exchanges/main, /exchanges/speculative
Single entity, multiple accounts/checking, /savings/alice/checking, /alice/savings

Common Patterns

PatternExampleUse case
/<category>/<entity>/<account>/users/alice/mainMulti-user apps — aggregate all users, or one user's accounts
/<category>/<account>/treasury/operatingCompany treasury — aggregate all treasury accounts
/<entity>/<account>/alice/savingsSimple two-level setup for small or single-entity use cases

How Aggregation Works

Call GET /api/v1/objects/aggregate?prefix=/users/ to get the total equity, spot balances, and exchange positions across every object whose path starts with /users/. A single standalone account at the root (e.g., /main) is fine — grouping matters when you have collections of similar entities.

There are no explicit “directories” to create. Folders are inferred from path segments automatically — the Browse endpoint and the Explorer UI display them as a navigable tree.

Path Design Patterns

The path hierarchy section above covers the basics. This section shows concrete path structures for common use cases, explains reserved segments, and highlights anti-patterns.

Path Hierarchy & Aggregation/payments/payments/users/payments/fees/payments/users/alice/payments/users/bob↑ aggregation rolls upLeaf nodes hold balances. Query any prefix to get aggregated totals.GET /aggregation?path=/payments → total across all users + fees

Real-World Path Hierarchies

Each example shows a path tree, what aggregation returns at each prefix level, and the rationale behind the structure.

Payment App

text
/users/alice/wallet ← user's primary account
/users/alice/savings ← user's savings
/users/bob/wallet ← another user
/treasury/operating ← platform operating funds
/treasury/reserve ← platform reserve
Aggregation:
prefix=/users/alice/ → Alice's total assets (wallet + savings)
prefix=/users/ → all user assets combined
prefix=/treasury/ → platform reserves
prefix=/ → everything in the realm

Trading Platform

text
/traders/t1/spot ← trader 1's spot account
/traders/t1/perps/hl ← trader 1's perps on Hyperliquid
/traders/t1/perps/bn ← trader 1's perps on Binance
/traders/t2/spot ← trader 2
/exchanges/main ← platform's exchange object
Aggregation:
prefix=/traders/t1/ → trader 1's total P&L across all accounts
prefix=/traders/ → all trader exposure
prefix=/traders/t1/perps/ → trader 1's perps across venues

Marketplace

text
/merchants/m1/escrow ← merchant 1's held funds
/merchants/m1/settled ← merchant 1's settled revenue
/merchants/m2/escrow ← merchant 2
/platform/fees ← marketplace fee collection
Aggregation:
prefix=/merchants/m1/ → merchant 1's total balance
prefix=/merchants/ → total GMV across all merchants

Multi-Tenant SaaS

text
/tenants/acme/users/u1/main ← Acme user 1
/tenants/acme/users/u2/main ← Acme user 2
/tenants/globex/users/u3/main ← Globex user 3
Aggregation:
prefix=/tenants/acme/users/u1/ → single user's balance
prefix=/tenants/acme/users/ → all Acme users
prefix=/tenants/acme/ → all Acme assets
prefix=/tenants/ → all tenant assets

Path Rules & Reserved Segments

RuleDetails
Paths are leavesAn object path cannot be both an object and a prefix for another object. If /users/alice exists, you cannot create /users/alice/wallet (or vice versa).
/_system/Reserved for platform internals. Builders cannot create objects under this prefix. Transfers cannot use /_system/ as source or target.
/_builder/Reserved. Cannot be used as a transfer target. Source is allowed for settlement flows.
Dot-prefixed segmentsSegments starting with . are reserved for platform conventions. The only exception is /.info, used for info-type objects that store identity metadata alongside financial accounts.
Label key restrictionsObject label keys cannot start with arca. or _ — those prefixes are reserved for platform metadata.
Trailing / in aggregationprefix=/users/ aggregates the subtree (all objects under /users/). Without the trailing slash, it matches the exact path only.

Anti-Patterns

  • Flat roots without grouping. Creating all objects at the root (/alice, /bob, /treasury) means you cannot aggregate subsets. Put similar entities under a shared prefix: /users/alice, /users/bob.
  • Display names as path segments. Paths like /users/Alice Smith break when the user changes their name. Use stable identifiers: /users/abc123.
  • Parent + child path conflicts. If you create /users/alice as an object, you cannot later create /users/alice/wallet. Plan your hierarchy depth upfront.

Fee Economics

Arca has two fee types: a fixed platform fee on transfers and a configurable builder fee on exchange orders. Both are transparent and visible in operation details.

Transfer Fee

Every transfer between Arca objects incurs a fixed $0.05 USD fee per direction, paid by the sender. The fee is deducted automatically and recorded in the operation context as fee.amount.

Preview fees before executing with the SDK:

typescript
const estimate = await arca.estimateFee({ from: walletA, to: walletB, amount: '100' });
console.log(estimate.fee); // "0.05"

The transfer fee applies to transfer() operations. Dev helpers (fundAccount(), defundAccount()) do not incur fees.

Builder Fee (Exchange Orders)

Builders earn revenue by configuring a fee on exchange orders. The builder fee is measured in tenths of a basis point (e.g. 40 = 4 bps = 0.04%).

Set a default for your realm in realm settings (defaultBuilderFeeBps), or override per-order:

typescript
const order = await arca.placeOrder({
objectId: exchangeId,
coin: 'hl:BTC',
side: 'buy',
size: '0.1',
orderType: 'market',
builderFeeBps: 40, // 4 bps (0.04%) on this order
});

Builder fee revenue is deposited into your designated fee path automatically on each fill.

Fee Targets (Revenue Splitting)

Split builder fee revenue across multiple paths using feeTargets on placeOrder(). Each target specifies a path and a share in basis points (out of 10,000 = 100%):

typescript
const order = await arca.placeOrder({
objectId: exchangeId,
coin: 'hl:BTC',
side: 'buy',
size: '0.1',
orderType: 'market',
builderFeeBps: 40,
feeTargets: [
{ path: '/kols/alice', bps: 2000 }, // 20% to Alice
{ path: '/referrals/bob', bps: 1000 }, // 10% to Bob
// remaining 70% goes to the default fee path
],
});

Use this for KOL rev-share, referral programs, or platform-builder splits.

Revenue Example

MetricValue
Builder fee10 bps (0.10%)
Daily trades1,000
Average notional$10,000
Daily builder revenue$100 (1,000 × $10,000 × 0.001)

Use the CLI to check fee schedules and estimate fees: arca fees estimate and arca fees schedule. See the API Reference for per-asset fee details via GET /api/v1/exchange/:id/asset-fees.

Authentication

Arca supports two authentication mechanisms, each designed for a different context:

JWT Tokens (Interactive Use)

JWT tokens are issued when a builder signs in. They are used for portal access and direct API calls. Include the token in the Authorization header:

http
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Tokens contain the builderId and email claims, and expire after the configured TTL (default: 24 hours).

API Keys (Programmatic Use)

API keys are used by SDKs, backend services, and CI pipelines. They are team-scoped (work across all realms) and authenticated via the Authorization: Bearer header using the raw key:

http
Authorization: Bearer arca_78ae7276_178e3df83d9...

The server identifies the key by computing its SHA-256 hash and looking it up in the database. Only active (non-revoked) keys are accepted.

Scoped Tokens (End-User Access)

Scoped tokens are short-lived JWTs that your backend mints for your end-users via POST /api/v1/auth/token. Each token is locked to a single realm and carries IAM-style policy statements that control exactly which actions and resources the user can access. The recommended pattern is to issue read-only tokens to your frontend and route all writes through your backend. See the Scoped Tokens & Permissions reference and Architecture Patterns guide for details.

Signing Keys (Operation Signing)

For additional security, you can register an Ed25519 signing key and sign each API request. Signed operations create cryptographic proof that you authorized the action, protecting both you and Arca. Signing is currently optional but will become required for production realms in a future release. See the API reference and Platform guide.

Quick Start: API

Get up and running with Arca in three steps using raw HTTP requests.

1. Create a Builder Account

A Builder is the primary identity in Arca — it represents your organization. Sign up with an email, password, and organization name.

bash
curl -X POST http://localhost:3052/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "you@company.com",
"password": "your-secure-password",
"orgName": "Acme Trading"
}'

The response includes a JWT token and your builder profile. Save the token — you'll use it for all subsequent requests.

2. Create a Realm

Realms are isolated environments for your data. Start with a demo realm for testing. This step is required before using API keys with the SDK — without a realm, SDK calls will fail with Realm not found.

bash
curl -X POST http://localhost:3052/api/v1/realms \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Development",
"type": "demo",
"description": "My development environment"
}'

3. Generate an API Key

API Keys are team-scoped and work across all of your realms. They are used for programmatic access (SDKs, backend services, CI). The raw key is shown only once.

bash
curl -X POST http://localhost:3052/api/v1/api-keys \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Backend Service"
}'
Save your key

The rawKey field is returned only on creation. Store it securely — Arca stores only a SHA-256 hash internally and cannot recover the original key.

Quick Start: TypeScript SDK

Get up and running with the @arcaresearch/sdk package. This guide assumes you already have a builder account, realm, and API key (see the API Quick Start above).

1. Install the SDK

bash
npm install @arcaresearch/sdk

2. Initialize the Client

Backend (API key): Use an API key for server-side code that creates objects, initiates transfers, and manages the realm.

typescript
import { Arca } from '@arcaresearch/sdk';
const arca = new Arca({
apiKey: 'arca_78ae7276_...',
realm: 'development', // slug or UUID
baseUrl: 'http://localhost:3052', // optional, defaults to https://api.arcaos.io
});
// Resolve the realm slug to an ID
await arca.ready();

Frontend (scoped token): Use a scoped JWT minted by your backend for end-user-facing code. The realm is extracted from the token automatically. Pass a tokenProvider for automatic refresh before expiry.

typescript
import { Arca } from '@arcaresearch/sdk';
// Recommended: with automatic refresh
const arca = Arca.fromToken(scopedJwt, {
tokenProvider: async () => {
const res = await fetch('/api/arca-token');
return (await res.json()).token;
},
});
await arca.ready();
// Read-only: stream balances, read objects, watch operations
const stream = await arca.watchBalances();

3. Ensure Wallets, Fund, Transfer

typescript
// Ensure two wallets exist (creates if missing, returns existing if matching)
// Safe to call on every request — no need for separate create-then-get logic
await arca.ensureDenominatedArca({
ref: '/wallets/main',
denomination: 'USD',
});
await arca.ensureDenominatedArca({
ref: '/wallets/savings',
denomination: 'USD',
});
// Fund the account (simulated in demo realms)
// await waits for settlement (throws OperationFailedError on failure)
await arca.fundAccount({
arcaRef: '/wallets/main',
amount: '1000.00',
});
// Transfer between wallets (atomic, idempotent by path)
await arca.transfer({
path: '/op/transfer/fund-savings-1',
from: '/wallets/main',
to: '/wallets/savings',
amount: '250.00',
});
// Check balances
const obj = await arca.getObject('/wallets/main');
const balances = await arca.getBalances(obj.id);
console.log(balances); // [{ denomination: 'USD', amount: '750.00', ... }]

Operation Handles

Every mutation method (ensureDenominatedArca, fundAccount, transfer, etc.) returns an OperationHandle — a thenable object that awaits all the way to settlement. This means await arca.fundAccount(...) doesn't resolve until the operation has completed (or throws OperationFailedError on failure). No need to call waitForOperation separately.

For more control, the handle exposes additional properties:

typescript
// Get the HTTP response immediately (before settlement)
const handle = arca.fundAccount({ arcaRef: '/wallets/main', amount: '1000' });
const { poolAddress, operation } = await handle.submitted;
// Wait for settlement with an explicit timeout
await handle.wait({ timeout: 15000 });
// Batch multiple operations in parallel
await Promise.all([
arca.fundAccount({ arcaRef: '/wallets/main', amount: '500' }),
arca.fundAccount({ arcaRef: '/wallets/savings', amount: '300' }),
]);

Quick Start: Swift SDK

Get up and running with Arca in an iOS or macOS app. The Swift SDK authenticates with a scoped token issued by your backend. See the full Swift SDK Reference for all available methods.

1. Install via Swift Package Manager

Add the package to your project in Xcode (File → Add Package Dependencies) or in Package.swift:

swift
dependencies: [
.package(url: "https://github.com/arcaresearch/arca-swift-sdk.git", from: "0.1.0"),
]

2. Initialize from a Scoped Token

Your backend mints a scoped JWT via POST /auth/token and passes it to the iOS app. The realm is extracted from the token automatically. Pass a tokenProvider for automatic refresh before expiry.

swift
import ArcaSDK
// Recommended: with automatic refresh
let arca = try Arca(
token: scopedJwt,
tokenProvider: {
try await myBackend.getArcaToken()
}
)
// Or without automatic refresh (use updateToken() manually)
let arca = try Arca(token: scopedJwt)

3. Ensure Wallets, Fund, Transfer

swift
// Ensure two wallets exist (creates if missing, returns existing if matching)
let main = try await arca.ensureDenominatedArca(
ref: "/wallets/main",
denomination: "USD"
)
if let op = main.operation {
try await arca.waitForOperation(operationId: op.id.rawValue)
}
let savings = try await arca.ensureDenominatedArca(
ref: "/wallets/savings",
denomination: "USD"
)
if let op = savings.operation {
try await arca.waitForOperation(operationId: op.id.rawValue)
}
// Fund the account (simulated in demo realms)
let fundResult = try await arca.fundAccount(
arcaRef: "/wallets/main",
amount: "1000.00"
)
// Wait for funding to settle (throws ArcaError.operationFailed on failure)
try await arca.waitForOperation(
operationId: fundResult.operation.id.rawValue
)
// Transfer between wallets
let transfer = try await arca.transfer(
path: "/op/transfer/fund-savings-1",
from: "/wallets/main",
to: "/wallets/savings",
amount: "250.00"
)
// Check balances
let balances = try await arca.getBalancesByPath(path: "/wallets/main")
print(balances) // [ArcaBalance(denomination: "USD", amount: "750.00")]

Quick Start: CLI

Get up and running with the Arca CLI. Build the binary from source and work through the same onboarding flow.

1. Install & Authenticate

bash
# Install via Homebrew
brew install arcaresearch/tap/arca
# Or build from source
cd cli && go build -o arca .
# Interactive setup (login or signup, API key, realm selection)
arca auth

2. Create a Realm & API Key

bash
# Create a demo realm
arca realm create --name development --type demo
# Set it as the default realm for your profile
arca config set local --realm development
# Create an API key for programmatic use
arca api-key create --name "cli-test"
# Save the key in your profile (optional)
arca config set local --api-key <raw-key-from-output>

3. Ensure Wallets, Fund, Transfer

bash
# Ensure two wallets exist (creates if missing, returns existing if matching)
arca object ensure --path /wallets/main --denomination USD
arca object ensure --path /wallets/savings --denomination USD
# Fund the account (simulated — settles after ~5 seconds)
arca fund-account --ref /wallets/main --amount 1000
sleep 6
# Transfer between wallets
arca transfer --path /op/transfer/fund-1 \
--from /wallets/main \
--to /wallets/savings \
--amount 250
# Verify balances (use --output json for machine-parseable output)
arca object list --output json
arca object balances <main-object-id> --output json

Real-time Updates

Arca streams every state change — balances, operations, exchange fills, prices — over WebSocket in real time. For any UI or service that displays live data, use the SDK's watch*() methods instead of polling REST endpoints.

Real-time Balances

Use watchBalances() for live balance display. getBalancesByPath() is for one-shot reads only.

typescript
// Recommended: real-time balance updates
const stream = await arca.watchBalances('/wallets/main');
// stream.balances → initial snapshot (Map of object ID → balances)
stream.onUpdate(({ entityPath, balances }) => {
renderBalance(entityPath, balances);
});
// Clean up when the component unmounts or the view changes
stream.close();

Portfolio Aggregation

Use createAggregationWatch() to get real-time total equity across any set of objects — by prefix, glob pattern, or explicit path list. A single watch can power an entire portfolio dashboard.

typescript
const { watchId, aggregation } = await arca.createAggregationWatch([
{ type: 'prefix', value: '/users/alice/' },
]);
console.log(aggregation.totalEquityUsd);
// Subscribe to aggregation.updated events via arca.ws for push updates

Available Streams

MethodDataUse for
watchBalances()Balance snapshots + updatesAccount balance display, vault dashboards
watchOperations()Operation creates + state changesActivity feeds, settlement tracking
watchObject(path)Object valuation updatesAccount equity, position monitoring
watchPrices()Mid prices for all assetsPrice tickers, charts
watchCandles()OHLCV candle updatesCandlestick charts
watchMaxOrderSize()Live max order sizesOrder entry size sliders
createAggregationWatch()Aggregated portfolio valuationPortfolio dashboards, total equity displays

All streaming methods work with both API keys and scoped JWT tokens. See the SDK: Real-time Streaming reference for full API details, types, and cleanup patterns.

Demo to Production

Your demo integration is working — what happens next? This section covers what changes when you go live, what Arca handles for you, and how to prepare.

What Arca Handles

Arca is a managed platform. You don't deploy Arca, monitor it, or run operational procedures. Arca handles:

  • Infrastructure — compute, storage, networking, and scaling. No servers to provision.
  • Monitoring & Alerts — system health, latency, error rates, and financial invariants are continuously monitored.
  • Incident Response — if something goes wrong, Arca's on-call engineering team responds.
  • Financial Integrity — conservation-of-value checks, solvency verification, and venue reconciliation run automatically.
  • Custody Operations — private key management, transaction signing, and on-chain settlement.
  • Data Durability — all operations, events, and state deltas are stored in Spanner with strong consistency guarantees.

What Changes in Production

AspectDemo RealmProduction Realm
FundingfundAccount() mints simulated funds instantlycreatePaymentLink() — real deposits via hosted pages
SettlementSimulated — instant, no real money movesReal — funds move through custody pools on-chain
PersistenceResets weeklyPermanent — objects, balances, and history never reset
ExchangesSim-exchange (simulated order matching)Live exchange venues (real order execution)
API KeysDevelopment keys — OK to share within teamProduction keys — treat as secrets, store securely
ReversibilityCan reset via portalOperations are permanent — mistakes require compensating transactions

How Funding Works by Realm Type

fundAccount() behaves differently depending on realm type:

  • Demo / development / testing realms: fundAccount() auto-mints simulated tokens and settles within seconds. No real funds move.
  • Production realms: fundAccount() creates a deposit intent with a poolAddress. The operation stays pending until real USDC arrives on-chain at that address. For user-facing deposit flows in production, use createPaymentLink() instead.

In production, the recommended path is payment links. Call createPaymentLink() to generate a hosted page where your end user can deposit (or withdraw) real funds. Arca handles KYC, payment processing, and settlement automatically. The user visits the hosted URL, completes the flow, and their Arca object balance updates when the deposit settles.

typescript
// Production funding: create a deposit link for the user
const link = await arca.createPaymentLink({
objectId: userWalletId,
direction: 'deposit',
amount: '500',
returnUrl: 'https://myapp.com/dashboard',
});
// Redirect the user to link.url

Production Enablement

  1. Build and test in a demo realm. Complete your integration using fundAccount() and simulated settlement. All SDK methods, streaming, and exchange operations work the same way in demo and production.
  2. Contact Arca to enable production. Production realms are currently enabled on request. Arca will review your integration, discuss custody setup, and provision your production environment.
  3. Complete the Production Readiness Checklist. Covers API key hygiene, scoped token configuration, fee setup, payment link integration, path hierarchy design, error handling, real-time monitoring, and the Security Pre-flight Checklist.
  4. Run parallel testing. Before going live, run your production integration against a dedicated test realm with real venue connectivity. Verify deposits, transfers, and exchange operations end-to-end.
  5. Go live. Switch your application to production API keys. Your code stays the same — only the realm, API key, and funding method change.

For Your Legal Team

If your legal team needs to understand Arca's custody model, key management, and builder-vs-platform responsibilities, see the Custody Model section on the Platform page. It covers the three-layer architecture, who controls keys, regulatory considerations, and how Arca compares to other infrastructure providers.

Troubleshooting

Common errors and how to resolve them. For the full error code catalog, see the Platform: Error Handling reference.

Common Errors

ErrorCauseResolution
UNAUTHENTICATEDMissing, expired, or invalid API key / JWT token.Verify your API key or scoped token is correct. For scoped tokens, check expiry and refresh with Arca.fromTokenProvider() for automatic renewal. Ensure await arca.ready() completed successfully.
FORBIDDENScoped token lacks the required action or resource permission.Check which action the endpoint requires (e.g. arca:Transfer, arca:Read) and verify your token scope includes it. Use GET /api/v1/auth/scope to inspect what your current credential can access. See Scoped Tokens & Permissions.
NOT_FOUNDRealm, object, or operation does not exist.For “realm not found,” verify the realm slug is correct and that the realm exists with GET /api/v1/realms. For objects, check the path format (paths start with /).
VALIDATION_ERRORInvalid request body — missing required fields, wrong types, or invalid values.Check the error message for field-specific details. Common causes: missing nonce, invalid amount format (must be a string like "100"), or unsupported object type.
CONFLICTAn operation with this path already exists but with different inputs (idempotency violation).This is working as intended — the idempotency contract rejects conflicting replays. If you intended a new operation, generate a new nonce with arca.nonce(path). If you intended a retry, use the same inputs as the original call.
OperationFailedError (SDK)An awaited operation reached the failed terminal state.Check error.operation.failureReason for the specific cause. Common reasons: insufficient balance, exchange rejection, or settlement failure. The original operation is immutable — create a new operation to retry.

Operation Lifecycle

Operations are asynchronous — they move through created → processing → completed/failed/expired. Most operations complete in under a second, but settlement can take longer.

Operation appears stuck in “pending”

Operations process asynchronously through a workflow engine. If an operation stays in created or processing for more than a few seconds, it may be waiting on external settlement. Use arca.watchOperations() to receive a push notification when state changes, or call arca.getOperation(operationId) to check the current state.

When an operation fails, the platform records the reason and — for transfers and deposits — automatically creates compensating transactions to restore balances. Check failureReason on the operation detail.

Nonce & Idempotency

Every mutation (create object, transfer, place order) requires a unique nonce that forms the operation's path. This is the idempotency key — sending the same path with the same inputs returns the original result instead of creating a duplicate.

Anti-pattern: generating a new nonce on each retry

typescript
// WRONG — creates a new operation on every retry attempt
async function transferWithRetry() {
for (let i = 0; i < 3; i++) {
try {
const { nonce } = await arca.nonce('/transfers/payment'); // new nonce each time!
return await arca.transfer({ from, to, amount: '50', nonce });
} catch (e) { /* retry */ }
}
}

The correct pattern: generate the nonce once, then reuse it on retries.

typescript
// CORRECT — same nonce on retry returns the original result
async function transferWithRetry() {
const { nonce } = await arca.nonce('/transfers/payment'); // generate once
for (let i = 0; i < 3; i++) {
try {
return await arca.transfer({ from, to, amount: '50', nonce });
} catch (e) {
if (i === 2) throw e; // final attempt failed
}
}
}

SDK Initialization

The SDK requires a one-time initialization step before making API calls:

typescript
const arca = new Arca({ apiKey: 'ak_...', realm: 'my-realm' });
await arca.ready(); // resolves realm slug → internal ID

Calling SDK methods before ready() completes will fail with a realm resolution error. For scoped tokens, use Arca.fromTokenProvider() to handle automatic token refresh before expiry:

typescript
const arca = Arca.fromTokenProvider(async () => {
const res = await fetch('/api/arca-token');
return (await res.json()).token;
});
await arca.ready();

WebSocket & Streaming

If you're not receiving expected events from watch streams:

  • Verify await arca.ready() completed before calling any watch*() method.
  • Use canonical coin IDs for market data subscriptions — "hl:BTC", not "BTC". The server rejects bare symbols.
  • The SDK auto-reconnects on WebSocket disconnection. For critical financial events (balance.updated, exchange.fill), the SDK detects sequence gaps and fetches missed data via REST automatically.
  • Always call stream.close() when you're done with a watch stream to clean up the WebSocket subscription and prevent memory leaks.