> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kuru.io/llms.txt
> Use this file to discover all available pages before exploring further.

# User orders stream

> Follow replayable order updates with periodic full snapshots.

```text theme={null}
userOrders@f[@<coalesceMs>]
```

This user-scoped stream uses `(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 with `orders: []` 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`. `globalSeq` and `globalUserSeq` need not both be null.

Use `(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.

```json theme={null}
{"op":"sub","id":6,"t":"userOrders@f@300","u":2,"feedEpoch":0,"seq":2951}
```

## Decoded event

The shape below is the inner `message` 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](/api/exchange/websocket#binary-subscription-envelope).

```ts theme={null}
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;
```

The binary decoder maps event codes `1` through `5` to `created`, `trade`,
`cancelled`, `rab-reduced`, and `operation-sentinel`. Every delta event carries its exact transaction
source tuple:

```ts theme={null}
const frame = decodeUserOrdersFrame(binaryPayload);

if (frame.snapshot) {
  replaceOpenOrders(frame.orders);
} else {
  for (const event of frame.events) {
    applyUserOrderEvent(event);
  }
}
```

Replace all local open-order state when `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.

* `created` carries the complete new open order.
* `trade` carries the complete [user trade payload](/api/exchange/websocket-reference/user-trades).
  For `activeFifo`, update the resting maker's order using `liquidity.orderId`
  and `liquidity.slotIndex`, setting its remaining size to
  `liquidity.remainingBaseAfter`; remove it when zero. Only mutate the local
  order when `users[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.
* `cancelled` removes the order.
* `rab-reduced` sets the remaining size to `updatedSize`; remove it when that
  value is zero.
* `operation-sentinel` reports an operation outcome without adding or removing
  an open order.

Real order events reach the affected maker. Tagged outcomes also reach the
action account, with overlapping recipients delivered once. For `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 |

Sentinels always have operation metadata, restricted to outcomes `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 includes `blockTimestamp: 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](/api/exchange/user-trades-changelog#kur-1719-canonical-event-timestamps).


## AsyncAPI

````yaml api/exchange/asyncapi.yaml userOrders
id: userOrders
title: Subscribe to user orders
description: Follow replayable order events with periodic full snapshots.
servers:
  - id: mainnet
    protocol: wss
    host: gateway.mainnet.kuru.io
    bindings: []
    variables: []
  - id: testnet
    protocol: wss
    host: gateway.testnet.kuru.io
    bindings: []
    variables: []
address: /ws
parameters: []
bindings: []
operations:
  - &ref_1
    id: subscribeUserOrders
    title: Subscribe user orders
    type: receive
    messages:
      - &ref_2
        id: request
        contentType: application/json
        payload:
          - name: userOrders@f subscription
            type: object
            properties:
              - name: op
                type: string
                enumValues:
                  - sub
                required: true
              - name: id
                type: integer
                description: >-
                  JSON integer restricted to JavaScript's exact safe-integer
                  range.
                required: true
              - name: t
                type: string
                required: true
              - name: u
                type: integer
                description: Numeric Kuru user ID.
                required: true
              - name: feedEpoch
                type: integer
                description: Feed namespace. Required when seq is supplied.
                required: false
              - name: seq
                type: integer
                required: false
        headers: []
        jsonPayloadSchema:
          type: object
          additionalProperties: false
          required:
            - op
            - id
            - t
            - u
          allOf:
            - if:
                required:
                  - seq
                x-parser-schema-id: <anonymous-schema-51>
              then:
                required:
                  - feedEpoch
                x-parser-schema-id: <anonymous-schema-52>
              x-parser-schema-id: <anonymous-schema-50>
          properties:
            op:
              type: string
              enum:
                - sub
              x-parser-schema-id: <anonymous-schema-48>
            id: &ref_0
              type: integer
              minimum: 0
              maximum: 9007199254740991
              description: >-
                JSON integer restricted to JavaScript's exact safe-integer
                range.
              x-parser-schema-id: ControlInteger
            t:
              type: string
              pattern: ^userOrders@f(@[1-9][0-9]*)?$
              x-parser-schema-id: <anonymous-schema-49>
            u:
              allOf:
                - *ref_0
              description: Numeric Kuru user ID.
              x-parser-schema-id: UserId
            feedEpoch:
              allOf:
                - *ref_0
              description: Feed namespace. Required when seq is supplied.
              x-parser-schema-id: FeedEpoch
            seq:
              allOf:
                - *ref_0
              minimum: 1
              x-parser-schema-id: ReplaySequence
          x-parser-schema-id: UserOrdersSubscription
        title: userOrders@f subscription
        example: |-
          {
            "op": "sub",
            "id": 6,
            "t": "userOrders@f@300",
            "u": 2,
            "feedEpoch": 0,
            "seq": 2951
          }
        bindings: []
        extensions:
          - id: x-parser-unique-object-id
            value: request
    bindings: []
    extensions:
      - id: x-parser-unique-object-id
        value: userOrders
sendOperations:
  - *ref_1
receiveOperations: []
sendMessages:
  - *ref_2
receiveMessages: []
extensions:
  - id: x-parser-unique-object-id
    value: userOrders
securitySchemes: []

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.