Skip to main content

WebSocket

General Information

  • All data is returned in JSON format
  • All timestamps are in milliseconds (Unix epoch time)
  • A single connection can subscribe to multiple streams
  • The WebSocket server sends a ping frame every 30 seconds
  • If a pong frame is not received within 60 seconds, the connection will be closed
  • Maximum connection lifetime is 24 hours

Message Format

Request Format

All requests to the WebSocket server follow this format:

{
"method": "subscribe",
"params": {
"channels": ["trade@1_2", "depth@1_2"]
},
"id": 1
}

Request Fields:

FieldTypeDescription
methodSTRINGAction to perform: "subscribe" or "unsubscribe"
paramsOBJECTParameters for the method
channelsARRAYArray of channel names to subscribe/unsubscribe
idINTRequest ID for correlation with responses

Response Format

Server responses for subscription requests:

{
"result": "ok",
"id": 1
}

Stream Data Format

Stream updates are sent in this format:

{
"method": "subscription",
"params": {
"channel": "trade@1_2",
"result": {
// Stream-specific data
}
}
}

Available Streams

Trade Streams

Real-time trade executions for a specific market.

Channel Name: trade@{marketId}

Example: trade@1_2

Subscribe:

{
"method": "subscribe",
"params": {
"channels": ["trade@1_2"]
},
"id": 1
}

Stream Data:

{
"method": "subscription",
"params": {
"channel": "trade@1_2",
"result": {
"tradeId": "405100010000",
"marketId": "1_2",
"price": "2.3",
"quantity": "0.7",
"buyOrderId": "0xdb9d5d086b4755fb5e49819fef783f80fb98f893",
"sellOrderId": "0x766be3ed6e3296708885405c7766c6cc880cfafa",
"createdAt": 1758005316000,
"isBuyerMaker": false
}
}
}

Diff. Depth Stream

Order book price and quantity depth updates used to locally manage an order book.

Stream Name: depth@{marketId}

Update Speed: Real-time

Subscribe:

{
"method": "subscribe",
"params": {
"channels": ["depth@1_2"]
},
"id": 1
}

Payload:

{
"method": "subscription",
"params": {
"channel": "depth@1_2",
"result": {
"marketId": "1_2", // Market ID
"bids": [ // Bids to be updated
[
"2.30", // Price level to be updated
"10.5" // Quantity
]
],
"asks": [ // Asks to be updated
[
"2.31", // Price level to be updated
"15.0" // Quantity
]
],
"firstId": 42920, // First update ID in event
"finalId": 42921, // Final update ID in event
"time": 1758096768006 // Event time
}
}
}

How to manage a local order book correctly:

  1. Open a stream to depth@{marketId}.
  2. Buffer the events you receive from the stream.
  3. Get a depth snapshot from GET /api/v1/market/depth?marketId={marketId}.
  4. Drop any event where finalId <= lastUpdatedId in the snapshot.
  5. The first processed event should have firstId <= lastUpdatedId + 1 AND finalId >= lastUpdatedId + 1.
  6. While listening to the stream, each new event's firstId should equal the previous event's finalId + 1. If not, re-sync from step 3.
  7. For each event, apply the price level updates:
    • The price level quantity is the absolute quantity at that price (not a delta).
    • If the quantity is 0, remove the price level.
  8. Bids are sorted by price in descending order (best bid first).
  9. Asks are sorted by price in ascending order (best ask first).
note

Receiving an event that removes a price level that is not in your local order book can happen and is normal.

Partial Book Depth Streams

Top 100 bids and asks, pushed every 500ms or on order book change.

Stream Name: depthSnapshot@{marketId}

Update Speed: 500ms or Real-time

Subscribe:

{
"method": "subscribe",
"params": {
"channels": ["depthSnapshot@1_2"]
},
"id": 1
}

Payload:

{
"method": "subscription",
"params": {
"channel": "depthSnapshot@1_2",
"result": {
"marketId": "1_2", // Market ID
"lastUpdateId": 42921, // Last update ID
"bids": [ // Bids
[
"2.30", // Price level
"100.5" // Quantity
]
],
"asks": [ // Asks
[
"2.31", // Price level
"150.0" // Quantity
]
],
"time": 1758096768006 // Event time
}
}
}

Ticker Streams

24hr rolling window ticker statistics for all markets.

Channel Name: ticker (all markets only)

Subscribe:

{
"method": "subscribe",
"params": {
"channels": ["ticker"]
},
"id": 1
}

Stream Data:

