> For the complete documentation index, see [llms.txt](https://docs.gammaswap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gammaswap.com/developers/guides/maintaining-an-order-book.md).

# Maintaining an Order Book

Use the HTTP API to load a complete order-book snapshot and the market WebSocket to apply real-time changes.

The order-book snapshot and every market update include a sequence ID.

A reliable client should use the sequence ID to:

* Ignore events already included in the snapshot
* Apply updates in order
* Detect missing events
* Determine when a complete resynchronization is required

### Order-book data flow

```
Subscribe to market updates
        ↓
Buffer incoming updates
        ↓
Load the HTTP order-book snapshot
        ↓
Discard buffered updates at or below the snapshot sequence ID
        ↓
Apply later updates in sequence
        ↓
Reload the snapshot after a gap or resync notification
```

### Create the clients

```ts
import {
  createInfoClient,
  createExchangeWebSocketClient,
} from "@gammaswap/v2-exchange-sdk";

const info = createInfoClient({
  apiUrl: "https://exchange-api.gammaswap.com/api",
});

const marketStream =
  createExchangeWebSocketClient({
    websocketUrl:
      "wss://exchange-api.gammaswap.com/ws/",
    onError: (error) => {
      console.error("Market stream error", error);
    },
  });
```

### Identify the market

Order books are identified by:

* `assetId`
* `epoch`

```ts
const assetId =
  "261336857817713630688382311349658711122006440411137";

const epoch = "12";
```

The market WebSocket subscription uses `assetId`.

The HTTP order-book snapshot uses both `assetId` and `epoch`.

### Subscribe before loading the snapshot

Subscribe first and temporarily buffer updates.

This prevents updates that occur during the HTTP request from being missed.

```ts
const bufferedUpdates: MarketUpdate[] = [];
let synchronized = false;

const unsubscribe =
  await marketStream.subscribeOrderBook(
    assetId,
    {
      onUpdate: (update) => {
        if (!synchronized) {
          bufferedUpdates.push(update);
          return;
        }

        applyUpdate(update);
      },
      onResyncRequired: () => {
        void resynchronize();
      },
      onError: (error) => {
        console.error(
          "Order-book subscription error",
          error,
        );
      },
    },
  );
```

`MarketUpdate` represents the market-update type exported by the SDK. Use the SDK’s actual exported type name in the implementation.

### Load the initial snapshot

```ts
let book = await info.getOrderBook({
  assetId,
  epoch,
});
```

Example response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "ts": 1730000000,
  "seqId": 123,
  "bids": [
    {
      "price": "999000",
      "size": "1025",
      "orderCount": 1,
      "orders": []
    }
  ],
  "asks": [
    {
      "price": "1000000",
      "size": "500",
      "orderCount": 1,
      "orders": []
    }
  ]
}
```

Record the snapshot sequence ID:

```ts
let lastSeqId = book.seqId;
```

### Apply buffered updates

Discard updates that are already represented by the snapshot.

```ts
const pendingUpdates = bufferedUpdates
  .filter((update) => update.seqId > lastSeqId)
  .sort((a, b) => a.seqId - b.seqId);
```

Apply the remaining events in sequence:

```ts
for (const update of pendingUpdates) {
  applyUpdate(update);
}

bufferedUpdates.length = 0;
synchronized = true;
```

### Validate sequence IDs

Before applying an update, confirm that it follows the last applied update.

```ts
function applyUpdate(update: MarketUpdate) {
  if (update.seqId <= lastSeqId) {
    return;
  }

  const expectedSeqId = lastSeqId + 1;

  if (update.seqId !== expectedSeqId) {
    void resynchronize();
    return;
  }

  applyMarketEvent(book, update);
  lastSeqId = update.seqId;
}
```

This example assumes sequence IDs increase by one for each applicable market event. Confirm that behavior against the SDK’s exported market-update contract before enforcing it in production.

At minimum, clients should reject updates that move backward and resynchronize whenever continuity cannot be established.

### Market update types

The market WebSocket publishes four update types.

#### Order

```json
{
  "type": "order",
  "seqId": 124,
  "data": {}
}
```

#### Trade

```json
{
  "type": "trade",
  "seqId": 125,
  "data": {}
}
```

#### Cancel

```json
{
  "type": "cancel",
  "seqId": 126,
  "data": {}
}
```

#### Resolution

```json
{
  "type": "resolution",
  "seqId": 127,
  "data": {}
}
```

The development team’s WebSocket document does not define the fields inside `data`.

The exact order-book mutation logic should be implemented from the SDK’s exported event types rather than inferred from the empty examples.

### Use specific handlers

Applications can use one general handler:

```ts
onUpdate: (update) => {
  applyUpdate(update);
}
```

They can also use event-specific handlers:

```ts
await marketStream.subscribeOrderBook(
  assetId,
  {
    onOrder: (update) => {
      console.log("Order", update);
    },
    onTrade: (update) => {
      console.log("Trade", update);
    },
    onCancel: (update) => {
      console.log("Cancel", update);
    },
    onResolution: (update) => {
      console.log("Resolution", update);
    },
  },
);
```

Do not apply the same update through both `onUpdate` and an event-specific handler.

Use `onUpdate` for one centralized order-book reducer, or use the specific handlers without also processing the general callback.

### Resynchronize the order book

Reload the entire snapshot when:

* `onResyncRequired` is called
* A sequence gap is detected
* The connection reconnects
* An update cannot be applied
* The local book fails an integrity check
* The market epoch changes

```ts
let resyncing = false;

