Arca/Documentation
Join Waitlist

Changelog

Notable changes to the Arca platform, SDKs, and API. Breaking changes are marked with a ⚠️ icon.

2026-06-09

  • Kotlin SDK fix: embedded R8 consumer rules were invalid. v1.0.0's META-INF/proguard/arca-sdk.pro backreferenced a second wildcard group (<2>) off a pattern that captures only one, so any Android consumer with isMinifyEnabled = true failed at minifyReleaseWithR8 with "Wildcard <2> is invalid". v1.0.1 ships corrected rules following the official kotlinx-serialization template (reconstructing the class name as network.arca.sdk.<1>); consumers carrying app-side workaround rules can delete them.
  • All SDKs: chart streams stamp the SDK-appended live point with status: open. watchEquityChart / watchPnlChart merge server history buckets with a client-appended live tip derived from the live aggregation (equity including unrealized P&L on open positions). Every synthetic live point now carries status: open — in TypeScript, Swift, and Kotlin alike — so clients can identify, restyle, drop, or re-anchor the live tip explicitly instead of inferring it from its timestamp.
  • Kotlin SDK: watch-stream snapshot replay + initial candle-chart emit. Snapshot-typed updates flows (prices, valuations, aggregation, exchange state, max order size, equity/P&L/candle charts) now replay the latest snapshot to collectors that attach after the stream is created, matching the Swift SDK's buffered streams; watchCandleChart additionally emits its initial REST history snapshot through updates / onUpdate, so a pure-updates consumer renders the chart without waiting for the first live candle. Event-typed flows (operations, balances, fills, funding) are unchanged and do not replay. The library jar now also embeds consumer R8/ProGuard rules (META-INF/proguard), so Android apps with minification enabled need no manual keep rules.
  • ⚠️ Removed: v1 custody contract (ArcaCustodyPool) and its admin surface. Following the production reset onto the v2 custody stack (custody kernel + coordinator + per-venue modules + per-arca vaults), the legacy v1 pool contract and everything that interacted with it have been removed. Every realm now provisions the v2 stack at create time. For operators: the v1-only admin endpoints POST /admin/custody-pool/sweep-unattributed, POST /admin/custody-pool/absorb-unattributed, and POST /admin/custody/replay/rebuild are gone, along with the matching arca admin CLI verbs; boundary balances shown in admin surfaces are now always live on-chain kernel reads (the replayBalance / replayDrift fields no longer appear in boundary balance-sheet responses). Builder-facing APIs and SDKs are unaffected.

2026-06-08

  • New (operator): cooperative Hyperliquid venue-unwind for custody recovery. The v2 custody hatch (kernel/coordinator/modules) gains a cooperative recovery path alongside the existing non-custodial escalation. A new on-chain entrypoint, ArcaCoordinator.requestVenueUnwind(boundary), emits a RecoveryUnwindRequested event without locking the boundary or revoking ARCA's per-vault trading agent — so the platform flattens every Hyperliquid exchange-arca in the boundary (cancel resting orders, close positions reduce-only, poll flat) on the user's behalf, sparing the recovery-key holder the manual per-arca drain. The flatten is solvency-neutral; the value movement and ledger settle run through the existing delete/withdraw paths (whose "venue must be flat" precondition this unblocks). Operators trigger it with arca admin recovery venue-unwind (dry-run by default; --execute is step-up gated on production realms). The recovery-key holder can also call the entrypoint directly on-chain. See the CLI admin docs.
  • New: Kotlin/Android SDK. A fourth first-party SDK joins TypeScript, Swift, and Go — com.github.arcaresearch:arca-kotlin-sdk, distributed via JitPack. It reaches full feature parity with the Swift SDK: objects, transfers, operations, aggregation, exchange (perps), TP/SL brackets, and real-time streaming. The Kotlin surface is idiomatic — suspend functions for request/response, coroutine Deferred-backed operation/order handles (submitted() / settled() / filled()), Flow / StateFlow watch streams, and a sealed ArcaException hierarchy. The library targets JVM 1.8 bytecode (Android minSdk 24+). See the Kotlin SDK docs to get started.
  • Swift SDK fix: malformed realtime frames no longer crash the app. A candles.updated or watch_snapshot frame whose candle / valuation field arrived as a JSON fragment (a bare number, string, boolean, or null instead of an object) could hard-crash the host app with a native NSInvalidArgumentException from JSONSerialization an Objective-C exception that Swift's try? cannot catch. The WebSocket handler now validates each value with isValidJSONObject before re-serializing and skips any item it can't decode, matching the existing decode-failure behavior. The same guard hardens the payment-link metadata and TP/SL leg-selection paths.

2026-06-07

  • New: sized (partial) TP/SL legs across all three SDKs. Take-profit and stop-loss legs close the whole position by default (sizeToMax). You can now request a sized partial close — e.g. “scale out half at the target” — by setting a base-unit size: size on setStopLoss / setTakeProfit (Go: SetPositionTriggerOptions.Size), and stopLossSz / takeProfitSz on setPositionTpsl and openWithBracket. A sized leg is a reduce-only partial capped at the live position size (min-notional waived). Fully additive — an empty/omitted size keeps today's whole-position behavior. Because a partial fill of one OCO leg cancels its sibling, setPositionTpsl deliberately does not auto-link the two legs when either is sized (pass an explicit ocoGroupId to link them anyway); for the same reason, to “scale out half and keep a stop on the rest” through openWithBracket, open the bracket with just the sized take-profit and attach the (unsized) stop separately via setStopLoss.
  • New: atomic order brackets via openWithBracket. Open a position and attach its reduce-only TP/SL triggers as one atomic batch (Hyperliquid normalTpsl parity). The entry and its triggers are submitted as a single signed batch to one operation at a new endpoint, POST /api/v1/objects/{id}/exchange/orders/batch — the whole bracket validates and commits at the venue, or none of it does. Crucially, the trigger legs arm only when the entry fills, so they can't fire on mark price before the position exists; the venue links them with a shared one-cancels-the-other group so a fill on one cancels its sibling. All three SDKs add openWithBracket({ entry, takeProfit?, stopLoss? }), returning one order handle per leg from a single call. The existing setPositionTpsl (attach TP/SL to an already-open position) is unchanged.
  • New: true per-pair TP/SL OCO via ocoGroupId. setPositionTpsl() now links its stop-loss and take-profit legs into a real one-cancels-the-other bracket: both legs share one auto-generated ocoGroupId, and when either fills — even partially — the venue cancels the other leg and records cancelReason: 'sibling_filled' on it. Previously a partial fill on one leg left the other resting until the position closed. Advanced callers can pass an explicit ocoGroupId to placeOrder() to build custom sized brackets. The id is advisory metadata — it is forwarded to the venue but not part of the order signature, so it's fully additive.
  • New: cancelReason on cancelled orders. A CANCELLED SimOrder now carries an optional cancelReason (OrderCancelReason) explaining why it was cancelled — 'user_requested', 'sibling_filled', 'position_closed', 'position_flipped', 'liquidated', or 'position_gone' — readable on-demand through getOrder / listOrders (including when listing CANCELLED orders). The OrderStatus enum is unchanged.
  • ⚠️ ALO (post-only) orders that would cross are now rejected. An ALO limit whose price would immediately cross the book is refused (a bad-ALO-price validation error) instead of executing as a taker. Non-crossing ALO orders rest as makers as before. If you were relying on a crossing ALO to fill, switch to GTC or IOC.
  • ⚠️ Trigger-price direction is now validated at placement. A trigger (TP/SL) set on the wrong side of the current mark — one that would fire immediately — is rejected with a bad-trigger-price validation error, matching Hyperliquid. Correct-side triggers are unaffected, and the check is skipped when the mark is momentarily unavailable.
  • Go & Swift SDKs: OCO + cancelReason parity. The same additive surface ships in the Go and Swift SDKs: SetPositionTpsl / setPositionTpsl stamps one shared ocoGroupId on both legs, placeOrder / SetStopLoss / SetTakeProfit accept an explicit ocoGroupId, and SimOrder now exposes ocoGroupId and cancelReason on a read. The ALO/trigger placement rules are server-side, so they already applied to every SDK.

2026-06-06

  • ⚠️ TP/SL is now sized or unsized; the grouping field is removed. The na / normalTpsl / positionTpsl grouping enum (and the parentOrderId field) are replaced by a single boolean sizeToMax on the order. A sized TP/SL (sizeToMax: false, the default) carries a fixed size and closes that quantity when triggered (reduce-only) — this is what a TP/SL attached to an entry order becomes. An unsized TP/SL (sizeToMax: true) carries no quantity and closes the entire position regardless of its size — the "trump card", and what setStopLoss / setTakeProfit / setPositionTpsl (CLI position set-tpsl) now place. One universal rule replaces OCO and auto-resize: no TP/SL outlives the position — when the position reaches zero (by close, by a TP/SL firing, or by liquidation) all waiting triggers for that market are cancelled. Reduce-only and unsized triggers remain exempt from the $10 minimum notional. The CLI flag --grouping is replaced by --size-to-max.
  • TP/SL are now directional across position transitions. A TP/SL closer has a fixed side — a "Close Long" is a reduce-only sell, a "Close Short" is a reduce-only buy — and its lifecycle now follows the direction it closes, matching Hyperliquid: closing a position to flat cancels every TP/SL for that market (both directions); flipping a position (e.g. selling more than a long, so it becomes a short) cancels the closers for the original direction and keeps the new direction's — including any TP/SL attached to the flipping order; and merely reducing a position leaves all triggers open, including a not-yet-in-effect closer for the opposite direction. Previously a flip left the stale opposite-direction closers resting until they were rejected at fire time.
  • New: resize a resting order. A new PATCH /api/v1/objects/{id}/exchange/orders/{orderId} endpoint resizes a resting order to a new total size. SDKs expose modifyOrder() plus an order.resize(newSize) / order.Resize(...) convenience on the order handle, and the CLI adds arca exchange order modify <order-id> --new-size. Only sized orders can be resized: resting limit orders and sized TP/SL triggers. Unsized (sizeToMax: true) triggers are rejected — they always close the entire position and have no quantity to amend. The new size must exceed the already-filled quantity, satisfy the $10 minimum notional on the unfilled remainder, and (when growing a non-reduce-only limit order) pass a margin check. path is a per-resize idempotency key.
  • Fixed: reduce-only closes on isolated-only markets no longer require leverage. A reduceOnly: true order (or TWAP) on an isolated-only market such as hl:1:NVDA previously failed with Isolated-margin orders on … require a positive leverage unless the caller restated leverage. A close only shrinks an existing position, so it now skips that gate and is accepted with leverage omitted — matching cross-margin behavior. When leverage is supplied on a reduce-only order it is treated as a no-op: the remaining position keeps its current leverage and is never re-margined. Opening or increasing an isolated position still requires a positive leverage.
  • Fixed: candle history responses now carry the market field. The candle-history endpoint (GET /exchange/market/candles/{market}, used by getCandles and watchCandleChart) was still returning the instrument id under the legacy coin key, missed in the coinmarket rename. The response now uses market to match CandlesResponse in every SDK. This unblocks the Swift SDK, whose strict decoder failed with Key 'market' not found and broke candlestick chart history.
  • The Arca network now takes no fee. The fixed transfer fee (previously $0.05 on exchange transfers) and the per-trade platform fee (previously 1 bps) are now $0. Only your application's fees (applicationFeeTenthsBps / builderFeeBps, with feeTargets splitting) and the underlying venue's own trading fee apply. estimateFee and Arca.fees.exchangeTransfer now report "0", and small exchange transfers are no longer rejected for failing to exceed a transfer fee. The fee plumbing is retained, so a network fee can be re-introduced later without an API change.