{
"method": "subscription",
"params": {
"channel": "ticker",
"result": [
{
"marketId": "1_2",
"baseTokenId": "1",
"quoteTokenId": "2",
"price": "2.3",
"open24h": "2.1",
"high24h": "2.5",
"low24h": "2.0",
"volume24h": "150000.5",
"quoteVolume24h": "345001.15"
},
{
"marketId": "3_2",
"baseTokenId": "3",
"quoteTokenId": "2",
"price": "3500.0",
"open24h": "3400.0",
"high24h": "3600.0",
"low24h": "3350.0",
"volume24h": "1200.5",
"quoteVolume24h": "4200000.0"
}
]
}
}

Ticker Stream Response Fields:

FieldTypeDescription
resultARRAYArray of ticker data for all markets
marketIdSTRINGMarket identifier
baseTokenIdSTRINGBase token identifier
quoteTokenIdSTRINGQuote token identifier
priceSTRINGCurrent/last traded price
open24hSTRINGOpening price 24 hours ago
high24hSTRINGHighest price in last 24 hours
low24hSTRINGLowest price in last 24 hours
volume24hSTRINGBase token volume in last 24 hours
quoteVolume24hSTRINGQuote token volume in last 24 hours

User Event Streams

Real-time updates for user's orders and trades.

Channel Name: userEvent@{address}

Important: The address in the channel name is automatically converted to EIP-55 checksum format. For example, if you subscribe with a lowercase address, the stream will return with the checksummed version.

Example:

  • Input (lowercase): userEvent@0x000000000000000000000000000000000000abcd
  • Stream returns (checksummed): userEvent@0x000000000000000000000000000000000000ABcD

Subscribe:

{
"method": "subscribe",
"params": {
"channels": ["userEvent@0x000000000000000000000000000000000000ABcD"]
},
"id": 1
}

User Event Response Fields:

Common Fields (All Events):

FieldTypeDescription
topicSTRINGEvent topic: "ORDER" or "ACCOUNT"
eventTypeSTRINGEvent type (see Event Types table below)
eventTimeLONGEvent timestamp in milliseconds
blockNumberLONGBlockchain block number
accountAddressSTRINGAccount address
txHashSTRINGTransaction hash

ORDER Topic Fields (topic="ORDER"):

FieldTypeEvent TypesDescription
orderIdSTRINGALLOrder identifier (transaction hash)
marketIdSTRINGALLMarket identifier
sideSTRINGALL"BUY" or "SELL"
orderTypeSTRINGALLOrder type (LIMIT, MARKET, etc.)
orderModeINTEGERALLOrder mode: 0 = base token quantity, 1 = quote token quantity
origPriceSTRINGALLOriginal order price
origQtySTRINGALLOriginal order quantity
origQuoteOrderQtySTRINGALLOriginal quote order quantity
statusSTRINGALLOrder status: "NEW", "PARTIALLY_FILLED", "FILLED", "CANCELED", "REJECTED"
createdAtLONGALLOrder creation timestamp in milliseconds
executedQtySTRINGALLTotal cumulative executed quantity including this event
executedQuoteQtySTRINGALLTotal cumulative executed quote amount including this event
lastPriceSTRINGALLPrice from this event (0 for non-execution events)
lastQtySTRINGALLQuantity from this event (0 for non-execution events)
feeSTRINGALLFee from this event
feeTokenIdSTRINGALLFee token ID
tradeIdSTRINGALLTrade ID (empty for non-trade events)
isMakerBOOLEANALLTrue if maker order

ACCOUNT Topic Fields (topic="ACCOUNT"):

FieldTypeEvent TypesDescription
tokenIdSTRINGALLToken identifier
amountSTRINGALLAsset amount
fromAddressSTRINGTRANSFERSource address (sender)
toAddressSTRINGTRANSFERDestination address (receiver)

User Event Types:

ORDER Topic Events:

Event TypeDescription
NEWNew order placed
TRADEOrder execution/trade
CANCELOrder canceled
TRIGGERTrigger order activated
REJECTEDOrder was rejected by the system

ACCOUNT Topic Events:

Event TypeDescription
DEPOSITAsset deposit to the DEX
WITHDRAWAsset withdrawal from the DEX
TRANSFERAsset transfer between accounts

ORDER Topic Events