async function resynchronize() {
  if (resyncing) {
    return;
  }

  resyncing = true;
  synchronized = false;
  bufferedUpdates.length = 0;

  try {
    const freshBook =
      await info.getOrderBook({
        assetId,
        epoch,
      });

    book = freshBook;
    lastSeqId = freshBook.seqId;

    const pendingUpdates = bufferedUpdates
      .filter(
        (update) =>
          update.seqId > lastSeqId,
      )
      .sort(
        (a, b) =>
          a.seqId - b.seqId,
      );

    for (const update of pendingUpdates) {
      applyUpdate(update);
    }

    bufferedUpdates.length = 0;
    synchronized = true;
  } catch (error) {
    console.error(
      "Order-book resynchronization failed",
      error,
    );
  } finally {
    resyncing = false;
  }
}
```

In production, retain updates received while the snapshot request is in progress. Do not clear the same buffer after new events have been added to it.

A safer implementation swaps active buffers:

```ts
let updateBuffer: MarketUpdate[] = [];

async function loadSnapshotWithBuffer() {
  synchronized = false;

  const activeBuffer = updateBuffer;
  updateBuffer = [];

  const snapshot =
    await info.getOrderBook({
      assetId,
      epoch,
    });

  const allBufferedUpdates = [
    ...activeBuffer,
    ...updateBuffer,
  ];

  book = snapshot;
  lastSeqId = snapshot.seqId;

  const pendingUpdates = allBufferedUpdates
    .filter(
      (update) =>
        update.seqId > lastSeqId,
    )
    .sort(
      (a, b) =>
        a.seqId - b.seqId,
    );

  updateBuffer = [];

  for (const update of pendingUpdates) {
    applyUpdate(update);
  }

  synchronized = true;
}
```

### Top-of-book applications

Applications that only need the best bid and ask can use:

```ts
const top = await info.getTopOfBook({
  assetId,
  epoch,
});
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "seqId": 123,
  "ts": 1730000000,
  "bid": {
    "price": "999000",
    "size": "1025",
    "orderCount": 1,
    "orders": []
  },
  "ask": {
    "price": "1000000",
    "size": "500",
    "orderCount": 1,
    "orders": []
  },
  "last": "999500",
  "lastTs": "1730000000"
}
```

The same resynchronization principles apply: reload the HTTP value after a disconnect or sequence failure.

### Account-owned orders

Use `getBookOrders` to load resting orders for one account.

```ts
const accountOrders =
  await info.getBookOrders({
    assetId,
    epoch,
    account:
      "0x1111111111111111111111111111111111111111",
  });
```

Response:

```json
{
  "assetId": "261336857817713630688382311349658711122006440411137",
  "epoch": "12",
  "seqId": 123,
  "ts": 1730000000,
  "buys": [],
  "sells": []
}
```

Reload this snapshot after a resynchronization event if the application maintains a separate account-order view.

### Unsubscribe

The function returned by `subscribeOrderBook` removes only the handler created by that subscription call.

```ts
await unsubscribe();
```

To remove every local handler for an asset:

```ts
await marketStream.unsubscribeOrderBook(
  assetId,
);
```

Close the socket when the application no longer needs market updates:

```ts
marketStream.close();
```

### Recommended safeguards

A production order-book consumer should:

* Buffer events during snapshot loading
* Track the last applied sequence ID
* Ignore duplicate or stale events
* Detect sequence discontinuity
* Allow only one resynchronization at a time
* Reload after `onResyncRequired`
* Validate that bid and ask levels remain sorted
* Validate that sizes do not become negative
* Replace the local state atomically after a new snapshot
* Record resynchronization failures and retry with backoff


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.gammaswap.com/developers/guides/maintaining-an-order-book.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