2026-06-05

  • ⚠️ Canonical market identity: the instrument field is renamed coinmarket. Across the REST API, WebSocket payloads, and every SDK, the field that carries a canonical market id is now market (request bodies, query params, and response shapes). The id value also gains a uniform three-segment shape: native Hyperliquid markets are now hl:0:SYMBOL (e.g. hl:BTC hl:0:BTC) — the :0: dex segment is always present for native markets. HIP-3 ids such as hl:1:TSLA are unchanged. Pass ids back to the API exactly as returned.
  • ⚠️ The market-metadata accessor is renamed asset()market(id). TypeScript and Swift expose arca.market(id) — an exact canonical-id lookup returning one market or undefined/nil; Go exposes client.Market(ctx, id) (was Asset()). The metadata type SimMetaAsset is renamed to Market (fields unchanged).
  • New symbol resolvers: resolveMarkets(symbol, { exchange?, dex? }) and resolveMarketOrThrow(symbol, opts). A human symbol ("BTC") can map to many markets across exchanges and HIP-3 dexes, so resolveMarkets returns an array — an empty array is an explicit “not found”, never a silent guess. Use resolveMarketOrThrow when you expect exactly one; it throws on zero or more than one match. Go: ResolveMarkets(ctx, symbol, opts) / ResolveMarket(ctx, symbol, opts).
  • ⚠️ Removed the isLikelyNonCanonicalCoin() heuristic export. The warn-only “does this look like a bare symbol?” shortcut (and the related KNOWN_EXCHANGE_PREFIXES / NonCanonicalCheck exports) is gone. Resolve a human symbol to its canonical id with resolveMarkets(symbol) (or resolveMarketOrThrow) instead of guessing.
  • ⚠️ The instant fill-preview event is renamed exchange.fillfill.previewed. A fill is delivered in two phases: an instant venue preview followed by the authoritative, journaled record. The preview now shares its sibling's naming convention — fill.previewed (Phase 1, sequence: 1) then fill.recorded (Phase 2, sequence: 2), correlated by correlationId (the order ID). The WebSockettype field carries the new value. SDK helpers are: TypeScript EventType.FillPreviewed and ws.onFillPreviewed(); Go EventFillPreviewed and OnFillPreviewed(); Swift EventType.fillPreviewed. The two-phase merge in watchFills() is unchanged — it correlates by order ID, not the event name.
  • Exchange objects are now selected by venue, not exchangeType. When creating a perps exchange object, set metadata.venue to "hl-sim" (simulated Hyperliquid, the default) or "hl" (live Hyperliquid). These short labels mirror the market-id prefix (hl:BTC); a future Binance venue would be bn / bn-sim. The SDKs expose a venue option on ensurePerpsExchange(). The old exchangeType field — which was always "hyperliquid" even for simulated accounts and therefore conveyed no information — is removed.
  • ⚠️ The configurable order fee is the “application fee”, with its unit in the name. The per-order and realm-default fee inputs are applicationFeeTenthsBps on placeOrder() / closePosition() / setPositionTpsl() / watchMaxOrderSize(), getActiveAssetData() / getAssetFees(), and the asset-fees query parameter; and defaultApplicationFeeTenthsBps in realm settings. The value is in tenths of a basis point (so 40 = 4 bps) — naming the unit removes a 10× foot-gun. The earlier builderFeeBps / applicationFeeBps input names are removed (clean break — no aliases). This is distinct from the Hyperliquid protocol-level builderFeeBps echoed back on raw venue order responses, which is unchanged. The fee-breakdown field cumulativeBuilderFee is also unchanged.
  • Realms now have two independent axes: asset and lifecycle. A realm's asset tier (paper vs live) controls the money mode — paper realms use simulated, auto-minted funds; live realms move real money and gate value-moving actions behind browser step-up. Its lifecycle tier (permanent vs temporary) controls durability — permanent realms are never auto-reaped and require step-up to archive, while temporary realms are disposable. The axes are orthogonal, so a long-lived practice app can be paper+permanent and a short-lived real-money run can be live+temporary. POST /realms now accepts asset, lifecycle, and backing fields (CLI: --asset / --lifecycle / --backing), and realm responses include all three. The legacy single-axis type (development / production) is fully backward-compatible — it's now a derived alias of asset (development↔paper, production↔live), so existing calls and SDK code keep working unchanged.
  • ⚠️ Position and trade side values are now lowercase, and fill dir is renamed direction. Position side is long/short (was LONG/SHORT) and trade/order side is buy/sell (was BUY/SELL) across the fills and positions API and all SDK types. The fill direction field is renamed dir direction with snake_case values open_long / close_long / open_short / close_short (was "Open Long" etc.). Update any code that branches on these values.
  • ⚠️ The npm packages are renamed to the @arca-network scope. @arcaresearch/sdk@arca-network/sdk and @arcaresearch/react @arca-network/react. Re-install under the new name and update your import paths. The Go SDK module path is github.com/arcaresearch/arca-go-sdk. The product domain stays arcaos.io.

2026-06-04

  • Delete now validates its sweep destination up front. When you delete an object and route its residual balance to another object (sweepToPath), the destination is now checkedbefore the delete begins: it must exist, be active, and be a denominated wallet of the matching denomination (exchange equity sweeps to a USD wallet). A missing or invalid destination is rejected immediately with a clear 404/409, instead of the delete starting and failing partway through. There is no fallback to a default destination — an invalid target fails loudly so funds are never mis-delivered.
  • You can't delete a wallet that is the live sweep destination of another in-flight delete. Such a request now returns a conflict (“object is the sweep target of an in-flight delete operation”); wait for that delete to settle, then retry. This extends the existing “in-flight blocks deletion” protection to cover an object that is the destination of an in-flight delete, closing a race that could otherwise strand value.
  • Swift SDK: forward-compatible contract for server-authoritative pricing. The Swift SDK now advertises a server-authoritative-pricing capability — an X-Arca-Client-Capabilities header on REST requests and a capabilities field in the WebSocket auth message — and valuation payloads (ObjectValuation, PathAggregation, ExchangeState) accept an optional pricingMode marker. When pricingMode is "server" the SDK trusts the server-computed values verbatim instead of recomputing them from raw mid prices; when it is absent or "client" — i.e. all of today's traffic — behavior is byte-for-byte unchanged. This behavior-neutral contract lets a future simulated-account price overlay be switched on server-side without a client upgrade. No action is required.

2026-06-02

  • Position stop-loss / take-profit across all SDKs and the CLI. New ergonomic helpers attach a trigger to an existing position without hand-building an order: setStopLoss, setTakeProfit, setPositionTpsl (both legs at once), and clearPositionTpsl in the TypeScript, Swift, and Go SDKs, plus arca exchange position set-tpsl / clear-tpsl in the CLI. Each looks up the open position, infers the closing side (LONG → SELL, SHORT → BUY), and places a reduce-only positionTpsl order with size 0 — so the venue fills it from, and auto-resizes it with, the live position, and cancels it when the position closes or is liquidated. Leverage and isolated are auto-filled from the position and market meta. By default a new trigger replaces the existing same-type trigger for the coin (pass replace: false / --replace=false to stack). For limit triggers or the normalTpsl grouping, keep using placeOrder / order place --trigger.
  • Correctness: trigger fields are now signed and idempotency-safe. Server-side order placement now validates trigger fields up front (triggerPx, tpsl, and grouping must be coherent) and includes the full trigger payload in the EIP-712 order signature, so take-profit / stop-loss orders sign and verify identically to standard orders. Idempotency now compares the complete order input (excluding the generated client order id), so retrying the same path with the same trigger parameters returns the prior result instead of being misread as a conflicting input.

2026-05-31

  • Fix: ticker dayNtlVlm no longer inflated. Market ticker responses (GET /market/tickers, getTickers()) now report dayNtlVlm as the true 24-hour notional volume in USD. Hyperliquid's dayNtlVlm is already denominated in USD, but it was being multiplied by the mark price a second time, inflating reported volume by a factor of the price (e.g. $1M of BTC volume showed as ~$64B). The value is now passed through unchanged.
  • Tiered-margin- and spread-aware max order size (TypeScript & Swift SDKs). watchMaxOrderSize() now factors in the asset's laddered initial-margin schedule and the bid/ask spread. getActiveAssetData() and ActiveAssetData expose marginTiers, maintenanceMarginRate, and the top-of-book bidPx/askPx, which the watch stream fetches once and applies. Market buys are margin-checked at the ask and sells at the bid, so the stream now sizes against that directional price instead of the mid — eliminating the case where maxBuyUsd over-stated the executable max for tiered or wide-spread assets (e.g. BTC at high leverage) and was rejected with insufficient balance. The streamed max remains a best-effort estimate (mids and the spread move between preview and submission) — to place exactly the maximum, use placeOrder({ useMax: true }) or pass a small sizeTolerance so the server resolves the size atomically. The Swift SDK reaches full parity (its watchMaxOrderSize now applies both the margin tiers and the spread), and the Go SDK's ActiveAssetData exposes bidPx/askPx. In the TypeScript SDK the spread is resolved independently of the margin inputs: pre-supplying maintenanceMarginRate + marginTiers no longer suppresses the spread fetch (which had silently fallen back to mid-based sizing). You can now also pass bidRatio/askRatio directly via MaxOrderSizeWatchOptions — supply all four to skip the getActiveAssetData lookup entirely.
  • Candle charts: cold-start history hardening (TypeScript SDK). watchCandleChart() now does one backfill-enabled fetch before resolving when the fast initial fetch is empty, so a consumer that reads chart.candles once (instead of subscribing to onUpdate) gets history on a cold market instead of an empty array. The getCandles() response cache is now keyed by skipBackfill and never caches an empty result, so a transient empty no longer lingers for the cache TTL. Best practice is unchanged: render from onUpdate, not a one-time read.
  • Non-canonical coin-id warnings (TypeScript SDK). getCandles(), watchCandleChart(), and watchCandles() now emit a one-time console.warn when a coin looks non-canonical — a bare symbol ("TSLA"), display name ("Tesla"), or Hyperliquid named-deployer id ("xyz:TSLA") — pointing at the canonical form ("hl:1:TSLA"). The check is also exported as isLikelyNonCanonicalCoin(). Reminder: a well-formed but unknown id returns 200 with an empty array, not an error, and staging reads the production candle CDN — so prod-listed markets (e.g. hl:ATOM, hl:1:TSLA) have full history on staging too.
  • Staging portal and CLI access. Staging now has dedicated builder and admin portal hostnames (app-staging.arcaos.io and admin-staging.arcaos.io) built against api-staging.arcaos.io. The CLI supports portal_base in profiles for browser auth and step-up, and admin login can target staging with arca admin auth login --env staging.
  • Market metadata: venue-native symbols. GET /api/v1/exchange/market/meta and SDK getMarketMeta() / asset(coin) now include venueSymbol for display and venue deep links. Keep using name for Arca API calls; venueSymbol exposes values like BTC and xyz:TSLA.
  • Public asset catalog expanded. GET /api/v1/exchange/market/meta and SDK getMarketMeta() now include category and curation metadata for each live Hyperliquid market: assetType, categoryLabel, mapped, hasDisplayName, hasLogo, and descriptionStatus. Newly listed venue assets still appear even when Arca has not yet curated a display name or icon.
  • ⚠️ Isolated margin & margin mode reach all three SDKs. updateIsolatedMargin and setMarginMode are now available in the TypeScript, Swift, and Go SDKs (previously TypeScript only). Positions and leverage settings now expose a single marginMode field (cross or isolated) plus isolatedMargin for isolated positions. Go: client.UpdateIsolatedMargin(ctx, arca.UpdateIsolatedMarginOptions{...}) and client.SetMarginMode(ctx, arca.SetMarginModeOptions{...}); Swift: arca.updateIsolatedMargin(objectId:coin:amount:) and arca.setMarginMode(objectId:coin:marginMode:).
  • Explicit marginModes on market metadata; onlyIsolated deprecated. GET /api/v1/exchange/market/meta and SDK getMarketMeta() / asset(coin) now return marginModes — an explicit list of the margin modes an asset supports (["isolated"] for isolated-only markets, ["cross", "isolated"] otherwise). Read it instead of the Hyperliquid-specific onlyIsolated boolean, which is now deprecated and will be removed in a future major version. Margin mode is independent of HIP-3: some HIP-3 markets (e.g. hl:1:TSLA) are cross-eligible, so never infer the margin mode from isHip3. No behavior change — marginModes is derived from onlyIsolated.

