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
| Detail | Description |
|---|---|
| Weekly resets | All data — accounts, realms, objects, API keys, and operations — is wiped on a weekly schedule. Do not rely on data persistence. |
| Demo realms only | Production realms and real-money operations are not available during the preview. All activity uses simulated environments. |
| APIs may change | Endpoints, request/response shapes, and data schemas may evolve between resets without prior notice. |
| No SLA | The 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:
| Interface | Best for | Language |
|---|---|---|
| REST API | Direct HTTP integration, any language | Any (curl, Python, Go, etc.) |
| TypeScript SDK | Node.js / browser applications | TypeScript / JavaScript |
| CLI | Terminal workflows, scripting, testing | Go binary |
https://api.arcaos.io/api/v1For 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
| Capability | DIY | With Arca |
|---|---|---|
| Multi-account ledger with conservation invariants | Months of engineering | ensureDenominatedArca — one call |
| Idempotent async operations with audit trail | Custom queue + dedup logic | Built-in via operation paths and nonces |
| Real-time streaming balances and aggregation | WebSocket infra + cache layer | watchBalances / watchAggregation |
| Historical equity curves and P&L | Time-series DB + computation pipeline | getEquityHistory — one call |
| Correlated event trail across transfers and fills | Custom event system + correlation logic | Automatic via the correlation spine |
| Fee splitting and revenue tracking | Payment logic per integration | feeTargets configuration |
| Custody and on-chain settlement | Key management + signing infrastructure | Managed 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.
import Arca from '@arca-network/sdk';
const arca = new Arca({ apiKey: process.env.ARCA_API_KEY! });
// 1. Create two accountsconst 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 walletawait arca.fundAccount({ arcaRef: '/demo/wallet', amount: '10000', path: '/demo/fund',}).completion();
// 3. Transfer to exchangeconst { 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 tradeconst 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 balancesconst 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 upawait 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
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:
| Type | Purpose | Behavior |
|---|---|---|
demo | Quick exploration | Simulated flows, no real money, permissive defaults |
development | Active development | Simulated flows for day-to-day building |
testing | Automated testing / CI | Simulated flows for test suites and integration tests |
staging | Pre-production validation | Production-like behavior for final verification |
production | Live operations | Real 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:
arca_<prefix>_<secret> ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ visible only shown once; Arca stores SHA-256 hashThe 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:
| Type | Description |
|---|---|
denominated | Holds a balance in a specific denomination (e.g., USD, BTC) |
exchange | Facilitates swaps between denominations |
deposit | Inbound funding point |
withdrawal | Outbound disbursement point |
escrow | Conditional 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 → deleting → deleted. 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.
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 ofarriving + 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.
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.
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.
| Scenario | Flat (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
| Pattern | Example | Use case |
|---|---|---|
/<category>/<entity>/<account> | /users/alice/main | Multi-user apps — aggregate all users, or one user's accounts |
/<category>/<account> | /treasury/operating | Company treasury — aggregate all treasury accounts |
/<entity>/<account> | /alice/savings | Simple 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.
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
/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 realmTrading Platform
/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 venuesMarketplace
/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 merchantsMulti-Tenant SaaS
/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 assetsPath Rules & Reserved Segments
| Rule | Details |
|---|---|
| Paths are leaves | An 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 segments | Segments 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 restrictions | Object label keys cannot start with arca. or _ — those prefixes are reserved for platform metadata. |
Trailing / in aggregation | prefix=/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 Smithbreak when the user changes their name. Use stable identifiers:/users/abc123. - Parent + child path conflicts. If you create
/users/aliceas 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:
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:
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%):
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
| Metric | Value |
|---|---|
| Builder fee | 10 bps (0.10%) |
| Daily trades | 1,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:
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:
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.
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.
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.
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" }'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
npm install @arcaresearch/sdk2. Initialize the Client
Backend (API key): Use an API key for server-side code that creates objects, initiates transfers, and manages the realm.
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 IDawait 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.
import { Arca } from '@arcaresearch/sdk';
// Recommended: with automatic refreshconst 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 operationsconst stream = await arca.watchBalances();3. Ensure Wallets, Fund, Transfer
// Ensure two wallets exist (creates if missing, returns existing if matching)// Safe to call on every request — no need for separate create-then-get logicawait 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 balancesconst 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:
// 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 timeoutawait handle.wait({ timeout: 15000 });
// Batch multiple operations in parallelawait 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:
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.
import ArcaSDK
// Recommended: with automatic refreshlet 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
// 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 walletslet transfer = try await arca.transfer( path: "/op/transfer/fund-savings-1", from: "/wallets/main", to: "/wallets/savings", amount: "250.00")
// Check balanceslet 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
# Install via Homebrewbrew install arcaresearch/tap/arca
# Or build from sourcecd cli && go build -o arca .
# Interactive setup (login or signup, API key, realm selection)arca auth2. Create a Realm & API Key
# Create a demo realmarca realm create --name development --type demo
# Set it as the default realm for your profilearca config set local --realm development
# Create an API key for programmatic usearca 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
# Ensure two wallets exist (creates if missing, returns existing if matching)arca object ensure --path /wallets/main --denomination USDarca object ensure --path /wallets/savings --denomination USD
# Fund the account (simulated — settles after ~5 seconds)arca fund-account --ref /wallets/main --amount 1000sleep 6
# Transfer between walletsarca 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 jsonarca object balances <main-object-id> --output jsonReal-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.
// Recommended: real-time balance updatesconst 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 changesstream.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.
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 updatesAvailable Streams
| Method | Data | Use for |
|---|---|---|
watchBalances() | Balance snapshots + updates | Account balance display, vault dashboards |
watchOperations() | Operation creates + state changes | Activity feeds, settlement tracking |
watchObject(path) | Object valuation updates | Account equity, position monitoring |
watchPrices() | Mid prices for all assets | Price tickers, charts |
watchCandles() | OHLCV candle updates | Candlestick charts |
watchMaxOrderSize() | Live max order sizes | Order entry size sliders |
createAggregationWatch() | Aggregated portfolio valuation | Portfolio 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
| Aspect | Demo Realm | Production Realm |
|---|---|---|
| Funding | fundAccount() mints simulated funds instantly | createPaymentLink() — real deposits via hosted pages |
| Settlement | Simulated — instant, no real money moves | Real — funds move through custody pools on-chain |
| Persistence | Resets weekly | Permanent — objects, balances, and history never reset |
| Exchanges | Sim-exchange (simulated order matching) | Live exchange venues (real order execution) |
| API Keys | Development keys — OK to share within team | Production keys — treat as secrets, store securely |
| Reversibility | Can reset via portal | Operations 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 apoolAddress. The operation stays pending until real USDC arrives on-chain at that address. For user-facing deposit flows in production, usecreatePaymentLink()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.
// Production funding: create a deposit link for the userconst link = await arca.createPaymentLink({ objectId: userWalletId, direction: 'deposit', amount: '500', returnUrl: 'https://myapp.com/dashboard',});// Redirect the user to link.urlProduction Enablement
- 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. - 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.
- 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.
- 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.
- 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
| Error | Cause | Resolution |
|---|---|---|
UNAUTHENTICATED | Missing, 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. |
FORBIDDEN | Scoped 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_FOUND | Realm, 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_ERROR | Invalid 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. |
CONFLICT | An 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
// WRONG — creates a new operation on every retry attemptasync 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.
// CORRECT — same nonce on retry returns the original resultasync 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:
const arca = new Arca({ apiKey: 'ak_...', realm: 'my-realm' });await arca.ready(); // resolves realm slug → internal IDCalling 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:
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 anywatch*()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.