{
"method": "subscription",
"params": {
"channel": "userEvent@0x000000000000000000000000000000000000ABcD",
"result": {
"topic": "ORDER",
"eventType": "TRADE", // Event types: "NEW", "TRADE", "CANCEL", "TRIGGER", "REJECTED"
"eventTime": 1758182405000,
"blockNumber": 12345678,
"accountAddress": "0x000000000000000000000000000000000000ABcD",
"orderId": "0x99eaddbe1a31751b4aca11269d6e45bd9c18894190140fbd80c08053933a7949",
"txHash": "0x99eaddbe1a31751b4aca11269d6e45bd9c18894190140fbd80c08053933a7949",
"marketId": "5_2",
"side": "SELL", // "BUY" or "SELL"
"orderType": "LIMIT", // "LIMIT", "MARKET", "STOP_LOSS_LIMIT", "TAKE_PROFIT_LIMIT"
"orderMode": 0,
"origPrice": "0.2",
"origQty": "10",
"origQuoteOrderQty": "0",
"status": "FILLED", // NEW: "NEW" | TRADE: "PARTIALLY_FILLED" or "FILLED" | CANCEL: "CANCELED" | TRIGGER: "NEW" | REJECTED: "REJECTED"
"createdAt": 1758182405000,
"executedQty": "10", // Cumulative filled amount. NEW/TRIGGER/REJECTED: "0" | TRADE: cumulative | CANCEL: amount filled before cancellation
"executedQuoteQty": "2", // Cumulative filled quote value. Same rules as executedQty
"lastPrice": "0.2", // Price from this specific trade. NEW/CANCEL/TRIGGER/REJECTED: "0" | TRADE: actual trade price
"lastQty": "10", // Quantity from this specific trade. NEW/CANCEL/TRIGGER/REJECTED: "0" | TRADE: actual trade quantity
"fee": "0.0006", // Fee amount for this event. NEW/CANCEL/TRIGGER/REJECTED: "0" | TRADE: actual fee
"feeTokenId": "2", // Fee token ID. null if no fee
"tradeId": "4340900010000", // Trade ID. Empty string for non-TRADE events
"isMaker": false // Whether this was a maker order. Only relevant for TRADE events
}
}
}

Field Notes by Event Type:

  • NEW: Order placed. status="NEW", all execution fields (executedQty, executedQuoteQty, lastPrice, lastQty, fee) are "0", tradeId is empty.
  • TRADE: Order executed. status is "PARTIALLY_FILLED" or "FILLED". executedQty/executedQuoteQty show cumulative totals. lastPrice/lastQty show this specific trade's execution.
  • CANCEL: Order canceled. status="CANCELED". executedQty/executedQuoteQty show amounts filled before cancellation. lastPrice/lastQty are "0".
  • TRIGGER: Stop-loss/take-profit order activated. status="NEW" (order now active in orderbook). All execution fields are "0" as triggering is not an execution.
  • REJECTED: Order rejected by system. status="REJECTED", all execution fields are "0".

ACCOUNT Topic Events

{
"method": "subscription",
"params": {
"channel": "userEvent@0x000000000000000000000000000000000000ABcD",
"result": {
"topic": "ACCOUNT",
"eventType": "TRANSFER", // Event types: "DEPOSIT", "WITHDRAW", "TRANSFER"
"eventTime": 1758182700000,
"blockNumber": 12345710,
"accountAddress": "0x000000000000000000000000000000000000ABcD", // DEPOSIT: recipient | WITHDRAW: sender | TRANSFER: sender or receiver
"txHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"tokenId": "2",
"amount": "25.5",
"fromAddress": "0x000000000000000000000000000000000000ABcD", // Only for TRANSFER events. The sender address
"toAddress": "0x1111111111111111111111111111111111111111" // Only for TRANSFER events. The receiver address
}
}
}

Field Notes by Event Type:

  • DEPOSIT: Asset deposited into the DEX. accountAddress is the recipient (matches ToAddress). fromAddress and toAddress are not included.
  • WITHDRAW: Asset withdrawn from the DEX. accountAddress is the initiator (matches FromAddress). fromAddress and toAddress are not included.
  • TRANSFER: Asset transferred between accounts. Two separate events are generated (one for sender, one for receiver). accountAddress matches the party receiving the event. fromAddress and toAddress fields indicate the transfer direction.

Subscription Management

Subscribe to Multiple Streams

You can subscribe to multiple streams in a single request:

{
"method": "subscribe",
"params": {
"channels": [
"trade@1_2",
"depth@1_2",
"ticker",
"userEvent@0x000000000000000000000000000000000000ABcD"
]
},
"id": 1
}

Unsubscribe from Streams

To unsubscribe from specific streams:

{
"method": "unsubscribe",
"params": {
"channels": ["trade@1_2", "depth@1_2"]
},
"id": 2
}

Connection Maintenance

Ping/Pong

The server sends ping frames every 30 seconds to maintain the connection. Your WebSocket client should automatically respond with pong frames. If you're implementing a custom client:

ws.on('ping', function() {
ws.pong(); // Respond to ping with pong
});