2026-05-30

  • True isolated-margin accounting on the simulated exchange. Isolated positions now carry their own dedicated collateral and are margined and liquidated independently of the cross pool — the clearinghouse state reports a distinct crossMarginSummary (cross bucket only, which drives withdrawable) versus the account-wide marginSummary, and each isolated position shows its own fixed liquidation price. An underwater isolated position no longer triggers liquidation of healthy cross or sibling positions, and its loss is capped at its assigned margin. Two new builder actions: updateIsolatedMargin (add/remove collateral from an isolated position; POST /objects/{id}/exchange/isolated-margin, SDK arca.updateIsolatedMargin, CLI arca exchange isolated-margin) and setMarginMode (switch an asset between cross and isolated; POST /objects/{id}/exchange/margin-mode, SDK arca.setMarginMode, CLI arca exchange margin-mode). Margin mode is rejected on isolated-only (HIP-3) markets and while a position is open. Leverage is now remembered per margin mode (matching Hyperliquid): setting 10x in cross and 5x in isolated keeps both — toggling modes restores each mode's own leverage rather than carrying one across. Backward compatible: existing positions remain cross.
  • New: Go SDK. github.com/arcaresearch/arca-go-sdk is an idiomatic Go client for building backends on Arca — context.Context on every call, typed errors (errors.As), generic operation/order handles, and channel/callback watch streams. Install with go get github.com/arcaresearch/arca-go-sdk@latest (Go 1.23+). See the Go SDK reference.
  • ⚠️ SDK license change: MIT → PolyForm Shield 1.0.0. The Go, TypeScript, React, and Swift SDKs are now licensed under the PolyForm Shield License 1.0.0 — still source-available and free to use to build on Arca, but they may not be used to build a competing product. Relicensing applies to new versions; previously published artifacts remain under their original license.
  • Documentation accuracy pass. Corrected the public docs and SDK examples to match shipped code ahead of the first consumer launch. Realm types are consistently development / production (legacy demo / testing / practice wording removed), with a note that the type controls money mode (paper vs real), not your deployment stage. Quick-start and trading examples now use the real API surface (@arca-network/sdk, await on operation handles, ref / operationPath / order path, side: 'buy' / orderType: 'MARKET', feeTargets: [{ arcaPath, percentage }]). Operation lifecycle is documented as pending → completed/failed/expired with failureMessage, the 412 STEP_UP_REQUIRED status is listed, canonical coin IDs (hl:BTC) are used in all examples, and the SDK requires Node.js 20+. No API, SDK, or CLI behavior changed — documentation only.
  • Faster equity & P&L history charts. The aggregation read path now bounds its snapshot read (seed-at-window-start instead of scanning full account history), caches immutable historical mid prices, and caches whole history responses with per-realm write-stamp invalidation — so first load and timeframe switches return noticeably faster, especially for exchange accounts.
  • SDK: getEquityHistory, getPnlHistory, watchEquityChart, and watchPnlChart accept { kind: 'object', objectId }. Charting a single object by id lets the server skip enumerating a path prefix's objects. The default (path-prefix) behavior is unchanged.
  • SDK: cache.ttlMs controls the historical-data cache lifetime (default 5 minutes). Lower it for stronger freshness or set it to 0 for no expiry (entries then live until LRU eviction). Pass cache: false to disable caching entirely.
  • Realm-locked API keys and permission presets. API keys can now be locked to a single realm at creation via realmId (CLI --realm, SDK createApiKey{ realmId }) — a realm-locked key is rejected on any other realm with 403 REALM_SCOPE_MISMATCH, so dev, staging, and production credentials can be cleanly isolated. A new permissions preset (read, trade, or full) applies least-privilege scopes without hand-writing policy statements. Scoped or realm-locked keys can no longer mint or revoke API keys, closing a privilege-escalation path. Existing keys are unchanged (org-wide, full access).
  • Faster, more reliable live account-equity startup. Opening a live equity or P&L stream for an object is now noticeably quicker. watchEquityChart, watchPnlChart, and watchAggregation now begin the realtime connection in parallel with the initial history fetch instead of waiting for it to finish, and the chart renders its first points immediately instead of blocking on a follow-up history refresh for newly funded accounts. Transient request retries use jittered backoff with a shorter first delay. New: EquityChartStream.requestHistoryRefresh() triggers a one-off background refetch of the dense server history (the same path used for gap/resume healing).

2026-05-23

  • V2 custody architecture (developer preview): kernel + coordinator + per-venue modules replaces the monolithic ArcaCustodyPool for new realms. Realms created while the platform's FEATURE_V2_CUSTODY_FOR_NEW_REALMS flag is on are provisioned with a per-realm v2 stack: an ArcaCustodyKernel proxy (boundary bookkeeping + recovery escape), an ArcaCoordinator proxy (policy + module registry + halt controls), a HyperliquidVenueModule proxy (per-arca CREATE2 vaults for HL deposits/withdrawals), and a SimExchangeVenueModule proxy (sim-exchange allocation tracking). All four impls are upgradeable today (UUPS / beacon) and can be permanently frozen post-audit. Worker activities + platform service transparently branch on a new custodyVersion column on each realm's custody pool row; existing v1 realms keep working unchanged. Realm responses now include a custodyVersion field (1 or 2) so builders can verify routing.

