Messages
{
"op": "sub",
"id": 6,
"t": "userOrders@f@300",
"u": 2,
"feedEpoch": 0,
"seq": 2951
}Exchange WebSocket methods
User orders stream
Follow replayable order updates with periodic full snapshots.
WSS
/
ws
userOrders@f[@<coalesceMs>]
(feedEpoch, globalUserSeq). Without seq, it
sends a complete finalized order snapshot, replays retained updates after that
snapshot, and then joins live delivery. It also sends a complete snapshot every
five seconds by default. Incremental updates use the requested coalescing
duration, which defaults to 300 milliseconds.
Subscribe
Empty snapshots and nullable sequences
An authoritative snapshot withorders: [] means the user has no open orders
in the selected view. Apply it by clearing the local open-order list, even if
globalSeq or globalUserSeq is null.
Order count and cursor availability are independent:
- An empty snapshot can have non-null sequences after orders are cancelled or
fully filled. Keep a valid
globalUserSeq; do not discard it because the list is empty. - Null sequences indicate an unavailable or zero baseline cursor, not proof that the user has never traded. They can occur for an untouched user, before a snapshot baseline exists, or after checkpoint restoration into a new feed epoch, including for users with restored open orders.
- The binary format encodes missing sequences as zero; the SDK maps zero to
null.globalSeqandglobalUserSeqneed not both be null.
(feedEpoch, globalUserSeq) for user-stream replay, never globalSeq.
If globalUserSeq is null, do not substitute globalSeq or infer a cursor from
the order list. Continue the current subscription’s bootstrap/live flow; on a
fresh connection, omit seq to request a new authoritative snapshot. Follow
the documented predecessor checks once sequenced updates arrive.
{"op":"sub","id":6,"t":"userOrders@f@300","u":2,"feedEpoch":0,"seq":2951}
Decoded event
The shape below is the innermessage returned by decodeExchangeWsEnvelope
or decodeExchangeWsMessage. Every binary socket message also carries an outer
id: bigint, matching your subscription ID. Use it to route snapshots, replay,
and live updates. See binary subscription envelopes.
interface ExchangeWsBlockContext {
blockNumber: bigint;
blockId: string;
}
interface ExchangeWsUserOrder {
createdAt: bigint;
marketAddress: string;
orderId: bigint;
slotIdx: number;
side: "buy" | "sell";
price: bigint;
remainingBase: bigint;
minSizeAfterBlock: bigint | null;
clientOrderId: string | null;
}
interface ExchangeWsUserOrderSource {
txHash: string;
txIdx: number;
logIdx: number;
recordIdx: number;
}
interface ExchangeWsAction {
accountId: bigint;
executor: string;
clientOrderId: string | null;
}
interface ExchangeWsOperation {
outcome: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
operationIndex: number;
}
interface ExchangeWsTradeOperation extends ExchangeWsOperation {
replacementSlot: number;
}
interface ExchangeWsUserTrade {
action: ExchangeWsAction;
operation: ExchangeWsTradeOperation | null;
marketAddress: string;
tradeId: bigint;
recordIndex: number;
users: [bigint, bigint]; // [takerId, makerId]; passive maker is 0n
takerSide: "buy" | "sell";
price: bigint;
baseFilled: bigint;
blockTimestamp: bigint;
liquidity:
| {
kind: "activeFifo";
slotIndex: number;
orderId: bigint;
makerSide: "buy" | "sell";
remainingBaseAfter: bigint;
makerFeePps: number;
}
| {
kind: "passiveBand";
lowPrice: bigint;
passiveSideRemainingAfter: bigint;
};
txHash: string;
txIdx: number;
logIdx: number;
effectiveTakerFeePps: number;
builderFeePps: number;
matchEnd: boolean;
}
type ExchangeWsUserOrderEvent =
| (Omit<ExchangeWsUserOrder, "createdAt"> & {
kind: "created";
source: ExchangeWsUserOrderSource;
blockTimestamp: bigint;
makerId: bigint;
action: ExchangeWsAction;
operation: ExchangeWsOperation | null;
})
| (ExchangeWsUserTrade & {
kind: "trade";
source: ExchangeWsUserOrderSource;
})
| {
kind: "cancelled";
action: ExchangeWsAction;
operation: ExchangeWsOperation | null;
source: ExchangeWsUserOrderSource;
blockTimestamp: bigint;
makerId: bigint;
marketAddress: string;
orderId: bigint;
slotIdx: number;
}
| {
kind: "rab-reduced";
source: ExchangeWsUserOrderSource;
blockTimestamp: bigint;
makerId: bigint;
marketAddress: string;
orderId: bigint;
slotIdx: number;
updatedSize: bigint;
}
| {
kind: "operation-sentinel";
source: ExchangeWsUserOrderSource;
blockTimestamp: bigint;
marketAddress: string;
action: ExchangeWsAction;
operation: ExchangeWsOperation;
slotIdx: number;
};
interface ExchangeWsUserOrdersFrameBase {
wireVersion: 1;
feedEpoch: bigint;
view: "finalized";
kind: "userOrders";
userId: bigint;
globalUserSeq: bigint | null;
globalSeq: bigint | null;
previousGlobalUserSeq: bigint | null;
}
interface ExchangeWsUserOrdersSnapshotFrame
extends ExchangeWsUserOrdersFrameBase {
snapshot: true;
stateHead: ExchangeWsBlockContext | null;
orders: ExchangeWsUserOrder[];
}
interface ExchangeWsUserOrdersDeltaFrame extends ExchangeWsUserOrdersFrameBase {
snapshot: false;
sourceBlock: ExchangeWsBlockContext;
events: ExchangeWsUserOrderEvent[];
}
type ExchangeWsUserOrdersFrame =
| ExchangeWsUserOrdersSnapshotFrame
| ExchangeWsUserOrdersDeltaFrame;
1 through 5 to created, trade,
cancelled, rab-reduced, and operation-sentinel. Every delta event carries its exact transaction
source tuple:
const frame = decodeUserOrdersFrame(binaryPayload);
if (frame.snapshot) {
replaceOpenOrders(frame.orders);
} else {
for (const event of frame.events) {
applyUserOrderEvent(event);
}
}
snapshot=true. For a delta, validate
previousGlobalUserSeq, then apply events in array order:
For an explicit replay cut R, the first publication is a valid sparse bridge
when globalUserSeq > R and previousGlobalUserSeq is null or at most R.
After accepting it, require every subsequent previousGlobalUserSeq to equal
the last accepted globalUserSeq exactly.
createdcarries the complete new open order.tradecarries the complete user trade payload. ForactiveFifo, update the resting maker’s order usingliquidity.orderIdandliquidity.slotIndex, setting its remaining size toliquidity.remainingBaseAfter; remove it when zero. Only mutate the local order whenusers[1]matches the subscribed user. A taker’s trade notification does not identify a taker resting order. Passive fills have no active order identity and must not mutate the open-order list.cancelledremoves the order.rab-reducedsets the remaining size toupdatedSize; remove it when that value is zero.operation-sentinelreports an operation outcome without adding or removing an open order.
created and
cancelled, only mutate the subscribed user’s open orders when makerId
matches that user. Tagged passive fills reach the taker, and sentinels reach
the action account. Snapshots still contain only real open orders.
Key each live slot by (marketAddress, slotIdx). Before updating or removing a
slot, verify that its current orderId matches the event. This prevents a late
event from mutating a newer order that reused the physical slot. Use the frame’s
globalUserSeq for stream continuity; the event’s source tuple identifies the
causal onchain or generated record.
Action and operation outcomes
action identifies the action account, executor address, and optional client
order ID. It is present on created, trade, cancelled, and
operation-sentinel, but not rab-reduced. The action account is not
necessarily the affected maker.
operation is nullable on created, trade, and cancelled events. When present,
it supplies operationIndex and an outcome code; trades additionally include
replacementSlot. This metadata describes the operation outcome, not another
fill or open order.
| Outcome | Meaning |
|---|---|
| 1 | FILLED |
| 2 | RESTED |
| 3 | COMPLETED_NO_REST |
| 4 | IOC_PARTIAL |
| 5 | IOC_UNFILLED |
| 6 | POST_ONLY_NOT_PLACED |
| 7 | CANCELLED |
| 8 | PROTOCOL_CANCELLED |
| 9 | CANCEL_NOOP |
| 10 | DUST_REMAINDER_DISCARDED |
3, 5, 6,
and 9. Use them to resolve pending actions, not to create synthetic orders.
Wire version remains 1; use the matching updated SDK decoder for these
event layouts. Snapshot order rows are unchanged. Prices remain in market
price precision and sizes in market size precision.
Canonical event timestamp
Each decoded trade row or order delta event includesblockTimestamp: bigint
in Unix seconds. It is the source block’s canonical header timestamp and is
identical in live delivery and retained replay. It is not the publication time.
Snapshot orders expose createdAt: bigint in Unix seconds: their original creation-block time, retained through updates and checkpoint restore. Each snapshot row is now 104 bytes, with createdAt appended at offset 96. On created deltas, initialize createdAt from blockTimestamp; later events do not change it. See the event timestamp changelog.Messages
{
"op": "sub",
"id": 6,
"t": "userOrders@f@300",
"u": 2,
"feedEpoch": 0,
"seq": 2951
}userOrders@f subscription
type:object