Skip to main content
Testnet-Only. Connect to wss://test-dex-api.cloud.aultblockchain.xyz/ws/perp. This endpoint is not available on Mainnet.
/ws/perp is a separate connection from Spot /ws, and a Spot connection does not receive Perpetual frames. The command protocol, TTL, limits, heartbeats, and reconnect rules follow WebSocket Overview. Use the exact symbol returned by Perpetual Market Data as coin, not a hex market ID or oracle feed ID. User subscriptions take the owner wallet address, while rows identify derived accountId values.

Supported channels

spotState and userTwapHistory are rejected on this connection as not applicable for perpetuals. There are no Lending, Margin Trading, Vault, or Oracle channels.
Data frames have {channel, data}. Only userEvents uses a different channel name, user. openOrders has no continuation token or truncation flag. For a complete owner inventory, follow every page of REST orders with status=open. Omit account_id when collecting TWAP parents because their account is chosen per slice.

Market payloads

l2Book, bbo, trades, candle, allMids, and chainBlock use the common Spot payload shapes, in the Perpetual namespace. Book sz is in raw base units, while trade sz is in normalized human base units. There is no sequence shared with REST, and chainBlock does not mark a point at which all channels are in sync. activeAssetCtx is {coin, ctx, time}, where time is in milliseconds. Decimal values are strings. ctx is partial, so a field can be absent until its value is available: Frames are sent when the context changes, so a quiet market can go without one. A frame does not mean that markPx is fresh or that the chain will allow a withdrawal. For the effective index and timestamps, use the Perpetual ticker and native price queries.

Account-state payloads

perpState.data contains user, accounts, marginConfigs, positions, and orderExposures. The initial frame carries isSnapshot: true but no block height or time. Live frames are deltas with numeric block height and millisecond time. Merge accounts by accountId, positions by (accountId, marketId), and owner-scoped configurations and exposures by marketId. A position or exposure with deleted: true removes the item. Monetary values are decimal strings in human units; leverage, height, and time are numbers. clearinghouseState.data contains {user, accounts, positions, marginConfigs} using the REST response fields. Each frame replaces the previous state. It refreshes after account-state changes, not on every mark price change, so refresh REST before relying on mark-dependent values. Null mark-dependent fields remain null.

Fills and reconciliation

Use the userFills snapshot for recent context, not as an accounting baseline. Use paginated REST fills for historical quantity and notional reconciliation. REST quantity is already a per-event amount, so do not difference adjacent rows. Count each unique fill ID once, and do not add overlapping WebSocket snapshots to REST totals. Perpetual userFills.fee uses the indexed collateral fee when available, with feeToken naming the asset. Older records without an indexed fee use an estimate. Use oidHex, not the lossy numeric oid, to join to full REST IDs.

Funding and event history

userFundings.data is {isSnapshot, user, fundings}. Each item is {id, timestamp, accountId, marketId, amount}. amount is in human collateral units, positive when received and negative when paid, matching REST funding payments. Pending funding on a REST position uses the opposite sign. userEvents adds these arrays on the user channel when present: positionUpdates.funding uses the chain sign, positive when paid, unlike userFundings.amount. Amounts and quantities are human-unit decimal strings, prices are quote per base, and timestamps are milliseconds. Streams have no guaranteed replay or transactional ordering across channels. After a reconnect, resubscribe and reconcile missed history with the REST ledger, funding payments, and closeouts.