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.probackreferenced a second wildcard group (<2>) off a pattern that captures only one, so any Android consumer withisMinifyEnabled = truefailed atminifyReleaseWithR8with "Wildcard <2> is invalid". v1.0.1 ships corrected rules following the official kotlinx-serialization template (reconstructing the class name asnetwork.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/watchPnlChartmerge 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 carriesstatus: 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
updatesflows (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;watchCandleChartadditionally emits its initial REST history snapshot throughupdates/onUpdate, so a pure-updatesconsumer 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, andPOST /admin/custody/replay/rebuildare gone, along with the matchingarca adminCLI verbs; boundary balances shown in admin surfaces are now always live on-chain kernel reads (thereplayBalance/replayDriftfields 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 aRecoveryUnwindRequestedevent 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 witharca admin recovery venue-unwind(dry-run by default;--executeis 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, coroutineDeferred-backed operation/order handles (submitted()/settled()/filled()),Flow/StateFlowwatch streams, and a sealedArcaExceptionhierarchy. 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.updatedorwatch_snapshotframe whosecandle/valuationfield arrived as a JSON fragment (a bare number, string, boolean, ornullinstead of an object) could hard-crash the host app with a nativeNSInvalidArgumentExceptionfromJSONSerialization— an Objective-C exception that Swift'stry?cannot catch. The WebSocket handler now validates each value withisValidJSONObjectbefore 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:sizeonsetStopLoss/setTakeProfit(Go:SetPositionTriggerOptions.Size), andstopLossSz/takeProfitSzonsetPositionTpslandopenWithBracket. 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,setPositionTpsldeliberately does not auto-link the two legs when either is sized (pass an explicitocoGroupIdto link them anyway); for the same reason, to “scale out half and keep a stop on the rest” throughopenWithBracket, open the bracket with just the sized take-profit and attach the (unsized) stop separately viasetStopLoss. - New: atomic order brackets via
openWithBracket. Open a position and attach its reduce-only TP/SL triggers as one atomic batch (HyperliquidnormalTpslparity). 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 addopenWithBracket({ entry, takeProfit?, stopLoss? }), returning one order handle per leg from a single call. The existingsetPositionTpsl(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-generatedocoGroupId, and when either fills — even partially — the venue cancels the other leg and recordscancelReason: 'sibling_filled'on it. Previously a partial fill on one leg left the other resting until the position closed. Advanced callers can pass an explicitocoGroupIdtoplaceOrder()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:
cancelReasonon cancelled orders. ACANCELLEDSimOrdernow carries an optionalcancelReason(OrderCancelReason) explaining why it was cancelled —'user_requested','sibling_filled','position_closed','position_flipped','liquidated', or'position_gone'— readable on-demand throughgetOrder/listOrders(including when listingCANCELLEDorders). TheOrderStatusenum is unchanged. - ⚠️ ALO (post-only) orders that would cross are now rejected. An
ALOlimit 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 toGTCorIOC. - ⚠️ 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 +
cancelReasonparity. The same additive surface ships in the Go and Swift SDKs:SetPositionTpsl/setPositionTpslstamps one sharedocoGroupIdon both legs,placeOrder/SetStopLoss/SetTakeProfitaccept an explicitocoGroupId, andSimOrdernow exposesocoGroupIdandcancelReasonon 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
groupingfield is removed. Thena/normalTpsl/positionTpslgrouping enum (and theparentOrderIdfield) are replaced by a single booleansizeToMaxon the order. A sized TP/SL (sizeToMax: false, the default) carries a fixedsizeand 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 whatsetStopLoss/setTakeProfit/setPositionTpsl(CLIposition 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--groupingis 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 exposemodifyOrder()plus anorder.resize(newSize)/order.Resize(...)convenience on the order handle, and the CLI addsarca 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.pathis a per-resize idempotency key. - Fixed: reduce-only closes on isolated-only markets no longer require
leverage. AreduceOnly: trueorder (or TWAP) on an isolated-only market such ashl:1:NVDApreviously failed withIsolated-margin orders on … require a positive leverageunless the caller restatedleverage. A close only shrinks an existing position, so it now skips that gate and is accepted withleverageomitted — matching cross-margin behavior. Whenleverageis 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 positiveleverage. - Fixed: candle history responses now carry the
marketfield. The candle-history endpoint (GET /exchange/market/candles/{market}, used bygetCandlesandwatchCandleChart) was still returning the instrument id under the legacycoinkey, missed in thecoin→marketrename. The response now usesmarketto matchCandlesResponsein every SDK. This unblocks the Swift SDK, whose strict decoder failed withKey 'market' not foundand 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, withfeeTargetssplitting) and the underlying venue's own trading fee apply.estimateFeeandArca.fees.exchangeTransfernow 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
coin→market. Across the REST API, WebSocket payloads, and every SDK, the field that carries a canonical market id is nowmarket(request bodies, query params, and response shapes). The id value also gains a uniform three-segment shape: native Hyperliquid markets are nowhl:0:SYMBOL(e.g.hl:BTC→hl:0:BTC) — the:0:dex segment is always present for native markets. HIP-3 ids such ashl:1:TSLAare unchanged. Pass ids back to the API exactly as returned. - ⚠️ The market-metadata accessor is renamed
asset()→market(id). TypeScript and Swift exposearca.market(id)— an exact canonical-id lookup returning one market orundefined/nil; Go exposesclient.Market(ctx, id)(wasAsset()). The metadata typeSimMetaAssetis renamed toMarket(fields unchanged). - New symbol resolvers:
resolveMarkets(symbol, { exchange?, dex? })andresolveMarketOrThrow(symbol, opts). A human symbol ("BTC") can map to many markets across exchanges and HIP-3 dexes, soresolveMarketsreturns an array — an empty array is an explicit “not found”, never a silent guess. UseresolveMarketOrThrowwhen 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 relatedKNOWN_EXCHANGE_PREFIXES/NonCanonicalCheckexports) is gone. Resolve a human symbol to its canonical id withresolveMarkets(symbol)(orresolveMarketOrThrow) instead of guessing. - ⚠️ The instant fill-preview event is renamed
exchange.fill→fill.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) thenfill.recorded(Phase 2,sequence: 2), correlated bycorrelationId(the order ID). The WebSockettypefield carries the new value. SDK helpers are: TypeScriptEventType.FillPreviewedandws.onFillPreviewed(); GoEventFillPreviewedandOnFillPreviewed(); SwiftEventType.fillPreviewed. The two-phase merge inwatchFills()is unchanged — it correlates by order ID, not the event name. - Exchange objects are now selected by
venue, notexchangeType. When creating a perps exchange object, setmetadata.venueto"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 bebn/bn-sim. The SDKs expose avenueoption onensurePerpsExchange(). The oldexchangeTypefield — 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
applicationFeeTenthsBpsonplaceOrder()/closePosition()/setPositionTpsl()/watchMaxOrderSize(),getActiveAssetData()/getAssetFees(), and theasset-feesquery parameter; anddefaultApplicationFeeTenthsBpsin realm settings. The value is in tenths of a basis point (so40= 4 bps) — naming the unit removes a 10× foot-gun. The earlierbuilderFeeBps/applicationFeeBpsinput names are removed (clean break — no aliases). This is distinct from the Hyperliquid protocol-levelbuilderFeeBpsechoed back on raw venue order responses, which is unchanged. The fee-breakdown fieldcumulativeBuilderFeeis also unchanged. - Realms now have two independent axes: asset and lifecycle. A realm's
assettier (papervslive) 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. Itslifecycletier (permanentvstemporary) 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 bepaper+permanentand a short-lived real-money run can belive+temporary.POST /realmsnow acceptsasset,lifecycle, andbackingfields (CLI:--asset/--lifecycle/--backing), and realm responses include all three. The legacy single-axistype(development/production) is fully backward-compatible — it's now a derived alias ofasset(development↔paper, production↔live), so existing calls and SDK code keep working unchanged. - ⚠️ Position and trade side values are now lowercase, and fill
diris renameddirection. Position side islong/short(wasLONG/SHORT) and trade/order side isbuy/sell(wasBUY/SELL) across the fills and positions API and all SDK types. The fill direction field is renameddir→directionwithsnake_casevaluesopen_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-networkscope.@arcaresearch/sdk→@arca-network/sdkand@arcaresearch/react→@arca-network/react. Re-install under the new name and update your import paths. The Go SDK module path isgithub.com/arcaresearch/arca-go-sdk. The product domain staysarcaos.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 aUSDwallet). A missing or invalid destination is rejected immediately with a clear404/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-pricingcapability — anX-Arca-Client-Capabilitiesheader on REST requests and acapabilitiesfield in the WebSocketauthmessage — and valuation payloads (ObjectValuation,PathAggregation,ExchangeState) accept an optionalpricingModemarker. WhenpricingModeis"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), andclearPositionTpslin the TypeScript, Swift, and Go SDKs, plusarca exchange position set-tpsl/clear-tpslin the CLI. Each looks up the open position, infers the closing side (LONG → SELL, SHORT → BUY), and places a reduce-onlypositionTpslorder withsize 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 andisolatedare auto-filled from the position and market meta. By default a new trigger replaces the existing same-type trigger for the coin (passreplace: false/--replace=falseto stack). For limit triggers or thenormalTpslgrouping, keep usingplaceOrder/order place --trigger. - Correctness: trigger fields are now signed and idempotency-safe. Server-side order placement now validates trigger fields up front (
triggerPx,tpsl, andgroupingmust 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 samepathwith the same trigger parameters returns the prior result instead of being misread as a conflicting input.
2026-05-31
- Fix: ticker
dayNtlVlmno longer inflated. Market ticker responses (GET /market/tickers,getTickers()) now reportdayNtlVlmas the true 24-hour notional volume in USD. Hyperliquid'sdayNtlVlmis 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()andActiveAssetDataexposemarginTiers,maintenanceMarginRate, and the top-of-bookbidPx/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 wheremaxBuyUsdover-stated the executable max for tiered or wide-spread assets (e.g. BTC at high leverage) and was rejected withinsufficient balance. The streamed max remains a best-effort estimate (mids and the spread move between preview and submission) — to place exactly the maximum, useplaceOrder({ useMax: true })or pass a smallsizeToleranceso the server resolves the size atomically. The Swift SDK reaches full parity (itswatchMaxOrderSizenow applies both the margin tiers and the spread), and the Go SDK'sActiveAssetDataexposesbidPx/askPx. In the TypeScript SDK the spread is resolved independently of the margin inputs: pre-supplyingmaintenanceMarginRate+marginTiersno longer suppresses the spread fetch (which had silently fallen back to mid-based sizing). You can now also passbidRatio/askRatiodirectly viaMaxOrderSizeWatchOptions— supply all four to skip thegetActiveAssetDatalookup 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 readschart.candlesonce (instead of subscribing toonUpdate) gets history on a cold market instead of an empty array. ThegetCandles()response cache is now keyed byskipBackfilland never caches an empty result, so a transient empty no longer lingers for the cache TTL. Best practice is unchanged: render fromonUpdate, not a one-time read. - Non-canonical coin-id warnings (TypeScript SDK).
getCandles(),watchCandleChart(), andwatchCandles()now emit a one-timeconsole.warnwhen 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 asisLikelyNonCanonicalCoin(). Reminder: a well-formed but unknown id returns200with 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.ioandadmin-staging.arcaos.io) built againstapi-staging.arcaos.io. The CLI supportsportal_basein profiles for browser auth and step-up, and admin login can target staging witharca admin auth login --env staging. - Market metadata: venue-native symbols.
GET /api/v1/exchange/market/metaand SDKgetMarketMeta()/asset(coin)now includevenueSymbolfor display and venue deep links. Keep usingnamefor Arca API calls;venueSymbolexposes values likeBTCandxyz:TSLA. - Public asset catalog expanded.
GET /api/v1/exchange/market/metaand SDKgetMarketMeta()now include category and curation metadata for each live Hyperliquid market:assetType,categoryLabel,mapped,hasDisplayName,hasLogo, anddescriptionStatus. 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.
updateIsolatedMarginandsetMarginModeare now available in the TypeScript, Swift, and Go SDKs (previously TypeScript only). Positions and leverage settings now expose a singlemarginModefield (crossorisolated) plusisolatedMarginfor isolated positions. Go:client.UpdateIsolatedMargin(ctx, arca.UpdateIsolatedMarginOptions{...})andclient.SetMarginMode(ctx, arca.SetMarginModeOptions{...}); Swift:arca.updateIsolatedMargin(objectId:coin:amount:)andarca.setMarginMode(objectId:coin:marginMode:). - Explicit
marginModeson market metadata;onlyIsolateddeprecated.GET /api/v1/exchange/market/metaand SDKgetMarketMeta()/asset(coin)now returnmarginModes— an explicit list of the margin modes an asset supports (["isolated"]for isolated-only markets,["cross", "isolated"]otherwise). Read it instead of the Hyperliquid-specificonlyIsolatedboolean, 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 fromisHip3. No behavior change —marginModesis derived fromonlyIsolated.
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-widemarginSummary, 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, SDKarca.updateIsolatedMargin, CLIarca exchange isolated-margin) andsetMarginMode(switch an asset between cross and isolated;POST /objects/{id}/exchange/margin-mode, SDKarca.setMarginMode, CLIarca 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-sdkis an idiomatic Go client for building backends on Arca —context.Contexton every call, typed errors (errors.As), generic operation/order handles, and channel/callback watch streams. Install withgo 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(legacydemo/testing/practicewording 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,awaiton operation handles,ref/operationPath/ orderpath,side: 'buy'/orderType: 'MARKET',feeTargets: [{ arcaPath, percentage }]). Operation lifecycle is documented aspending → completed/failed/expiredwithfailureMessage, the412 STEP_UP_REQUIREDstatus 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, andwatchPnlChartaccept{ 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.ttlMscontrols the historical-data cache lifetime (default 5 minutes). Lower it for stronger freshness or set it to0for no expiry (entries then live until LRU eviction). Passcache: falseto 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, SDKcreateApiKey{ realmId }) — a realm-locked key is rejected on any other realm with403 REALM_SCOPE_MISMATCH, so dev, staging, and production credentials can be cleanly isolated. A newpermissionspreset (read,trade, orfull) 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, andwatchAggregationnow 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
ArcaCustodyPoolfor new realms. Realms created while the platform'sFEATURE_V2_CUSTODY_FOR_NEW_REALMSflag is on are provisioned with a per-realm v2 stack: anArcaCustodyKernelproxy (boundary bookkeeping + recovery escape), anArcaCoordinatorproxy (policy + module registry + halt controls), aHyperliquidVenueModuleproxy (per-arca CREATE2 vaults for HL deposits/withdrawals), and aSimExchangeVenueModuleproxy (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 newcustodyVersioncolumn on each realm's custody pool row; existing v1 realms keep working unchanged. Realm responses now include acustodyVersionfield (1or2) so builders can verify routing.
2026-05-22
- CLI:
arca payment-link preview <token>andarca payment-link complete <token>surface deposit instructions browser-free. The pay page'sonChainDepositblock (pool address, boundary, ref, raw amount) is now readable from the CLI for any payment link, with a printed copy of the equivalentcastcommands 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 completeregisters 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 withoutarca auth login. Useful in CI, test scripts, and integration tooling. - Portal: CLI step-up confirmation now returns you to
/cli/confirmafter 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 andPublicRoutenow honor thereturn_toURL 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 deletegains--force-pool-imbalanceand--force-active-operationsfor 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-reasonfor the audit trail and both are refused on production realms regardless of step-up. The platform rule (.cursor/rules/ledger-integrity.mdcrule 5) was also updated:type=developmentis 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_failedevents. A recent commit added a new custody event type (written by the worker'sRecordRegistrationFailureactivity when aRegisterExchangeArcaWorkflowexhausts its retry budget) but missed adding the matching case inapplyReplayEvent. The result was that every subsequentAnnotateDeposit/AnnotateBurn/AnnotateCrossTransferon any realm with a stuck-registration row failed withCUSTODY_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,EnsureRealmRegisteredin platform-go;ensureCustodyContractPoolin worker-go) now short-circuit onpool.WalletID != niland register asReplayPoolinstead ofOnChainPool. Smart-wallet pools (EIP-4337 SimpleAccount viaEnsurePoolpath B) carry on-chain bytecode but do not expose the ArcaCustodyPool selectors, so any future caller that forgets theIsContractBackedPoolgate 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:CLare isolated-margin only on Hyperliquid. The sim-exchange now mirrors that rejection: a cross order on anonlyIsolated=truemarket is refused with a 400, and isolated orders must carry a positiveleverageso the matching engine can identify the bucket.placeOrder()andclosePosition()gain anisolatedfield;closePosition()auto-fills bothisolated(from the market'sonlyIsolatedflag) andleverage(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
leveragefield 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), butbody.leverageflows through to the venue verbatim. - CLI:
arca exchange position closecommand +--isolatedflag. Newarca exchange position closemirrors the SDK'sclosePosition()helper.arca exchange order placegains a--isolatedflag 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 bycreatedAtdescending (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 legacyfoldersstring array follows the same newest-first order aspaths. - Swift SDK: Faster WebSocket reconnection on iOS app foreground. The
WebSocketManagernow observesUIScene.willEnterForegroundNotificationin addition toUIApplication.willEnterForegroundNotificationto 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-boundariesCLI no longer reports successful runs as "unknown error". ThePOST /api/v1/admin/custody/backfill-boundariesroute used to emit a bare JSON body on success ({realmId, fromBlock, monitorsScanned}) and bare plaintext viahttp.Erroron failure, neither of which matched the CLI's{success, data, error}envelope. The CLI's JSON parser silently defaultedsuccesstofalse, 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 inadmin_custody_backfill_test.gonow pin the envelope shape; any future handler that drops back to bare JSON will fail the unmarshal there.- Regression suite:
boundary-conservationnow asserts on per-run drift. Theboundary-conservationprobe in the post-deploy regression suite previously asserted on realm-wide invariants —contractTotal == boundarySum + unattributedand0 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-11cross-boundary-transferteardown 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-deltafails only if the gap grew during the run, andno-new-boundary-drift-deltafails 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}andarca_boundary_replay_realm_conservation_drift_usd{realm}with $10 warn / $100 page thresholds). Operator cleanup of the historical residue (viacli/arca admin custody backfill-boundariesandcleanup-stranded-zones) remains a separate track — seedocuments/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-goandworker-gopod sharing the same Redis sees a single high-water-mark for the next nonce. Before the fix, two pods running concurrentfundAccountcalls (auto-mint) or other admin chain operations could each read the samependingNonceAtfrom the RPC, sign distinct transactions at the same nonce, and one of the two broadcasts would be rejected asreplacement 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 structured503 CHAIN_BROADCAST_CONFLICTwith retry semantics instead of a generic500when 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). NewREDIS_URLdependency for chain providers; the manager keys per-chain so the same admin EOA on two chains has independent counters. ensureDeletedretries are now idempotent. A retriedarca.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 toactive) used to collide onidx_operations_realm_path_uniqand return a generic500 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 differentsweepToPathorliquidatePositionsflag) surface as a structured409 IDEMPOTENCY_VIOLATION— clients can either honour the existing operation or pass a uniqueoperationPathfor 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 existingensureDeletedpolling loop simply observes the prior operation's terminal state.- Cross-boundary chain-tx direction fix in the regression probe. The
cross-boundary-transferregression probe now looks fordirection='internal'on the recordedchain_transactionsrow 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 newcreatedAton eachpathsentry (isolation zones use the zone's owncreated_at; plain folders useMIN(child.createdAt)over the returned descendants) and a new inlinelabelsmap. Folder labels live in a dedicated, sparsefolder_labelstable keyed by(realmId, path)and are read/written through two new endpoints:GET /api/v1/folders/labelsandPUT /api/v1/folders/labels(full overwrite — pass{}to clear). The SDK addsarca.getFolderLabels(path)andarca.updateFolderLabels({ path, labels }); theBrowsePathEntrytype gains optionalcreatedAtandlabelsfields. Permission wise, writes require the newarca:UpdateFolderLabelsaction (rolled into thearca:Lifecycleandarca:Writealiases); reads reusearca:ReadObjecton 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-streamerwhose 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 whenhl-streameris publishing on time but feeding lagged data. Five new metrics underarca_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 the
412 STEP_UP_REQUIREDresponse, 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-archiveonarca realm deletefor production realms. Three new endpoints under/api/v1/auth/step-up-requestsimplement the create / read / approve flow. Seedocuments/runbooks/step-up-auth.mdfor operational details. - ⚠️ Production-realm archival now refuses unsafe deletes.
DELETE /api/v1/realms/{id}on a production realm now refuses unless the request body setsforceProductionArchive: true, the caller carries a step-up token bound toadmin: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 bothDEVELOPMENT=trueandFEATURE_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 exposedev/resetto 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
watchPnlChartwas missing the new recovery wiring (onResume,onAuthenticated, wall-clock boundary timer, multi-bucket gap detection, live-tail sliding window) — onlywatchEquityChartandwatchCandleCharthad received it, so a backgrounded iOS chart silently failed to refetch on foreground. TypeScriptWebSocketManager.connect()did not reinstall thevisibilitychangelistener thatdisconnect()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, breakingonResumefor 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-levelMaximumAttempts: 5retry 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 withMaximumAttempts: 0(unlimited retries), so transient chain-RPC blips, briefeth_callstale 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 theabsorbUnattributedadmin runbook. Discovered after the 2026-05-03 regression run hitI8-SurplusMagnitudewith $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 originaltowas 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 viaUIApplication.willEnterForegroundNotificationandNSApplication.willBecomeActiveNotificationobservers; web apps viadocument.visibilitychange. No code changes required for builders consumingwatchEquityChart/watchPnlChart. - Recovery-hatch boundary state is now mirrored end-to-end. When an isolation boundary's recovery-key holder calls
lockForRecovery,unlockBoundary,withdraw, orassignRecoveryKeyon the on-chain custody pool, the platform now (1) records the chain observation incustody_contract_events, (2) transitions the boundary's status in a newcustody_boundariescache (active→soft_frozen→hard_frozen), (3) refuses subsequent value-moving operations on the boundary at the API entry-point with409 BOUNDARY_FROZEN, (4) onWithdrawnsweeps every user wallet in the boundary into a system-owned recovery arca at/.recovery/<boundary-suffix>via a newRecoveryFreezeWorkflow, and (5) mirrors any subsequentWithdrawnor unexpectedDepositedagainst that recovery arca so the platform's ledger stays consistent with the on-chain pool balance. Builders see the new state viaArcaObject.boundary: present only when the boundary is frozen, withstatus, 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 tohard_frozenif aWithdrawnis observed. Both the TypeScript and Swift SDKs surfaceArcaObject.boundaryas an optionalBoundarySnapshot. A new super-admin endpointGET /api/v1/admin/custody/boundary?realmId&boundaryIdreturns the boundary's current state row plus its fullcustody_contract_eventstimeline for incident review, andPOST /api/v1/admin/custody/backfill-boundariesreplays 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/realmspreviously required a custody pool only forproductionrealms;developmentrealms silently degraded into a half-broken state when the dev chain wasn't reachable, and the regression suite'sgetCustodyStatusprobe 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) orEnsurePoolfails,CreateRealmreturns503 SERVICE_UNAVAILABLEwith 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 (memory035c82d1): 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
operatorWithdrawon the production custody contract was previously silently skipping its correspondingAnnotateWithdrawalcall, leaving the in-memory replay model's per-boundarybalanceover-reporting by exactly the withdrawn amount on every withdrawal. Discovered by the 2026-05-02 tiptoe-prod-realm runbook recovery drill, which expectedreplay = $50after the recovery key drained the boundary but observedreplay = $100instead. Fixed in the worker activitysendFromPool: a newannotateOperatorWithdrawalhelper mirrors the on-chain decrement into the replay model on the success path of everyCustodyOperatorWithdraw. 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 diagnosticreplayBalance/replayDriftvalues surfaced in the admin boundary balance sheet. Recovery-key withdrawals are still (intentionally) unannotated, so post-fix the drill producesdrift = +amountexactly 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/objectsdrops thedenominationbody field; denominated and exchange objects are stampedUSDserver-side. The TypeScript SDK (arca.ensureDenominatedArca,arca.ensureArca), Swift SDK (arca.ensureDenominatedArca,arca.ensureArca), CLI (arca object ensure --denominationflag removed), and Portal "Create Object" modal (denomination dropdown removed) follow suit. The agentpropose_plantool no longer describes adenominationparameter forcreateObject. Wire clients still sending the field will have it silently ignored. Read paths are unchanged — every object, balance, fee, and operation response continues to exposedenominationfor 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 samedenominatedtype. - Withdrawal payment links now accept a destination address at creation time.
POST /api/v1/payment-linksfortype: withdrawalaccepts a new optionaldestinationAddressfield (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 legacy0x000…dEaDfallback 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). Thearca payment-link createCLI gains a matching--destinationflag. 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_getLogsfailures atErrorlevel (previouslyDebug, 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 returnsHTTP 403foreth_getLogs. The cold-start block lookback (depositScanLookback) was raised from100to5000blocks (≈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 testTestDepositScanLookback_CoversFastChainOutagepins the floor at 1000 blocks. Production runbook now lists theeth_getLogsprecheck 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 optionalonChainDeposit.explorerTxUrlTemplatefield — a URL with a literal{hash}token to substitute. Returned ashttps://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 newonChainDepositobject withchainId,poolAddress,tokenAddress,boundary(bytes32),amountRaw(USDC 1e6 units), andref(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 → optionallyusdc.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 matchedDepositIntent. 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 bothplatform-goandworker-gowhenFEATURE_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_URLis 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 bothplatform-goandworker-gowhenFEATURE_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 ourArcaCustodyPoolimplementation 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 USDCTransferevents. 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/transferbetween two paths that resolve to different isolation boundaries automatically dispatches the custody contract'scrossTransferbetween the source and target boundaries (recorded as achain_transactionsrow with purposetransfer.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 reachescompleted(~requiredConfirmations× block time on top of the journal commit); same-zone transfers continue to settle immediately. Transfers from a source boundary with a non-zerotimeLockare rejected upfront with a newVALIDATION_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 throughPOST /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'slistOperations/exportOperationEvidencenow acceptarcaIdfor 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:cancelTwapon a TWAP that has alreadycompleted,cancelled, orfailednow returns200 OKwith the existing record (previously400 VALIDATION_ERROR).- The
Twapresponse gainsexpectedSliceCount(the planned total, derived fromdurationMinutes × 60 / intervalSeconds; min 1) so a freshly placed TWAP no longer renders as0/0 sliceswhile the scheduler materializes the schedule, and1h × 1h intervalreports1 sliceinstead of zero.targetPrice(the mid at create time, used for "vs target" deltas from slice 1) andfailureReason(descriptive last-slice error on terminalstatus === 'failed') are also new. - New
GET /api/v1/twap/limitsreturns 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, andtwap.failedare enriched server-side with the fullTwappayload, 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
slippageBpsis now honored at the matching engine on every slice: a per-side price ceiling ofmid × (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 IAMPolicyStatementconstruction with three presets (read,trade,full) and an optionalforUserPathresource scope.
fill.recordedevents now include a top-levelfeefield. The realtimefill.recordedWebSocket event payload was missing the top-levelfeefield thatGET /objects/{id}/exchange/fillsreturns, forcing realtime consumers to compose it fromexchangeFeethemselves 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:feeis populated with the same exchange-fee component that RESTFillResponse.feereturns. The dedicatedexchangeFee/platformFee/builderFeebreakdown fields are unchanged. No SDK type changes are required — TSFill.feeand SwiftFill.feewere already declared optional.- SDK:
watchFillstwo-phase semantics documented.FillWatchStreamdocstrings (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 authoritativefill.recordedcounterparts viacorrelationId) for activity feeds, and theupdatesstream /onUpdatecallback only when you need the preview→recorded transition itself (e.g. a "pending → confirmed" animation). Deduping byFill.idalone does not work because the preview'sidis the venue's fill id while the recorded'sidis the platform's position-ledger row — always merge bycorrelationId/orderIdif 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, sequentialN) and the operation completes withoutcome.status = "recovered". The recovery arca is an ordinary denominated arca — builders can sweep its full balance to the intended destination via a normaltransfer(ordelete-with-sweep), and the arca's existence itself acts as the "unprocessed" flag. Operation metadata underoutcomeincludes the original target path, recovery path, and amount so the source of the funds is auditable. No SDK changes are required;deposit.recoveredis 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
15mand30mrungs. The aggregation resolution ladder now includes intermediate rungs between5mand1h:1m/5m/15m/30m/1h/4h/1d. The biggest user-visible impact is the 1W chart: at the default 1000-point budget it now picks15m(672 buckets) instead of1h(168 buckets) — roughly 4× denser intra-week detail. Compact-budget (500-pt) 1W charts pick30m(336 buckets). All other periods (1D / 1M / 3M / 12M) are unchanged.GET /historyandGET /pnl/historyresponse 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_ERRORwhensize × pricefalls below $10. Reduce-only orders andpositionTpsltrigger 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 tofailed~2 minutes later. New SDK helperarca.getOrderLimits()returns{ minOrderNotionalUsd: 10 }for client-side pre-validation. The existingarca.getTwapLimits()minSliceNotionalUsdfield is unchanged in shape and is now an honest reflection of the venue-enforced floor. - SDK:
placeOrder.sizeandplaceTwap.totalSizeare 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/fillsnow strictly returns actual exchange fills. Internally, the underlyingarca_position_ledgertable 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 intoFillListResponsealongside real fills, where they rendered as empty "dash@dash, fee $0" entries in client UIs (Svall iOS, the prototype, the SDK'sarca.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
SimPositionexposescumulativeFeealongsidecumulativeFunding. 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 onSimPosition(TS and Swift):cumulativeFee— total fees paid over the position's current open lot, equal by construction tocumulativeExchangeFee + cumulativePlatformFee + cumulativeBuilderFee.cumulativeExchangeFee/cumulativePlatformFee/cumulativeBuilderFee— per-bucket breakdown matching the buckets onFill.
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 insim-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:
cumulativeFundingnow reaches the SDK. The TSSimPositiontype was missing thecumulativeFundingfield 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/pnlresponses include anendingMidPriceobject and/objects/aggregateresponses include amidPriceobject. Both carrymarketDataAsOf(the timestamp of the freshest mid the engine consulted),ageSeconds,source("snapshot"|"cache"|"none"),staleAfterSeconds, and a derivedstaleflag. 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=trueon sim-exchange enables enforcement and any unsigned/invalid request is rejected withSIGNATURE_MISSING,SIGNATURE_INVALID,WALLET_NOT_BOUND,NONCE_REUSED,TIMESTAMP_SKEW, orOPERATOR_NOT_ALLOWED.
2026-04-28
- ⚠️ Realms: realm types are now
developmentorproduction. The legacydemo,testing, andstagingrealm types have been removed from the API, SDKs, CLI, and portal. Existing non-production realms are migrated todevelopment; new create requests must usedevelopmentorproduction. - History charts: V1 fallback retired. Equity and P&L history now serve both legacy-shaped
prefixrequests and currenttarget/kindrequests 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.
OperationTypenow includesvenue_closeand preserves unknown future operation type strings asunknown(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.updatedevents now expose a single polishedmarketDataAsOftimestamp 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
4hrung, so expanded charts target5mfor 1D,1hfor 1M,4hfor 3M, and1dfor 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-resolution4hbackfill. - SDK: V2 equity and P&L chart streams recover from chart snapshot pushes.
watchEquityChartandwatchPnlChartnow preserve V2 history metadata (status, resolution details, and server timing) and subscribe to the newchart.snapshot.updatedWebSocket 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:
orderBreakdownliquidation estimate is now cross-margin.Arca.orderBreakdown'sestimatedLiquidationPricepreviously 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, thenliq = 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,OrderBreakdownOptionsnow requires anaccountContextfield (account equity, maintenance margin from positions in other coins, and any existing same-coin position) whenevermaintenanceMarginRateis provided. This is a source-breaking change for existing callers usingmaintenanceMarginRate; callers that don't need a liquidation estimate can omit both fields. In Swift, use the new initializer overload that accepts bothmaintenanceMarginRateandaccountContext.
2026-04-16
- SDK:
orderBreakdownestimates liquidation price. TheArca.orderBreakdownfunction now returns anestimatedLiquidationPricewhen the new optionalmaintenanceMarginRateparameter is provided. This isolated estimate assumes the order is the only open position. To support this,ActiveAssetData(returned bygetActiveAssetDataandwatchMaxOrderSize) now includes amaintenanceMarginRatefield, 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:
watchMaxOrderSizeresolves dynamicmaintenanceMarginRate.ActiveAssetData.maintenanceMarginRateemitted bywatchMaxOrderSizepreviously defaulted to"0.03"on every recompute, which fed an incorrect value intoorderBreakdown'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 viagetActiveAssetDataand thread it through every recomputation. Callers can also passmaintenanceMarginRatedirectly viaMaxOrderSizeWatchOptionsto 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 abufferingNewest(1)policy on their underlyingAsyncStream. If a consumer is slower than the producer (for example, hopping toMainActoron 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 idlewatchPrices()consumer with 392 coins).watchPricesadditionally skips the yield entirely when the incoming mids snapshot contains no actual change. TheSendableBox.onChangepush-based API remains available and is unaffected. - Market metadata:
logoSourcesarray.getMarketMetaandasset(coin)now include alogoSourcesarray with pre-rendered raster variants (128, 256, 512 width) in WebP and PNG formats. The existinglogoUrlstring 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:
watchPricesergonomics.arca.watchPrices()now accepts an options object:coinsnarrows the subscription to specific canonical market IDs (multiple narrow subscribers share one WebSocket subscription via set-union), andreadyTimeoutMsbounds the initial snapshot wait (rejects with the newWatchStreamReadyTimeoutError). The returnedPriceWatchStreamexposesget(coin)andgetNumber(coin)convenience accessors..pricesnow returns a fresh object reference on every tick, so reference-equality checks (React state, Zustand selectors) correctly detect changes. The legacy positionalwatchPrices(exchange)call still works.WatchStream.ready()also accepts atimeoutMsargument so app startup can degrade gracefully when the backend is unreachable. The React hookuseArcaPricesnow 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/signupand/api/v1/auth/signinendpoints have been removed. Use/api/v1/auth/googlefor all authentication. The CLI no longer supports the--no-browserflag, as authentication now strictly requires a browser for the Google OAuth consent screen. - Swift SDK: diagnostics & structured logging.
Arca.initnow acceptslogLevel(defaults to.warning) and an optionallogHandler. The SDK emits records to Apple's unified logging system (subsystemio.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 bytry?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 threeprint("[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 thevalueUsdfield using the server's authoritative historicalequityUsdfor 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 theSimMetaAssetfor 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. UsepreloadMarketMeta()to warm the cache at startup, orrefreshMarketMeta()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/transferAPI endpoint, andarca transferCLI command now accept an optionalfeeOverrideparameter. 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_tradesWebSocket actions deliver a live trade tape per coin. Trades arrive as batchedtrades.batchmessages. SDK addswatchTrades(coins)which returns aTradeWatchStream, and lower-levelacquireTrades/releaseTrades/onTradeExecutedon 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-deflatewith 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: truewithsubscribe_candles, and the server coalesces allcandle.updatedevents in a tick into a singlecandles.updatedmessage. This reduces WebSocket frame count from N to 1 per batch.candle.closedevents are always sent individually. The SDK handles this transparently —onUpdatecallbacks still fire per candle. Raw WebSocket users can opt in by addingbatch: trueto thesubscribe_candlesmessage; 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 anopenPositionCostsobject 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
platformFeeonSimFill,cumulativeFundingonSimPosition,getAssetFees(objectId:builderFeeBps:)for per-asset fee rates, andenableAutoTracking(operations:balances:exchange:)for optimistic UI updates. Also addedAssetFeeEntrytype. - Swift SDK: PaymentLinkView now uses WKWebView.
PaymentLinkView/.paymentLinkSheet()now usesWKWebViewinstead ofSFSafariViewController, enablingwindow.close()support forreturnStrategy: "close". - TypeScript SDK: aggregation revaluation includes perp entries.
revalueAggregation()now revaluesperpbreakdown entries using fresh mid prices, keeping aggregation equity in sync with per-object valuations between server pushes. - Platform: object creation response includes timestamps.
createObjectresponses now populatecreatedAtandupdatedAtimmediately, enabling deterministic ordering of newly created objects without waiting for background workflow completion. - Docs: clarified
fundAccountbehavior by realm type and documented thatwatchCandleChartcountis best-effort (sets time window, actual count depends on asset history). - Platform: realm listing now returns realms across all org memberships.
GET /api/v1/realmsnow 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:
nextFundingTimenow populated on market tickers.MarketTicker.nextFundingTimeis now returned as a Unix-ms timestamp from the/api/v1/exchange/market/tickersendpoint and SDKgetMarketTickers()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.getPnlHistorynow returns aneffectiveFromfield, andwatchPnlChartuses it to alignflowsSincecorrectly. 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 verifiesX-Arca-Key-Id,X-Arca-Signature, andX-Arca-Timestampheaders 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
venueAccountIdmetadata 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, andcandlepayload, and now may also includepublishedAt,platformReceivedAt, andwsSentAtfor 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. Usescrypto.getRandomValuesfor 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 new
POST /api/v1/isolation-zonesendpoint 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 newGET /api/v1/operations/exportendpoint 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 includeswithdrawnAt. 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 optionaldisplayNamefield. Active objects usually leave it unset, while deleted objects receive a tombstone label likewallet.deleted.YYYY.MM.DD.SSSSSso 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 optionalboundaryIdfilter), andPOST /custody/recovery-key. The/custody/statusresponse now includescontractAddress,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). Legacyarca_balance_ledgerwrites 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 withanchor: .equity). The fix uses server-authoritative cumulative flow values via the newflowsSinceparameter onwatchAggregation. - Swift SDK:
watchAggregationnow acceptsflowsSince. Pass an ISO 8601 timestamp to receive cumulative inflow/outflow values computed from that point forward.PathAggregationnow includescumInflowsUsdandcumOutflowsUsdfields. - 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:
intervalandpointsparameters 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. UsegetCandlesfor 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
/infotransport 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 sharedarca_hl_read_onlyrotating 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 into502 EXCHANGE_ERROR. Client-aborted candle requests now return499 REQUEST_CANCELED, downstream deadline overruns return504 EXCHANGE_TIMEOUT, and502is reserved for genuine upstream failures. - SDK: equity-anchored P&L charts.
watchPnlChartnow accepts ananchoroption. Setanchor: 'equity'to shift the P&L chart so the live (rightmost) value equals the current account equity — a portfolio value view. Each point includes avalueUsdfield 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
createdAtdescending instead of raw path order, with a deterministicidtie-breaker. The SDK and API docs now also clarify thatlistObjectsandbrowseObjectsalways 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
ensureRangecalls 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 return409 ORDER_FAILEDinstead 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.updatednotification arrives without inlineexchangeState, so watch streams and explorer views still converge without relying on the old heartbeat. - Fix: aggregation watch
flowsSincenow accepts standard RFC 3339 timestamps. The platform had started requiring exactly six fractional digits forflowsSince, which caused valid client timestamps like2026-01-01T00:00:00Zand2026-01-01T00:00:00.000Zto 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
cumInflowsUsdandcumOutflowsUsdfields, computed from the balance ledger. The SDK'sPnlChartStreamuses these server-provided values instead of tracking flows client-side fromoperation.updatedevents, 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
watchAggregationmethod now accepts an optionalflowsSinceparameter.watchPnlChartpasses the chart'sfromtime 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/settingswith abrandingobject. Supported fields:displayName,accentColor(hex),logoUrl(HTTPS),hideExpiryTimer, andenvironmentBannerText. 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
watchCandleCharthang 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()andwatchEquityChart()now documented. Live chart streams for P&L and equity are now fully documented in the SDK reference with usage examples.watchPnlChartmerges 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.leveragein fill events. Theleveragefield infill.recordedevents andlistFillsresults now correctly reflects the per-coin leverage setting used at fill time. Previously, it always returned1for 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.getMarketMetanow 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), anddeployerDisplayName(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:
watchMaxOrderSizenot updating after position close. The investment slider / max order size stream was not reflecting freed margin after selling a position. A prematureexchange.updatedevent 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 lackedwatchPath/unwatchPathcalls (the Swift SDK already had them), which prevented event delivery on standalone WebSocket connections. Both issues are now fixed.
2026-03-30
- Market metadata:
logoUrlandfeeScaleadded.getMarketMetanow includeslogoUrl(CDN URL for the asset logo) andfeeScale(HIP-3 fee multiplier, defaults to 1.0 for standard perps). Together with the previously addeddisplayName,isHip3, anddeployerDisplayName, the market meta response now provides full display metadata for all assets.displayNameandlogoUrlare curated and managed via the admin panel. Available in TypeScript, Swift, and CLI. - SDK: CDN candle history enabled by default. The
candleCdnBaseUrlconfig 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. Passnullor""""to disable CDN and use the REST API exclusively (e.g. for local development). Affects both TypeScript and Swift SDKs. - Fix:
watchCandleChartsparse 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:
availableToTradeis now a single USD string.ActiveAssetData.availableToTradechanged from[string, string](buy/sell token pair) to a singlestringrepresenting raw available margin in USD (equity minus margin in use). This is direction-agnostic — usemaxBuyUsd/maxSellUsdfor 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), andtokens(token count). Returnstokens,notionalUsd,marginRequired,estimatedFee,totalSpend,price, andfeeRate. Available in both TypeScript and Swift SDKs. Dollar values are estimates at the provided price. - SDK:
sizeToleranceonplaceOrder. 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 withoutuseMax. Recommended: 0.01 (1%) for interactive flows, 0.02 (2%) for retail.maxSizeToleranceis deprecated but still works as an alias. - SDK:
feeRateis all-in.ActiveAssetData.feeRateincludes exchange taker fee (HIP-3 scaled), platform fee, and builder fee (whenbuilderFeeBpsis passed togetActiveAssetData). No need to add builder fees separately. - SDK:
getActiveAssetDatanow accepts optionalleverage. Both TypeScript and Swift SDKs now pass an optionalleverageparameter to the server. When provided, the server uses this value instead of the stored per-coin leverage setting (which defaults to 1x ifupdateLeveragehasn't been called). This ensuresgetActiveAssetDatareturns max order sizes consistent with the intended leverage, even if there's a timing gap betweenupdateLeverageand the query. For live max-size streaming, continue usingwatchMaxOrderSize()which computes client-side with the leverage from options. - ⚠️ Breaking:
computedfield removed from valuations. Thecomputedboolean has been removed fromObjectValuationandPathAggregationin 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 onPositionValueare now optional in both SDKs. - ⚠️ Breaking:
PathAggregationno longer includesobjects. Theobjects: ObjectValuation[]field has been removed fromPathAggregation(TypeScript SDK),PathAggregationResponse(Go API), and the matching SwiftPathAggregationmodel. Portfolio rollups still includetotalEquityUsd,breakdown, and in-transit USD fields. For per-object valuations, usewatchObject(path)or the newwatchObjects(paths)in TypeScript andwatchObjects(paths:exchange:)in Swift — these emit merged per-pathObjectValuationupdates. For one-shot reads, usegetObjectValuation(path). Client-siderevalueAggregationnow derives live totals frombreakdown(spot entries revalued; exchange/perp pass-through;departingUsd/arrivingUsdunchanged 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 aswatchObject()andwatchAggregation(). Each update includes asourcefield ('exchange'for server-pushed structural changes,'mids'for client-side revaluation). Available in both TypeScript and Swift SDKs. UserevalueExchangeState(state, mids)for manual revaluation outside a stream. - Mid price responsiveness: all assets have prices immediately.
watchPrices()now guarantees that.pricesis fully populated when theawaitresolves — 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 resolvesready()before data arrives.
2026-03-29
- ⚠️ SDK fix: max order size now uses correct margin metric.
watchMaxOrderSize()andderiveActiveAssetDataFromState()previously usedavailableToWithdraw(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 usesequity - initialMarginUsedto determine available trading margin, matching the backend's order validation logic. If you were computing max order sizes with your own code usingavailableToWithdraw, switch toequity - initialMarginUsed. - Docs: clarified availableToWithdraw semantics. The
availableToWithdrawfield 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
CandleChartStreamthat 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 mergegetCandles()andwatchCandles()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 aMaxOrderSizeWatchStreamthat recomputesActiveAssetDataon price and exchange state changes.
2026-03-09
- ⚠️ ActiveAssetData sell sizing fields are now unsigned.
maxSellSize,maxSellUsd, and the sell entry inavailableToTradeare now returned as positive magnitudes. Direction continues to be selected via orderside.
2026-03-08
- ⚠️ ActiveAssetData: named fields replace arrays.
maxTradeSzsandavailableToTradearrays 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
leverageparameter onplaceOrdernow automatically sets the account's per-coin leverage before placing the order. Previously, it was accepted but ignored — callers had to callupdateLeverageseparately. - Swift SDK v0.1.0 tag. First semver tag for the Swift SDK. Pin your SPM dependency to
from: "0.1.0"instead ofbranch: "main". - Weekly reset status endpoint.
GET /api/v1/status/resetreturns 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.