2026-05-22

  • CLI: arca payment-link preview <token> and arca payment-link complete <token> surface deposit instructions browser-free. The pay page's onChainDeposit block (pool address, boundary, ref, raw amount) is now readable from the CLI for any payment link, with a printed copy of the equivalent cast commands so a depositor can drive the deposit from MetaMask, a Safe, cast, foundry, or any other on-chain tool without opening the browser pay page. arca payment-link complete registers the deposit/withdrawal intent on the platform side (idempotent; second-call on a completed link is a no-op). Endpoints are unauthenticated — the token in the URL is the credential — so both work without arca auth login. Useful in CI, test scripts, and integration tooling.
  • Portal: CLI step-up confirmation now returns you to /cli/confirm after sign-in instead of the portal home. When the CLI's step-up flow opened /cli/confirm?port=...&state=...&token=... in a browser where the user was logged out, the "Sign in required" screen redirected to /signin?return_to=... via a plain anchor — which wiped the React Router state the sign-in page was relying on. The user landed on the portal Explorer after Google sign-in and had to manually return to the CLI confirm tab. The portal's sign-in page and PublicRoute now honor the return_to URL query parameter (in addition to router state), gated by a same-origin relative-path guard (safeReturnTo) that rejects absolute URLs, protocol-relative paths (//evil.com), and backslash trickery.

2026-05-20

  • CLI: arca realm delete gains --force-pool-imbalance and --force-active-operations for ephemeral dev realms. Soft-archive of a development realm previously refused if the realm held any pool or ledger value, or if any Temporal workflow was still in flight. That was correct for realms backing real apps (e.g. Svall paper trading) but over-restrictive for load-test scaffolding and throwaway probes that the operator wants to walk away from in whatever state they're in. The two new flags opt out of those preconditions on dev realms only; both require --imbalance-reason for the audit trail and both are refused on production realms regardless of step-up. The platform rule (.cursor/rules/ledger-integrity.mdc rule 5) was also updated: type=development is no longer treated as a license to ignore ledger violations; the default for a dev realm in active use is the same investigate-and-compensate path as production.
  • Custody: replay engine handles register_exchange_arca_failed events. A recent commit added a new custody event type (written by the worker's RecordRegistrationFailure activity when a RegisterExchangeArcaWorkflow exhausts its retry budget) but missed adding the matching case in applyReplayEvent. The result was that every subsequent AnnotateDeposit/ AnnotateBurn/AnnotateCrossTransfer on any realm with a stuck-registration row failed with CUSTODY_ANNOTATE_FAILED: unknown replay custody event type "register_exchange_arca_failed". Because the error was retryable, Temporal hammered the failing activity for hours without paging. Fix is a 3-line case statement that treats the event as observability-only (no contract state change), pinned by a regression test.
  • Custody: smart-wallet pool registration cleanup. Three custody-pool registration sites (registerContractPool, EnsureRealmRegistered in platform-go; ensureCustodyContractPool in worker-go) now short-circuit on pool.WalletID != nil and register as ReplayPool instead of OnChainPool. Smart-wallet pools (EIP-4337 SimpleAccount via EnsurePool path B) carry on-chain bytecode but do not expose the ArcaCustodyPool selectors, so any future caller that forgets the IsContractBackedPool gate gets a no-op ReplayPool method instead of a contract revert. Follow-up to the original classification fix in the predicate itself.

2026-05-18

  • Isolated-margin support for HIP-3 markets. Orders on HIP-3 markets such as hl:1:CL are isolated-margin only on Hyperliquid. The sim-exchange now mirrors that rejection: a cross order on an onlyIsolated=true market is refused with a 400, and isolated orders must carry a positive leverage so the matching engine can identify the bucket. placeOrder() and closePosition() gain an isolated field; closePosition() auto-fills both isolated (from the market's onlyIsolated flag) and leverage(from the position) so existing close flows work transparently. The portal displays an Isolated badge on HIP-3 markets and forces the flag. Mirrors the iOS team's Quick Close fix end-to-end. The internal margin and liquidation math is still computed cross-only pending the full economic model — tracked under pre-launch cleanup.
  • Worker: reduce-only orders now carry leverage through to the venue. Previously the worker stripped the leverage field from every reduce-only order to prevent it from reconfiguring the account's stored leverage setting. That correctly protected against an unwanted side effect, but also zeroed the value in the order body — which Hyperliquid's matching engine needs to route an isolated close to the correct margin bucket. The two concerns are now decoupled: the stored leverage setting is never reconfigured on reduce-only (the March 2026 invariant survives), but body.leverageflows through to the venue verbatim.
  • CLI: arca exchange position close command + --isolated flag. New arca exchange position close mirrors the SDK's closePosition() helper. arca exchange order placegains a --isolated flag for explicit margin-mode selection.

2026-05-17

  • Explorer: newest-first ordering for objects and folders. browseObjects() now returns direct-child objects and folder/isolation-zone entries sorted by createdAt descending (most recent first), with path as a stable tiebreaker. Previously, entries were sorted alphabetically by path, which left newly created rows at the bottom of the explorer list for realms whose paths grow lexicographically over time. The legacy folders string array follows the same newest-first order as paths.
  • Swift SDK: Faster WebSocket reconnection on iOS app foreground. The WebSocketManager now observes UIScene.willEnterForegroundNotificationin addition to UIApplication.willEnterForegroundNotification to instantly detect when the app returns from the background. This fixes a 30–45 second freeze where the SDK waited for a heartbeat timeout to detect a half-open socket. For apps with custom lifecycle events, a newarca.reconnect() method is available to manually force an immediate WebSocket reconnect.

2026-05-15

  • admin custody backfill-boundaries CLI no longer reports successful runs as "unknown error". The POST /api/v1/admin/custody/backfill-boundaries route used to emit a bare JSON body on success ({realmId, fromBlock, monitorsScanned}) and bare plaintext via http.Error on failure, neither of which matched the CLI's {success, data, error} envelope. The CLI's JSON parser silently defaulted success to false, mapping every backfill — even a fully successful chain replay that committed recovery-monitor state — to the "unknown error" branch. The route now wraps every response (200, 400, 503, 500) in the standard envelope with a stable error code (MISSING_PARAM, INVALID_REQUEST, SERVICE_UNAVAILABLE, INTERNAL_ERROR) so CLI automation, dashboards, and runbooks can distinguish a real failure from a clean run. The four route tests in admin_custody_backfill_test.go now pin the envelope shape; any future handler that drops back to bare JSON will fail the unmarshal there.
  • Regression suite: boundary-conservation now asserts on per-run drift. The boundary-conservation probe in the post-deploy regression suite previously asserted on realm-wide invariants —contractTotal == boundarySum + unattributed and 0 unaccepted insolvent boundaries — which meant any historical residue on the long-lived qa-regression realm (e.g. 14 stranded $25 zones from the pre-2026-05-11 cross-boundary-transfer teardown bug, or accumulated missed-event drift from the 2026-05-02 Dwellir RPC outage) caused the probe to fail every run regardless of whether today's run actually introduced new drift. The probe now captures a baseline snapshot of the boundary balance sheet at suite start, runs LAST (after every other category), and asserts on the delta: pool-conservation-delta fails only if the gap grew during the run, and no-new-boundary-drift-delta fails only on a new insolvent boundary or an existing one whose surplus worsened beyond $0.01. Realm-wide invariants are tracked separately by Datadog (arca_solvency_boundary_insolvent_count{accepted:false}and arca_boundary_replay_realm_conservation_drift_usd{realm}with $10 warn / $100 page thresholds). Operator cleanup of the historical residue (via cli/arca admin custody backfill-boundariesand cleanup-stranded-zones) remains a separate track — see documents/runbooks/boundary-residue-cleanup.md.
  • Multi-pod nonce coordination for chain broadcasts. The platform's admin deployer EOA is now coordinated through a Redis-backed nonce manager so every platform-go and worker-go pod sharing the same Redis sees a single high-water-mark for the next nonce. Before the fix, two pods running concurrent fundAccount calls (auto-mint) or other admin chain operations could each read the same pendingNonceAt from the RPC, sign distinct transactions at the same nonce, and one of the two broadcasts would be rejected as replacement transaction underpriced — producing phantom pool surplus and bench-realm boundary deficits. The broadcast-conflict error path is now classified: SDK and probe callers receive a structured 503 CHAIN_BROADCAST_CONFLICT with retry semantics instead of a generic 500 when the race surfaces outside the manager (e.g. external use of the deployer key, or a Redis outage where pods fall back to per-Client in-memory managers). New REDIS_URL dependency for chain providers; the manager keys per-chain so the same admin EOA on two chains has independent counters.
  • ensureDeleted retries are now idempotent. A retried arca.ensureDeleted() call against an object whose previous delete attempt failed (for example, a Phase-2 Spanner timeout or a workflow-dispatch failure that reverted the object back to active) used to collide on idx_operations_realm_path_uniq and return a generic 500 INTERNAL_ERROR. The platform now looks up the existing operation at the deterministic /op/delete/{objectId} path inside the same transaction; on a match it returns the prior operation handle. For operations written by the new code, retries with materially different parameters (a different sweepToPath or liquidatePositions flag) surface as a structured 409 IDEMPOTENCY_VIOLATION — clients can either honour the existing operation or pass a unique operationPath for a true retry. Operations written by the pre-fix code (which persisted only {objectId, path}) are treated as compatible with the matching retry to unblock the in-flight Svall scenario without trading the 500 for a 409. No SDK change required; the existing ensureDeleted polling loop simply observes the prior operation's terminal state.
  • Cross-boundary chain-tx direction fix in the regression probe. The cross-boundary-transfer regression probe now looks for direction='internal' on the recorded chain_transactions row instead of 'outbound'. Cross-boundary transfers burn from one boundary and mint to another in the same on-chain transaction (no change to total pool supply), so the worker correctly stamps them as internal. The amount and non-reverted sub-assertions now run regardless of whether the direction tag is found, so future regressions to the tag don't mask amount or status drift.
  • Folder creation dates and labels in the Explorer. Folder rows now show a relative creation timestamp and up to two label chips, with an edit affordance for managing them. The browse response (GET /api/v1/objects/browse) carries a new createdAt on each paths entry (isolation zones use the zone's own created_at; plain folders use MIN(child.createdAt) over the returned descendants) and a new inline labels map. Folder labels live in a dedicated, sparse folder_labels table keyed by (realmId, path) and are read/written through two new endpoints: GET /api/v1/folders/labels and PUT /api/v1/folders/labels (full overwrite — pass {} to clear). The SDK adds arca.getFolderLabels(path) and arca.updateFolderLabels({ path, labels }); the BrowsePathEntry type gains optional createdAt and labels fields. Permission wise, writes require the new arca:UpdateFolderLabels action (rolled into the arca:Lifecycle and arca:Write aliases); reads reuse arca:ReadObject on the folder path. Label validation mirrors object labels (32 keys max, key pattern ^[a-zA-Z][a-zA-Z0-9._-]*$, value ≤ 256 chars, arca. / _ reserved prefixes).

2026-05-09

  • Mid-price freshness gate stops dual-source flapping. The platform now drops BBO updates from hl-streamer whose upstream timestamp is older than 15 seconds, and the in-memory mid cache rejects any update strictly older than the value already stored for that coin. Together this stops the failure mode where a backlog of stale NATS BBOs and the gRPC public-Hyperliquid-WS fallback would race for the same coin and the frontend's revalued P&L would alternate between two number ranges every 100ms. isNATSHealthy("mid") now also considers data age, so the public-WS fallback activates when hl-streamer is publishing on time but feeding lagged data. Five new metrics under arca_market_data_mid_* make source attribution, stale drops, and source flips visible in monitoring. No SDK or API changes.

2026-05-03

  • ⚠️ Step-up authentication for destructive production actions. Destructive actions on production realms — production-realm archival, custody-pool sweeps and absorbs, custody-pool transfers, revenue withdrawals, boundary rebalances — now require browser-confirmed step-up auth in addition to the standard JWT or API key. The CLI catches the412 STEP_UP_REQUIRED response, opens your browser at /cli/confirm, and retries automatically with the resulting token. Long-lived API keys can no longer execute destructive production actions without a human in the loop. New CLI flag --force-production-archive on arca realm delete for production realms. Three new endpoints under /api/v1/auth/step-up-requestsimplement the create / read / approve flow. See documents/runbooks/step-up-auth.md for operational details.
  • ⚠️ Production-realm archival now refuses unsafe deletes. DELETE /api/v1/realms/{id} on a production realm now refuses unless the request body sets forceProductionArchive: true, the caller carries a step-up token bound to admin:realms/delete, the realm has zero pool-backed obligations, and the realm has no pending operations or held reservations. Soft-archiving an in-use production realm is the operational hole that motivated this work.
  • ⚠️ Anonymous /api/v1/dev/* routes are now gated and authenticated. The destructive dev routes (reset, cleanup-realm, archive-realm) and the debug surface no longer register on production pods. Registration requires both DEVELOPMENT=true andFEATURE_DEV_ENDPOINTS=true; the destructive endpoints additionally require super-admin auth even on dev pods. Closes a long-standing security hole where a misconfigured pod could expose dev/reset to unauthenticated callers.
  • New org-scoped boundary-balance-sheet endpoint. GET /api/v1/custody/boundary-balance-sheet?realmId=… returns the same per-boundary surplus / obligations / on-chain attribution shape as the platform-admin endpoint at /api/v1/admin/realms/{realmId}/boundary-balance-sheet, but gated on builder API key + realm-access check instead of platform-admin role. Lets a builder introspect their own realm's contract bookkeeping (stranded surplus, replay-vs-onchain drift, per-boundary solvency) for dashboards and alerts. Surfaced after the 2026-05-03 regression run hit a 403 on the admin path when the QA-regression API key (a regular org-scoped builder key) tried to verify boundary conservation.
  • Chart self-healing parity fix across the SDKs. Two follow-ups to the 2026-05-02 chart self-healing change. Swift watchPnlChart was missing the new recovery wiring (onResume, onAuthenticated, wall-clock boundary timer, multi-bucket gap detection, live-tail sliding window) — only watchEquityChart and watchCandleChart had received it, so a backgrounded iOS chart silently failed to refetch on foreground. TypeScript WebSocketManager.connect() did not reinstall the visibilitychange listener that disconnect() removed — after a single disconnect-then-reconnect cycle (e.g. an idle-timer drop followed by a watch-stream re-acquire) the tab-resume handler was permanently lost for the rest of the session, breaking onResume for every consumer. Both paths now match the documented behavior. No code changes required.
  • Exchange-arca delete now retries the venue-mint absorb step indefinitely instead of swallowing transient failures. When deleting an exchange arca with venue equity, the workflow withdraws the equity from the venue (which mints fresh tokens at the on-chain pool address), then folds those tokens into the source isolation boundary via AbsorbVenueMintToBoundary. Previously the absorb activity inherited the workflow-level MaximumAttempts: 5 retry policy and silently dropped any failure with a warn-and-continue branch, leaving the just-minted tokens stuck as "stranded surplus" (tokens at the pool address but not attributed to any boundary). The absorb section now runs in a dedicated activity context with MaximumAttempts: 0 (unlimited retries), so transient chain-RPC blips, brief eth_call stale reads, and chain monitor lag can no longer accumulate stranded surplus. Persistent non-retryable errors (e.g. contract reverts) still fall through to the warn-and-continue safety net so the user's sweep completes and the operator handles cleanup via the absorbUnattributedadmin runbook. Discovered after the 2026-05-03 regression run hit I8-SurplusMagnitude with $15,295 of accumulated stranded on the qa-regression realm — root-caused to this exact code path producing ~$200 of new stranded per regression run.

2026-05-02

  • Historical equity / P&L charts now self-heal across tab idle. Both the TypeScript and Swift SDKs gained four new recovery triggers on the chart streams returned by watchEquityChart / watchPnlChart / watchCandleChart: tab/app foreground (onResume), every successful WebSocket auth (onAuthenticated), a multi-bucket time jump on the agg-tick path, and a wall-clock boundary timer for quiet realms. The watch's requested window also slides forward on every refresh when the original to was within ~60s of construction time, so a 1h chart left idle in a backgrounded tab now shows the most recent 1h instead of a stale window with a missing tail. iOS / tvOS / macOS apps get the same behavior via UIApplication.willEnterForegroundNotification and NSApplication.willBecomeActiveNotification observers; web apps via document.visibilitychange. No code changes required for builders consuming watchEquityChart / watchPnlChart.
  • Recovery-hatch boundary state is now mirrored end-to-end. When an isolation boundary's recovery-key holder calls lockForRecovery, unlockBoundary, withdraw, or assignRecoveryKey on the on-chain custody pool, the platform now (1) records the chain observation in custody_contract_events, (2) transitions the boundary's status in a new custody_boundariescache (activesoft_frozen hard_frozen), (3) refuses subsequent value-moving operations on the boundary at the API entry-point with 409 BOUNDARY_FROZEN, (4) on Withdrawn sweeps every user wallet in the boundary into a system-owned recovery arca at /.recovery/<boundary-suffix> via a new RecoveryFreezeWorkflow, and (5) mirrors any subsequentWithdrawn or unexpected Deposited against that recovery arca so the platform's ledger stays consistent with the on-chain pool balance. Builders see the new state via ArcaObject.boundary: present only when the boundary is frozen, with status, timestamps, the recovery key address, the triggering chain tx hash, and the recovery arca path. The portal renders a banner and disables mutating actions on objects whose boundary is frozen. Soft-freeze is reversible — a recovery key holder can lock out of caution without nuking ledger state — and only converts to hard_frozen if a Withdrawn is observed. Both the TypeScript and Swift SDKs surface ArcaObject.boundary as an optional BoundarySnapshot. A new super-admin endpoint GET /api/v1/admin/custody/boundary?realmId&boundaryId returns the boundary's current state row plus its full custody_contract_events timeline for incident review, and POST /api/v1/admin/custody/backfill-boundaries replays historical pool events for a realm into the live monitor handlers when a boundary needs reconciliation after the fact.
  • ⚠️ Realm creation now requires a working custody pool for every realm type. POST /api/v1/realms previously required a custody pool only for production realms; development realms silently degraded into a half-broken state when the dev chain wasn't reachable, and the regression suite's getCustodyStatus probe later saw an empty response with no clear cause. Both realm types now share the same gate: if the routed chain provider isn't ready (no impl deployed, RPC unreachable) or EnsurePool fails,CreateRealm returns 503 SERVICE_UNAVAILABLE with a message naming the chain and the underlying error, and the orphan realm row is soft-archived so the same name can be retried. Discovered by the 2026-05-02 production regression run that hit a startup race when the new v4-selector requirement landed before the HyperEVM v4 impl was deployed (memory 035c82d1): three platform pods skipped pool registration entirely after a Spanner deadline-exceeded during boot, and the probe happened to land on one of them. Read-path lazy registration now self-heals any pod that missed startup registration on the first request — no operator intervention required.
  • Operator withdrawals now annotate the custody replay model. Every successful operatorWithdraw on the production custody contract was previously silently skipping its corresponding AnnotateWithdrawal call, leaving the in-memory replay model's per-boundary balance over-reporting by exactly the withdrawn amount on every withdrawal. Discovered by the 2026-05-02 tiptoe-prod-realm runbook recovery drill, which expected replay = $50 after the recovery key drained the boundary but observed replay = $100 instead. Fixed in the worker activity sendFromPool: a new annotateOperatorWithdrawal helper mirrors the on-chain decrement into the replay model on the success path of every CustodyOperatorWithdraw. The platform-go admin path (CustodyService.SendFromPool) used to swallow annotation errors via _ = AnnotateWithdrawal(...); it now propagates them so callers can retry rather than letting the boundary balance sheet drift. Three new regression tests pin the contract: TestAnnotateOperatorWithdrawal_RecordsCallOnContractClient, _NilCustodyClient_NoOp, _PropagatesErrorFromContractClient. No user-visible behavior changes — this only affects the diagnostic replayBalance / replayDrift values surfaced in the admin boundary balance sheet. Recovery-key withdrawals are still (intentionally) unannotated, so post-fix the drill produces drift = +amount exactly equal to the recovery-key drain — the correct alarm signal for "recovery hatch fired."
  • ⚠️ Denomination is no longer a request input on object creation. POST /api/v1/objects drops the denomination body field; denominated and exchange objects are stamped USD server-side. The TypeScript SDK (arca.ensureDenominatedArca, arca.ensureArca), Swift SDK (arca.ensureDenominatedArca, arca.ensureArca), CLI (arca object ensure --denomination flag removed), and Portal "Create Object" modal (denomination dropdown removed) follow suit. The agent propose_plan tool no longer describes a denomination parameter for createObject. Wire clients still sending the field will have it silently ignored. Read paths are unchanged — every object, balance, fee, and operation response continues to expose denomination for forensic visibility, and the underlying journal, aggregation, solvency, and TLA+ specifications remain multi-denomination by construction. When a second denomination is ready to ship, the parameter will be re-exposed on the same denominated type.
  • Withdrawal payment links now accept a destination address at creation time. POST /api/v1/payment-links for type: withdrawal accepts a new optional destinationAddress field (0x-prefixed 20-byte hex EOA). It is required for production-realm withdrawal links; the platform refuses to invent a destination for a real chain. Development-realm links keep the legacy 0x000…dEaD fallback when the field is omitted. The destination is captured into the operation's input JSON at link-creation time and re-validated at completion (defensive — the creation path is the authoritative gate). The arca payment-link create CLI gains a matching --destination flag. Closes the gap that previously made the production-realm withdrawal flow impossible to drive end-to-end: link completion blocked with "Withdrawal payment links require a destination address for production realms" but no surface accepted one.
  • Chain monitor surfaces RPC failures and recovers from longer outages. The platform's deposit-detection chain monitor now logs eth_getLogs failures at Error level (previously Debug, which was silenced in production). A 2026-05-02 HyperEVM tiptoe deposit was confirmed on-chain but invisible to the platform for 30+ minutes because Dwellir's free plan returns HTTP 403 for eth_getLogs. The cold-start block lookback (depositScanLookback) was raised from 100 to 5000 blocks (≈83 minutes on HyperEVM's ~1s cadence) so a transient RPC outage no longer permanently hides a deposit once head advances past the previous 100-block window. New regression test TestDepositScanLookback_CoversFastChainOutage pins the floor at 1000 blocks. Production runbook now lists the eth_getLogs precheck in Prerequisites and the symptom in Common Failures.

2026-05-01

  • Pay page surfaces the full deposit transaction hash and links it to the chain's block explorer. After MetaMask returns the pool.deposit(...) hash on the hosted /pay/{token} page, the "Waiting for confirmations" subtitle now renders the full hash (so it can be copied) and, when an explorer is configured for the realm's chain, wraps it in a clickable link. GET /api/v1/payment-links/{token} exposes a new optional onChainDeposit.explorerTxUrlTemplate field — a URL with a literal {hash} token to substitute. Returned as https://hyperevmscan.io/tx/{hash} for production HyperEVM realms; omitted for chains without a public explorer.
  • Production-realm payment links now carry on-chain deposit details and the pay page can submit them via MetaMask. GET /api/v1/payment-links/{token} for production-realm deposit links includes a new onChainDeposit object with chainId, poolAddress, tokenAddress, boundary (bytes32), amountRaw (USDC 1e6 units), and ref (bytes32 — informational, used for on-chain audit correlation; the chain monitor matches deposits by amount). The hosted Arca pay page (/pay/{token}) renders a "Deposit with MetaMask" button for these links instead of the simulated "Complete" flow: connect wallet → switch network → optionally usdc.approve(pool, amountRaw) pool.deposit(boundary, amountRaw, ref). Wallet failures or rejected requests fall through to a manual panel showing the raw on-chain payload so the deposit can be submitted from another wallet without losing the matched DepositIntent. Development-realm payment links are unchanged and continue to use the existing simulated complete flow.
  • Production chain operations now require an explicit operator-controlled deployer key. New required environment variable PROD_REALM_CHAIN_DEPLOYER_KEY (hex-encoded ECDSA private key) for both platform-go and worker-go when FEATURE_PRODUCTION_REALMS=true. Without it the chain client previously fell back to the well-known anvil dev key (account 0 of the public dev mnemonic), which on any mainnet means anyone can submit transactions at the same nonces and grief deployments. The startup guard refuses to boot whenPROD_REALM_CHAIN_RPC_URL is set and the deployer key is missing or empty.
  • Production chain operations now require an explicit canonical token address. New required environment variable PROD_REALM_CHAIN_TOKEN_ADDRESS (the canonical USDC contract on the production chain) for both platform-go and worker-go when FEATURE_PRODUCTION_REALMS=true. The chain client previously discovered the token by forward-scanning the deployer's nonce 0 — which works on the dev chain (where the same deployer bootstraps MockUSDC) but mis-classifies whatever happens to sit at nonce 0 on a real chain (typically our ArcaCustodyPool implementation contract) as the canonical token. Without this set, per-realm proxies were initialized against the wrong token and the chain monitor watched a contract that never emitted USDC Transfer events. The platform's startup guard now refuses to boot when this is missing; the worker's app-init guard mirrors it.
  • Cross-boundary internal transfers now keep custody in sync with the journal. POST /api/v1/transfer between two paths that resolve to different isolation boundaries automatically dispatches the custody contract's crossTransfer between the source and target boundaries (recorded as a chain_transactions row with purpose transfer.cross_boundary). Previously these transfers committed a balanced ledger entry but never asked the contract to follow, producing a phantom per-boundary surplus on the source side and matching deficit on the target side that net to zero at the realm level. Cross-boundary transfers now wait for on-chain confirmation before the operation reaches completed (~ requiredConfirmations × block time on top of the journal commit); same-zone transfers continue to settle immediately. Transfers from a source boundary with a non-zero timeLock are rejected upfront with a new VALIDATION_ERROR: Cross-boundary transfer not supported when source boundary has a time lock; use admin rebalance. Operators with a genuine time-locked rebalance need go through POST /admin/realms/{realmId}/boundaries/rebalance, which handles the pending-exit lifecycle.
  • Explorer and operations APIs can filter by exact Arca object ID. GET /api/v1/operations, GET /api/v1/operations/export, and the TypeScript SDK's listOperations / exportOperationEvidence now accept arcaId for one object's operation history without including sibling or child paths. The portal object detail panel links directly to that object-scoped operations view and restores the object detail when returning to Objects.
  • TWAP integration: typed events, watchTwap, server-driven limits, idempotent cancel, and slice-level slippage enforcement. Several builder-reported gaps in the TWAP surface are addressed in one batch:
    • cancelTwap on a TWAP that has already completed, cancelled, or failed now returns 200 OK with the existing record (previously 400 VALIDATION_ERROR).
    • The Twap response gains expectedSliceCount (the planned total, derived from durationMinutes × 60 / intervalSeconds; min 1) so a freshly placed TWAP no longer renders as 0/0 slices while the scheduler materializes the schedule, and 1h × 1h interval reports 1 slice instead of zero. targetPrice (the mid at create time, used for "vs target" deltas from slice 1) and failureReason (descriptive last-slice error on terminal status === 'failed') are also new.
    • New GET /api/v1/twap/limits returns the universal limits and a duration-keyed recommendation curve (≤4h → 5m, ≤1d → 15m, longer → 1h). SDKs cache the response for the process lifetime; arca.recommendedIntervalSeconds(durationMinutes) is the one-shot helper.
    • Five typed WebSocket events twap.started, twap.progress, twap.completed, twap.cancelled, and twap.failed are enriched server-side with the full Twap payload, so SDKs hand builders strongly-typed events without a follow-up REST fetch.
    • New SDK methods arca.watchTwap(exchangeId, operationId) (TS and Swift) deliver a live single-TWAP stream that combines the initial REST snapshot with the typed event stream — replaces the per-app workaround of filtering the global firehose.
    • A TWAP's configured slippageBps is now honored at the matching engine on every slice: a per-side price ceiling of mid × (1 ± bps/10000) is passed through the workflow → activity → sim-exchange path. Previously the value was stored on the parent TWAP record but inert at fill time.
    • New SDK helper admin.mintDeviceToken(...) (TS and Swift) replaces the boilerplate IAM PolicyStatement construction with three presets (read, trade, full) and an optional forUserPath resource scope.
  • fill.recorded events now include a top-level fee field. The realtime fill.recorded WebSocket event payload was missing the top-level fee field that GET /objects/{id}/exchange/fills returns, forcing realtime consumers to compose it from exchangeFee themselves and causing fills loaded over the WebSocket to render with a $0 fee in some clients while the same fill from REST showed the correct value. The realtime payload now mirrors the REST shape exactly: fee is populated with the same exchange-fee component that REST FillResponse.fee returns. The dedicated exchangeFee / platformFee / builderFee breakdown fields are unchanged. No SDK type changes are required — TS Fill.fee and Swift Fill.fee were already declared optional.
  • SDK: watchFills two-phase semantics documented. FillWatchStream docstrings (TS and Swift) now make it explicit which surface to read for which use case: fills (the merged list — preview rows replaced in place by their authoritative fill.recorded counterparts via correlationId) for activity feeds, and the updates stream / onUpdate callback only when you need the preview→recorded transition itself (e.g. a "pending → confirmed" animation). Deduping by Fill.id alone does not work because the preview's id is the venue's fill id while the recorded's id is the platform's position-ledger row — always merge by correlationId / orderId if you consume the stream directly.

2026-04-30

  • Deposits to a deleted arca now auto-recover instead of failing. When a chain-confirmed deposit reaches the platform but its target arca was deleted between intent and ledger commit (a rare race that the existing delete gate normally blocks), the tokens previously stuck as a pool surplus and the operation transitioned to failed. They now route automatically to a system-managed denominated arca at /.recovered/recovery-N (per realm, sequential N) and the operation completes with outcome.status = "recovered". The recovery arca is an ordinary denominated arca — builders can sweep its full balance to the intended destination via a normal transfer (or delete-with-sweep), and the arca's existence itself acts as the "unprocessed" flag. Operation metadata under outcome includes the original target path, recovery path, and amount so the source of the funds is auditable. No SDK changes are required; deposit.recovered is a new event type subscribers can opt into. Exchange deposits pull tokens back from the venue first so the recovery credit is fully pool-backed.
  • Equity history charts gain 15m and 30m rungs. The aggregation resolution ladder now includes intermediate rungs between 5m and 1h: 1m / 5m / 15m / 30m / 1h / 4h / 1d. The biggest user-visible impact is the 1W chart: at the default 1000-point budget it now picks 15m (672 buckets) instead of 1h (168 buckets) — roughly 4× denser intra-week detail. Compact-budget (500-pt) 1W charts pick 30m (336 buckets). All other periods (1D / 1M / 3M / 12M) are unchanged. GET /history and GET /pnl/history response payloads on 1W queries grow ~4× in row count; payloads are highly gzip-compressible so over-the-wire size grows much less. SDKs (TS, Swift) accept the new resolution strings transparently — no code changes required for consumers.
  • Hyperliquid's $10 minimum order notional is now enforced platform-wide. Every order placed through sim-exchange is rejected with VALIDATION_ERROR when size × price falls below $10. Reduce-only orders and positionTpsl trigger orders are exempt so dust positions can always be closed. The TWAP service now also pre-validates expected slice notional at create time when a mid price is cached, so a TWAP whose every slice would fail the floor is rejected up-front rather than transitioning to failed ~2 minutes later. New SDK helper arca.getOrderLimits() returns { minOrderNotionalUsd: 10 } for client-side pre-validation. The existing arca.getTwapLimits() minSliceNotionalUsd field is unchanged in shape and is now an honest reflection of the venue-enforced floor.
  • SDK: placeOrder.size and placeTwap.totalSize are base-asset units, not USD. JSDoc on both fields was tightened to make the unit explicit. For example, placeOrder({ coin: 'hl:BTC', size: '0.001' }) opens a 0.001 BTC position; it does not spend $0.001. This was always the platform contract — the docs now match.
  • Exchange fills feed no longer leaks venue-reconciliation audit rows. GET /api/v1/objects/{id}/exchange/fills now strictly returns actual exchange fills. Internally, the underlying arca_position_ledger table is also written to by the venue position reconciler when it heals drift between platform and venue position state — those rows have no fill side, size, price, order ID, or fees, and were previously serialized into FillListResponse alongside real fills, where they rendered as empty "dash@dash, fee $0" entries in client UIs (Svall iOS, the prototype, the SDK's arca.listFills). The response now filters those audit rows at the API boundary so every consumer is correct by default. Pagination cursors are anchored to the underlying DB row order, so paginating past pages that contain reconciliation rows still works.

2026-04-30

  • SimPosition exposes cumulativeFee alongside cumulativeFunding. Open positions now carry a per-lot trading-fee accumulator so consumers can show "fees accrued" on a Position Detail screen without re-aggregating fills client-side. New fields on SimPosition (TS and Swift):
    • cumulativeFee — total fees paid over the position's current open lot, equal by construction to cumulativeExchangeFee + cumulativePlatformFee + cumulativeBuilderFee.
    • cumulativeExchangeFee / cumulativePlatformFee / cumulativeBuilderFee — per-bucket breakdown matching the buckets on Fill.
    Lot semantics match cumulativeFunding: accumulators reset when the position fully closes (the row is deleted) or flips through zero (a new lot starts at the opening-portion's proportional share of the flip-fill's fees). Fees are computed in sim-exchange-go (single source of truth, no client-side aggregation needed) and flow through the gRPC/HTTP/WS surfaces unchanged. Fields are omitted on positions with no accrued fees so consumers can distinguish "fee-free so far" from a placeholder zero.
  • TypeScript SDK: cumulativeFunding now reaches the SDK. The TS SimPosition type was missing the cumulativeFunding field that the Swift SDK already exposed. The value flowed correctly on the wire all along; only the TypeScript type declaration was incomplete.

2026-04-29

  • P&L and aggregation responses now expose mid-price freshness. /objects/pnl responses include an endingMidPrice object and /objects/aggregate responses include a midPrice object. Both carry marketDataAsOf (the timestamp of the freshest mid the engine consulted), ageSeconds, source ("snapshot" | "cache" | "none"), staleAfterSeconds, and a derived stale flag. Clients can detect when upstream market data has stalled and surface a freshness indicator instead of silently rendering frozen valuations. Internal records remain the authority for equity computation; this change is purely informational.
  • Sim-exchange writes are now EIP-712 signed. Every per-account sim-exchange write (PlaceOrder, CancelOrder, SetLeverage, Withdraw, Deposit, LiquidateAll, DeleteAccount) now carries an EIP-712 envelope signed by the account's bound wallet, matching production Hyperliquid behaviour. Operator RPCs (DeleteAccountsByRealm, ResetAll, SweepFees, FundAccount) sign with a separate platform operator wallet verified against an env-injected allowlist. This change is internal — the SDK and REST surface are unchanged for builders. The rollout proceeds in shadow mode by default; flipping SIM_REQUIRE_SIGNED_WRITES=true on sim-exchange enables enforcement and any unsigned/invalid request is rejected with SIGNATURE_MISSING, SIGNATURE_INVALID,WALLET_NOT_BOUND, NONCE_REUSED, TIMESTAMP_SKEW, or OPERATOR_NOT_ALLOWED.

2026-04-28

  • ⚠️ Realms: realm types are now development or production. The legacy demo, testing, and staging realm types have been removed from the API, SDKs, CLI, and portal. Existing non-production realms are migrated todevelopment; new create requests must use development or production.
  • History charts: V1 fallback retired. Equity and P&L history now serve both legacy-shaped prefix requests and current target/kind requests from the V2 snapshot read path. The old delta/path-cache fallback and object-balance snapshot writer have been removed.

2026-04-27

  • Swift SDK: operation type decoding is forward-resilient. OperationType now includes venue_close and preserves unknown future operation type strings as unknown(String), so operation-carrying REST responses and WebSocket events continue decoding when the platform adds a new operation type.
  • WebSocket mids: batch-level freshness. mids.updated events now expose a single polishedmarketDataAsOf timestamp for the oldest mid in the update instead of sending per-coin source-time diagnostics by default.
  • History charts: per-bucket position revaluation for exchange objects. Equity and P&L history endpoints now mark-to-market open exchange positions at every bucket, not just at fill / funding events. Charts that previously stayed flat between cash-impacting events now move with the underlying market mid the same way the live equity panel does. Buckets where a position lacks a mid are returned with status: "incomplete". Read-time only — no schema changes, no backfill required.
  • History charts: denser default resolutions for longer ranges. Explorer history charts now request more samples and the aggregation ladder includes a new 4h rung, so expanded charts target 5m for 1D, 1h for 1M, 4h for 3M, and 1d for 12M. The consolidated history path projects stored balance-change snapshots into the requested bucket size at read time, so the new rung does not require a separate per-resolution 4h backfill.
  • SDK: V2 equity and P&L chart streams recover from chart snapshot pushes. watchEquityChart and watchPnlChart now preserve V2 history metadata (status, resolution details, and server timing) and subscribe to the newchart.snapshot.updated WebSocket signal. On reconnects or delivery-sequence gaps, the SDK refetches the visible V2 history window so missed sealed/carried buckets converge to the server rows instead of relying only on local rollover guesses.

2026-04-20

  • ⚠️ SDK: orderBreakdown liquidation estimate is now cross-margin. Arca.orderBreakdown's estimatedLiquidationPrice previously used an isolated single-position formula that ignored every other position and the rest of the account's collateral, so the value drifted from the actual liquidation price the user would see post-fill. It now uses the same cross-margin formula the sim-exchange backend uses (marginAvailable = equity − maintenanceMargin, then liq = price ∓ marginAvailable / size) and applies the same merge rules to any existing same-coin position (same-side blends entry, opposite-side reduces or flips). To compute this faithfully,OrderBreakdownOptions now requires an accountContext field (account equity, maintenance margin from positions in other coins, and any existing same-coin position) whenever maintenanceMarginRate is provided. This is a source-breaking change for existing callers using maintenanceMarginRate; callers that don't need a liquidation estimate can omit both fields. In Swift, use the new initializer overload that accepts bothmaintenanceMarginRate and accountContext.

2026-04-16

  • SDK: orderBreakdown estimates liquidation price. The Arca.orderBreakdown function now returns an estimatedLiquidationPrice when the new optional maintenanceMarginRate parameter is provided. This isolated estimate assumes the order is the only open position. To support this, ActiveAssetData (returned by getActiveAssetData and watchMaxOrderSize) now includes a maintenanceMarginRate field, populated from the asset's base margin tier or a global default. This is fully backward-compatible: omitting the new parameter yields the same breakdown as before.
  • SDK: watchMaxOrderSize resolves dynamic maintenanceMarginRate. ActiveAssetData.maintenanceMarginRate emitted by watchMaxOrderSize previously defaulted to "0.03" on every recompute, which fed an incorrect value into orderBreakdown's liquidation estimate for tiered assets (e.g. BTC, where the true base MMR is "0.01"). The TypeScript and Swift SDKs now auto-fetch the per-asset MMR once via getActiveAssetData and thread it through every recomputation. Callers can also pass maintenanceMarginRate directly via MaxOrderSizeWatchOptions to skip the lookup.
  • Swift SDK: bounded buffers on real-time watch streams. All snapshot-style watch streams (watchPrices, watchExchangeState, watchObject, watchObjects, watchAggregation, watchMaxOrderSize, watchCandleChart) now use a bufferingNewest(1) policy on their underlying AsyncStream. If a consumer is slower than the producer (for example, hopping to MainActor on every tick to update a SwiftUI @Published), intermediate updates are dropped in favor of the latest value rather than accumulating in memory. Previously, the default unbounded buffer caused linear memory growth (~700 MB OOM after 80 s of an idle watchPrices() consumer with 392 coins). watchPrices additionally skips the yield entirely when the incoming mids snapshot contains no actual change. The SendableBox.onChange push-based API remains available and is unaffected.
  • Market metadata: logoSources array. getMarketMeta and asset(coin) now include a logoSources array with pre-rendered raster variants (128, 256, 512 width) in WebP and PNG formats. The existing logoUrl string remains for backward compatibility, now pointing to the 128px WebP. Use the new array to pick the smallest size that fits your rendering target, saving significant memory when rendering lists.
  • SDK: watchPrices ergonomics. arca.watchPrices() now accepts an options object: coins narrows the subscription to specific canonical market IDs (multiple narrow subscribers share one WebSocket subscription via set-union), and readyTimeoutMs bounds the initial snapshot wait (rejects with the new WatchStreamReadyTimeoutError). The returned PriceWatchStream exposes get(coin) and getNumber(coin) convenience accessors. .prices now returns a fresh object reference on every tick, so reference-equality checks (React state, Zustand selectors) correctly detect changes. The legacy positional watchPrices(exchange) call still works. WatchStream.ready() also accepts a timeoutMs argument so app startup can degrade gracefully when the backend is unreachable. The React hook useArcaPrices now surfaces 'reconnecting' status and accepts all the same options.
  • Authentication: Google-only Auth. Email and password authentication has been removed from the platform in favor of a simpler, more secure Google-only OAuth flow. The /api/v1/auth/signup and /api/v1/auth/signin endpoints have been removed. Use /api/v1/auth/google for all authentication. The CLI no longer supports the --no-browser flag, as authentication now strictly requires a browser for the Google OAuth consent screen.
  • Swift SDK: diagnostics & structured logging. Arca.init now accepts logLevel (defaults to .warning) and an optional logHandler. The SDK emits records to Apple's unified logging system (subsystem io.arcaos.sdk) and, if set, to the host handler — making it easy to forward to Datadog, Sentry, Crashlytics, or a custom backend. REST errors inside background tasks (watch snapshots, gap-recovery refetches, candle retries, CDN fallbacks) that were previously swallowed by try? now surface as warning-level records. WebSocket lifecycle events — server errors, delivery gaps, reconnect backoff, heartbeat stalls, token refresh failures — are also logged. Builders upgrading will immediately see previously hidden warnings in Xcode. See Logging & diagnostics for Datadog, Sentry, Crashlytics, and custom adapter examples. The three print("[ArcaSDK] …") sites have been replaced by structured records.

2026-04-21

  • SDK: true historical equity for equity-anchored charts. The watchPnlChart(anchor: 'equity') method in both the Swift and TypeScript SDKs now populates the valueUsd field using the server's authoritative historical equityUsd for each point, rather than applying a fixed offset derived from current live equity. This provides a true point-in-time portfolio value view that accurately reflects historical balances and flows.

2026-04-15

  • SDK: cached market metadata lookup. New asset(coin) method on both TypeScript and Swift SDKs returns the SimMetaAsset for a canonical coin ID (symbol, display name, logo, leverage limits, fee scale, etc.). Metadata is lazily fetched on first call and cached for the session. Use preloadMarketMeta() to warm the cache at startup, or refreshMarketMeta() to force re-fetch after new assets are listed. Concurrent calls coalesce into a single API request.
  • Transfers: per-command fee override. The transfer() SDK method, POST /api/v1/transfer API endpoint, and arca transfer CLI command now accept an optional feeOverride parameter. Set to "0" to disable the $0.05 platform fee for a specific transfer, or any non-negative decimal for a custom fee. Only permitted in development realms. Production realms always use the standard fee.
  • WebSocket: real-time market trade streaming. New subscribe_trades / unsubscribe_trades WebSocket actions deliver a live trade tape per coin. Trades arrive as batched trades.batch messages. SDK adds watchTrades(coins) which returns a TradeWatchStream, and lower-level acquireTrades / releaseTrades / onTradeExecuted on the WebSocket manager.

2026-04-14

  • Trading: Tiered margin tables support. Sim-exchange now dynamically enforces margin and leverage limits based on these tier definitions.

2026-04-09

  • WebSocket: per-message compression enabled. All WebSocket connections now negotiate permessage-deflate with context takeover (RFC 7692). Repeated JSON structure compresses 3-5x with no SDK or client changes required.
  • WebSocket: batched candle delivery. The SDK now sends batch: true with subscribe_candles, and the server coalesces all candle.updated events in a tick into a single candles.updated message. This reduces WebSocket frame count from N to 1 per batch. candle.closed events are always sent individually. The SDK handles this transparently — onUpdate callbacks still fire per candle. Raw WebSocket users can opt in by adding batch: trueto the subscribe_candles message; without it, the old per-event format is used.

2026-04-08

  • Trade summary: fee breakdown, funding totals, and position-level cost attribution. tradeSummary() now returns per-fee-type breakdown (totalExchangeFees, totalPlatformFees, totalBuilderFees), cumulative funding (totalFundingPaid / totalFundingReceived), and an openPositionCosts object per market with costs attributed to the current open position using proportional close-out accounting. Existing fields are unchanged.
  • Swift SDK: new fields and methods. Added platformFee on SimFill, cumulativeFunding on SimPosition, getAssetFees(objectId:builderFeeBps:) for per-asset fee rates, and enableAutoTracking(operations:balances:exchange:) for optimistic UI updates. Also added AssetFeeEntry type.
  • Swift SDK: PaymentLinkView now uses WKWebView. PaymentLinkView / .paymentLinkSheet() now uses WKWebView instead of SFSafariViewController, enabling window.close() support for returnStrategy: "close".
  • TypeScript SDK: aggregation revaluation includes perp entries. revalueAggregation() now revalues perp breakdown entries using fresh mid prices, keeping aggregation equity in sync with per-object valuations between server pushes.
  • Platform: object creation response includes timestamps. createObject responses now populate createdAt and updatedAt immediately, enabling deterministic ordering of newly created objects without waiting for background workflow completion.
  • Docs: clarified fundAccount behavior by realm type and documented that watchCandleChart count is best-effort (sets time window, actual count depends on asset history).
  • Platform: realm listing now returns realms across all org memberships. GET /api/v1/realms now returns realms from all of the builder's organizations, not just the first resolved org. Builders with multiple org memberships will now see all their realms. When a preferred org header is set, results are scoped to that org only.
  • API: nextFundingTime now populated on market tickers. MarketTicker.nextFundingTime is now returned as a Unix-ms timestamp from the /api/v1/exchange/market/tickers endpoint and SDK getMarketTickers() calls.
  • Fix: P&L chart no longer double-counts initial deposit. When a chart window started before the account's first deposit, the deposit was counted both in startingEquityUsd (via leading-zero trimming) and in cumulative inflows, producing an inflated loss. getPnlHistory now returns an effectiveFrom field, and watchPnlChart uses it to align flowsSince correctly. Affects both TypeScript and Swift SDKs.
  • Fix: portal exchange positions now revalue on live mid prices. The portal's exchange detail panel previously displayed stale unrealized P&L from the sim-exchange snapshot. It now revalues cached exchange positions on every WebSocket mid-price update, matching the SDK's behavior.

2026-04-07

  • Platform + API + Portal: builder operation signing keys. Builders can now register Ed25519 signing keys and sign API requests for cryptographic non-repudiation. New endpoints: POST /api/v1/signing-keys (register), GET /api/v1/signing-keys (list), DELETE /api/v1/signing-keys/:id (revoke), POST /api/v1/signing-keys/:id/cancel (cancel pending), GET /api/v1/signing-keys/:id/events (audit log). The server verifies X-Arca-Key-Id, X-Arca-Signature, and X-Arca-Timestamp headers and records signatures on operations. Signing is optional in this release; it will become required for production realms in a future release. A new portal page under Manage → Signing Keys provides key registration, revocation, and lifecycle visibility. Key lifecycle events are recorded in a hash-chained, tamper-evident audit log.

2026-04-06

  • Objects + exchange processing: stricter object path validation and more robust venue account lookup. Object creation now rejects path segments containing whitespace or control characters, preventing malformed identities from entering the system, and exchange account resolution now uses canonical venueAccountId metadata when mapping sim-exchange events back to Arca objects.
  • SDK + streaming: candle WebSocket events now include optional delivery timestamps. Candle subscriptions still emit the same coin, interval, and candle payload, and now may also include publishedAt, platformReceivedAt, and wsSentAt for delivery attribution and latency debugging.
  • SDK: client-side recovery key generation. New static method Arca.generateRecoveryKey() creates a 12-word BIP-39 mnemonic and derives the Ethereum address at the standard BIP-44 path, entirely client-side. Uses crypto.getRandomValues for entropy and zeroes all intermediate key material after derivation. Recovery key docs now cover a two-path UX model: “use an existing wallet” (recommended) or “generate a new one”, with platform-specific backup guidance for iOS, Android, and web.

2026-04-05

  • Explorer + SDK + API: path-level isolation zones. The explorer browse response now includes path entries and isolation metadata so the portal can mark boundary roots and paths contained within a boundary. A newPOST /api/v1/isolation-zones endpoint andarca.createIsolationZone({ path }) SDK method let builders declare an isolation zone before any object exists under that path, fixing the empty-path race for zone-first explorer workflows.
  • Explorer + SDK + API: Phase 1 audit evidence export. Operation detail now accepts includeEvidence=true, returning journal-backed evidence, proof references, chain transaction metadata, and integrity annotations for a single operation. A new GET /api/v1/operations/export endpoint exports the same evidence bundle across a realm and time range, the TypeScript SDK addsgetOperation(id, { includeEvidence: true }) andexportOperationEvidence(...), and the portal Explorer now supports copy/download of per-operation JSON plus realm-scoped evidence export.
  • Objects: full withdrawals now terminate the object path. When a non-exchange Arca is fully withdrawn, its status becomes withdrawnand the response includes withdrawnAt. Withdrawn paths remain visible in history, show a zero-cliff in aggregation after the withdrawal timestamp, and cannot be reused for a new object generation.
  • Objects: deleted generations now expose tombstone displayName. Object responses now include an optional displayName field. Active objects usually leave it unset, while deleted objects receive a tombstone label like wallet.deleted.YYYY.MM.DD.SSSSS so explorer and version-history views can distinguish repeated deleted generations at the same canonical path.
  • SDK: custody contract integration. New methods for reading on-chain custody state (getCustodyStatus, getBoundary, listBoundaries, listExchangeArcas), registering recovery keys (registerRecoveryKey), and preparing unsigned sovereignty transactions (prepareLockBoundary, prepareWithdraw, prepareAssignRecoveryKey, etc.). Recovery keys give users non-custodial control over their isolation boundaries: lock, unlock, withdraw, and emergency recovery.
  • API: custody endpoints. New REST endpoints for custody contract state: GET /custody/status (full aggregated view including boundaries, exchange arcas, and venue halts), GET /custody/boundaries, GET /custody/exchange-arcas (with optional boundaryId filter), and POST /custody/recovery-key. The /custody/status response now includes contractAddress, chainId, and all nested state in a single call.
  • ⚠️ Backend: balance ledger writes fully migrated to journal. All balance-affecting write paths in the worker service now commit exclusively through the unified journal (journal_entries / journal_legs). Legacy arca_balance_ledger writes have been removed from all activities. All read paths (forensics, aggregation, explorer snapshots, balance ledger compatibility layer) have been migrated to source data from the journal tables.

2026-04-03

  • Swift SDK: fixed live P&L chart discontinuity in watchPnlChart. The live tail point now correctly accounts for deposits and withdrawals. Previously, the Swift SDK's flow accounting was non-functional — deposits appeared as profit and withdrawals as losses, causing a massive jump at the chart's live edge (especially visible with anchor: .equity). The fix uses server-authoritative cumulative flow values via the new flowsSince parameter on watchAggregation.
  • Swift SDK: watchAggregation now accepts flowsSince. Pass an ISO 8601 timestamp to receive cumulative inflow/outflow values computed from that point forward. PathAggregation now includes cumInflowsUsd and cumOutflowsUsd fields.
  • SDK: reduced chart discontinuity on reload. Both TypeScript and Swift SDKs now trim trailing server-generated live points from historical chart data, preventing small jumps where the server's HTTP-time equity diverged from the WebSocket-time equity.

2026-04-02

  • Market data: guaranteed sparklines via background pre-computation. Sparkline data is now pre-computed every ~5 minutes by a background process and stored in Redis. All tracked coins are always included — no more missing sparklines due to timeouts or computation pressure. The serving path is a single Redis read, making it faster and more reliable across all replicas. Sparkline data may be up to 5 minutes behind real-time; for live price information, use mid prices (watchPrices) or candle subscriptions (subscribeCandles).
  • ⚠️ Sparklines: interval and points parameters removed. The sparkline endpoint now always returns 24 hourly close prices. The query parameters are still accepted for backward compatibility but ignored. Previously, requesting a non-default interval/points combination would return empty data because only the default (1h/24) was pre-computed. Use getCandles for custom intervals.
  • Trading: TWAP (Time-Weighted Average Price) orders. Execute large trades over time by placing market orders at regular intervals. TWAPs support durations up to 30 days, configurable intervals (10s–1h), and automatic catch-up if slices are missed. New SDK methods: placeTwap(), cancelTwap(), getTwap(), listTwaps(), getTwapLimits(). API endpoints: POST /api/v1/objects/{id}/exchange/twap, GET .../twap/{operationId}, DELETE .../twap/{operationId}, GET .../twaps. New event types: twap.started, twap.progress, twap.completed, twap.cancelled, twap.failed. Active TWAPs are automatically cancelled on account liquidation.
  • Market data: candle CDN publish schedule reduced to daily. The CDN publish workflow now runs at 00:05 and 00:35 UTC (retry window) instead of every 5 minutes. Missing closed chunks discovered during API reads are repaired asynchronously via a fire-and-forget checker that verifies GCS and enqueues a Temporal repair workflow when needed, with 30-minute cooldown deduplication.

2026-04-01

  • Trading: deleted exchange accounts now disappear cleanly from live trading. The API no longer allows place/cancel order requests or live exchange reads against a soft-deleted exchange object ID. The portal also invalidates its trading caches when an exchange object is deleted, fixing the stale state where a just-deleted account could still show cached cash while reporting zero buying power.
  • Market infrastructure: Bright Data-managed Hyperliquid proxy routing. The Hyperliquid /info transport now supports backend-managed Bright Data datacenter routing. Instead of hand-maintained proxy URLs, the backend can use a Bright Data API key to reconcile a zone, mint managed proxy identities, and feed them into the shared HL router with direct fallback and per-lane telemetry. The current rollout sends all Hyperliquid read traffic through the shared arca_hl_read_only rotating zone first, so the direct Hyperliquid rate limiter is only consumed when the proxy path fails.
  • API: candle fetch cancellations and timeouts now use specific HTTP error types. GET /api/v1/exchange/market/candles/{coin} no longer flattens every downstream market-data failure into 502 EXCHANGE_ERROR. Client-aborted candle requests now return 499 REQUEST_CANCELED, downstream deadline overruns return 504 EXCHANGE_TIMEOUT, and 502 is reserved for genuine upstream failures.
  • SDK: equity-anchored P&L charts. watchPnlChart now accepts an anchor option. Set anchor: 'equity' to shift the P&L chart so the live (rightmost) value equals the current account equity — a portfolio value view. Each point includes a valueUsd field for the chart y-axis. The offset is stable during price movements; historical points never wiggle. Available in both the TypeScript and Swift SDKs.

2026-03-31

  • Portal: default exchange selection now prefers the newest persisted account. When multiple active exchange objects are present, the portal now orders them by createdAt descending instead of raw path order, with a deterministic id tie-breaker. The SDK and API docs now also clarify that listObjects and browseObjects always return authoritative timestamps for persisted objects, while the immediate create response may briefly leave those fields empty until async creation completes.
  • SDK: candle chart range loads now coalesce overlapping requests. Repeated ensureRange calls on the same candle chart stream no longer drop gesture-driven history loads while an earlier fetch is in flight. The TypeScript and Swift SDKs now merge overlapping pending ranges, and failed gaps stay retryable instead of being marked covered. This fixes charts that could appear frozen after rapid pan/zoom interactions.
  • Fix: repeated exchange order cancels now return deterministic API errors. Repeating DELETE /api/v1/objects/{id}/exchange/orders/{orderId} now reuses the original cancel operation instead of surfacing an internal server error from a duplicate implicit operation path. Already-cancelled orders now return 409 ORDER_FAILED instead of a generic upstream/internal-style failure.
  • Exchange state: removed the legacy 5s sim-exchange heartbeat. The sim-exchange no longer emits a periodic equity ticker that forced a full clearinghouse refresh every five seconds for active accounts. Structural exchange updates now come from mutation-driven account events and venue reconciliation. The TypeScript SDK, Swift SDK, and portal were hardened to refetch exchange state when an exchange.updated notification arrives without inline exchangeState, so watch streams and explorer views still converge without relying on the old heartbeat.
  • Fix: aggregation watch flowsSince now accepts standard RFC 3339 timestamps. The platform had started requiring exactly six fractional digits for flowsSince, which caused valid client timestamps like 2026-01-01T00:00:00Z and 2026-01-01T00:00:00.000Z to be rejected when creating aggregation watches over HTTP or WebSocket. The parser now accepts RFC 3339 variants and normalizes them to UTC while responses continue to use the canonical microsecond format.
  • Fix: P&L chart no longer shows a momentary spike on deposits. The aggregation watch response now includes cumInflowsUsd and cumOutflowsUsd fields, computed from the balance ledger. The SDK's PnlChartStream uses these server-provided values instead of tracking flows client-side from operation.updated events, ensuring equity and flow data are always derived from the same source.
  • Fix: P&L flow tracking no longer double-counts or misses flows. The watchAggregation method now accepts an optional flowsSince parameter. watchPnlChart passes the chart's from time so the server computes cumulative flows over the entire chart window — eliminating the gap/overlap between historical and watch-creation time windows.
  • Market data: first-class HIP-3 (builder-deployed perp) coverage. All HIP-3 markets now receive the same trade, candle, and sparkline data coverage as native Hyperliquid perps. Previously, HIP-3 markets could have gaps in sparkline and candle data because their trade feeds were demand-driven. Now all discovered markets — native and HIP-3 — are automatically subscribed to trade and order book streams at startup. The fallback data provider also supplements trades for markets that the primary data source hasn't seen, closing coverage gaps during data source transitions.
  • Perf: Payment link page now loads instantly. The /pay/ page is now a self-contained static HTML file instead of loading the full portal SPA. Previously, opening a payment link downloaded ~1.4MB of JavaScript (React, charts, markdown, OAuth, etc.) before showing a simple payment card. The standalone page is ~16KB with zero external dependencies and loads in under 200ms. All features preserved: branding, dark mode, polling, return strategies.
  • Platform: white-label payment portal branding. Realms can now configure custom branding for the payment portal via PATCH /api/v1/realms/:id/settings with a branding object. Supported fields: displayName, accentColor (hex), logoUrl (HTTPS), hideExpiryTimer, and environmentBannerText. When configured, the payment page renders the builder's branding instead of the default Arca logo and name. The “Powered by Arca” footer always remains.
  • Swift SDK: fix watchCandleChart hang on rapid interval switching. Switching candle intervals quickly (e.g. tapping 1D → 1M → 3M) could cause the SDK to hang indefinitely when loading large candle counts. The cancelled task's CDN chunk fetches were falling back to the REST API instead of exiting, saturating the HTTP client queue and blocking the new request. Cancelled tasks now bail out immediately at every async boundary.
  • SDK: watchPnlChart() and watchEquityChart() now documented. Live chart streams for P&L and equity are now fully documented in the SDK reference with usage examples. watchPnlChart merges historical P&L data with real-time aggregation updates and automatically adjusts for deposits, withdrawals, and transfers that cross the path boundary — no client-side workarounds needed. Both methods are available in TypeScript and Swift SDKs.
  • Platform: atomic event delivery for related events. Operations that produce multiple related events (e.g. operation.updated + balance.updated) now deliver them as a single atomic batch over the WebSocket connection. Previously, these events were published as separate messages with potential inter-message gaps. Affects deposits, withdrawals, transfers, and deletions.
  • SDK: watchEquityChart() cache invalidation on deposit. The equity chart stream now detects when live equity diverges significantly from the last historical data point (e.g. after a deposit) and automatically invalidates the cached history so subsequent chart opens fetch fresh data.
  • Fix: resultingPosition.leverage in fill events. The leverage field in fill.recorded events and listFills results now correctly reflects the per-coin leverage setting used at fill time. Previously, it always returned 1 for new positions because the worker defaulted to the pre-fill platform leverage instead of reading the venue's actual leverage.
  • Market metadata: displayName, isHip3, deployerDisplayName. getMarketMeta now returns three new fields per asset: displayName (human-readable name, e.g. “Crude Oil” for BRENTOIL), isHip3 (whether the asset is a HIP-3 deployer market), and deployerDisplayName (the deployer's full name for HIP-3 assets). Available in TypeScript, Swift, and CLI.
  • Docs: sparklines freshness model. SDK and API docs now document the sparkline freshness guarantee: data is pre-computed every ~5 minutes with all tracked coins always included. For real-time prices, use mid prices or candle subscriptions.
  • Fix: watchMaxOrderSize not updating after position close. The investment slider / max order size stream was not reflecting freed margin after selling a position. A premature exchange.updated event emitted at order placement time poisoned the event bus dedup cache, silently suppressing the real post-fill event that carried the updated exchange state. The TypeScript SDK also lacked watchPath/unwatchPath calls (the Swift SDK already had them), which prevented event delivery on standalone WebSocket connections. Both issues are now fixed.

2026-03-30

  • Market metadata: logoUrl and feeScale added. getMarketMeta now includes logoUrl (CDN URL for the asset logo) and feeScale (HIP-3 fee multiplier, defaults to 1.0 for standard perps). Together with the previously added displayName, isHip3, and deployerDisplayName, the market meta response now provides full display metadata for all assets. displayName and logoUrl are curated and managed via the admin panel. Available in TypeScript, Swift, and CLI.
  • SDK: CDN candle history enabled by default. The candleCdnBaseUrl config now defaults to the production CDN. Historical candle data for longer intervals (1d, 4h, 1h) is now served from the CDN automatically — no configuration needed. Pass null or """" to disable CDN and use the REST API exclusively (e.g. for local development). Affects both TypeScript and Swift SDKs.
  • Fix: watchCandleChart sparse initial data handling. When the initial fast-path fetch (skipBackfill) returns fewer candles than expected, the SDK now detects this as sparse data and retries with a full backfill in the background. Previously, a single cached candle would mark the entire requested range as covered, preventing further loads. The retry now also requires the result to have more candles than the initial load to count as successful, preventing premature stop. Affects both TypeScript and Swift SDKs.
  • Fix: Explorer equity column now populated on initial load. The watch snapshot now includes per-object valuations for multi-object watches, so the Explorer table displays equity values immediately without waiting for a state change event.
  • ⚠️ Breaking: availableToTrade is now a single USD string. ActiveAssetData.availableToTrade changed from [string, string] (buy/sell token pair) to a single string representing raw available margin in USD (equity minus margin in use). This is direction-agnostic — use maxBuyUsd/maxSellUsd for per-side max exposure. Affects both TypeScript and Swift SDKs, the REST API, and client-side derivation.
  • SDK: New Arca.orderBreakdown() static helper. Pure calculator (no network call) that converts between three order input modes: spend (total from balance), notional (position exposure), and tokens (token count). Returns tokens, notionalUsd, marginRequired, estimatedFee, totalSpend, price, and feeRate. Available in both TypeScript and Swift SDKs. Dollar values are estimates at the provided price.
  • SDK: sizeTolerance on placeOrder. New parameter that allows the server to reduce order size by up to the specified percentage if a margin check fails due to price drift. Only reduces, never increases. Works with or without useMax. Recommended: 0.01 (1%) for interactive flows, 0.02 (2%) for retail. maxSizeTolerance is deprecated but still works as an alias.
  • SDK: feeRate is all-in. ActiveAssetData.feeRate includes exchange taker fee (HIP-3 scaled), platform fee, and builder fee (when builderFeeBps is passed to getActiveAssetData). No need to add builder fees separately.
  • SDK: getActiveAssetData now accepts optional leverage. Both TypeScript and Swift SDKs now pass an optional leverage parameter to the server. When provided, the server uses this value instead of the stored per-coin leverage setting (which defaults to 1x if updateLeverage hasn't been called). This ensures getActiveAssetData returns max order sizes consistent with the intended leverage, even if there's a timing gap between updateLeverage and the query. For live max-size streaming, continue using watchMaxOrderSize() which computes client-side with the leverage from options.
  • ⚠️ Breaking: computed field removed from valuations. The computed boolean has been removed from ObjectValuation and PathAggregation in both TypeScript and Swift SDKs, and from the corresponding API responses. The server now guarantees accurate data by using last-known mid prices as a fallback when market data is temporarily unavailable. When a mid price is genuinely unavailable for a position, price-derived fields (unrealizedPnl, valueUsd, markPrice) are omitted from that position rather than sending placeholder zeros. These fields on PositionValue are now optional in both SDKs.
  • ⚠️ Breaking: PathAggregation no longer includes objects. The objects: ObjectValuation[] field has been removed from PathAggregation (TypeScript SDK), PathAggregationResponse (Go API), and the matching Swift PathAggregation model. Portfolio rollups still include totalEquityUsd, breakdown, and in-transit USD fields. For per-object valuations, use watchObject(path) or the new watchObjects(paths) in TypeScript and watchObjects(paths:exchange:) in Swift — these emit merged per-path ObjectValuation updates. For one-shot reads, use getObjectValuation(path). Client-side revalueAggregation now derives live totals from breakdown (spot entries revalued; exchange/perp pass-through; departingUsd / arrivingUsd unchanged as USD pass-through).
  • Real-time P&L on exchange state. watchExchangeState() now subscribes to mid prices and revalues position P&L, equity, and margin summary client-side on every price tick. Previously, exchange state updates arrived only every ~5 seconds via a server round-trip. Position P&L now updates at the same frequency as watchObject() and watchAggregation(). Each update includes a source field ('exchange' for server-pushed structural changes, 'mids' for client-side revaluation). Available in both TypeScript and Swift SDKs. Use revalueExchangeState(state, mids) for manual revaluation outside a stream.
  • Mid price responsiveness: all assets have prices immediately. watchPrices() now guarantees that .prices is fully populated when the await resolves — including illiquid and HIP-3 assets that previously could take seconds to minutes to appear. The server now seeds mid prices from the full asset universe at startup, and the SDK no longer resolves ready() before data arrives.

2026-03-29

  • ⚠️ SDK fix: max order size now uses correct margin metric. watchMaxOrderSize() and deriveActiveAssetDataFromState() previously used availableToWithdraw (withdrawal cushion: equity minus maintenance margin) to compute max order sizes. This inflated buying power because maintenance margin is much smaller than initial margin. The SDK now correctly uses equity - initialMarginUsedto determine available trading margin, matching the backend's order validation logic. If you were computing max order sizes with your own code using availableToWithdraw, switch to equity - initialMarginUsed.
  • Docs: clarified availableToWithdraw semantics. The availableToWithdraw field documentation previously described it as "equity minus initial margin," which was incorrect. It is equity minus maintenance margin — a withdrawal safety metric, not trading capacity. Docs now clearly distinguish between withdrawable amount and available trading margin.

2026-03-26

  • Swift SDK: watchCandleChart(coin:interval:count:) New CandleChartStream that blends historical candles from the REST API with live WebSocket events into a single, always-up-to-date candle array. Handles deduplication, sorting, in-place updates for in-progress candles, and automatic gap recovery on WebSocket reconnection. This replaces the need to manually merge getCandles() and watchCandles() in your app — the stream does it for you.

2026-03-21

  • Active asset data now streams in real time. watchMaxOrderSize() now receives live exchange state updates (orders, fills, leverage, deposits, funding) in addition to price changes. Previously, max order sizes only updated on price ticks.
  • Swift SDK: watchMaxOrderSize(options:) Added watchMaxOrderSize() to the Swift SDK, matching the TypeScript SDK. Returns a MaxOrderSizeWatchStream that recomputes ActiveAssetData on price and exchange state changes.

2026-03-09

  • ⚠️ ActiveAssetData sell sizing fields are now unsigned. maxSellSize, maxSellUsd, and the sell entry in availableToTrade are now returned as positive magnitudes. Direction continues to be selected via order side.

2026-03-08

  • ⚠️ ActiveAssetData: named fields replace arrays. maxTradeSzs and availableToTrade arrays have been replaced with four named fields: maxBuySize, maxSellSize, maxBuyUsd, maxSellUsd. Affects the REST API, TypeScript SDK, and Swift SDK.
  • placeOrder leverage parameter now works. The leverage parameter on placeOrder now automatically sets the account's per-coin leverage before placing the order. Previously, it was accepted but ignored — callers had to call updateLeverage separately.
  • Swift SDK v0.1.0 tag. First semver tag for the Swift SDK. Pin your SPM dependency to from: "0.1.0" instead of branch: "main".
  • Weekly reset status endpoint. GET /api/v1/status/reset returns the next and last weekly reset timestamps. No authentication required.
  • REST docs: transfer examples and balance cross-reference. Added curl example for the transfer endpoint, clarified transfer vs withdrawal semantics, and added a balance endpoint cross-reference in Getting Started.