# DLOB (Decentralized Limit Order Book)

> Canonical: https://docs.velocity.exchange/developers/velocity-sdk/dlob

> **Warning:**
>
> The DLOB is to be removed. An order will rest in one CLOB market account instead
> of in the `User` account of its owner. The fill route already moves that way: a
> taker remainder that can rest goes onto the book, not into `User.orders`. This
> page describes the DLOB as it works now. New work belongs on the CLOB book. See
> [PropAMM and CLOB Order Flow](/developers/concepts/propamm-and-clob-order-flow.md).

## What is the DLOB?

The **Decentralized Limit Order Book (DLOB)** is Velocity's onchain representation of all resting limit orders across all users. Unlike a traditional centralized order book maintained by an exchange, the DLOB is constructed locally by reading onchain user accounts and aggregating their open limit orders into a price-ordered book.

When a new order arrives, keepers and market makers query the DLOB to find matching resting orders. Velocity's matching engine then executes fills between the incoming taker and the resting makers on the DLOB, or routes to the AMM as a fallback.

> **Warning:**
>
> Velocity removed **spot DLOB trading**: `place_spot_order`, `place_and_take_spot_order`, `place_and_make_spot_order`, and `fill_spot_order` no longer exist onchain (calls now fail with `SpotDlobTradingDisabled`). Spot markets still exist for collateral, borrow-lend, and [swaps](/developers/velocity-sdk/swaps.md), but they can no longer be traded on an order book. In practice the DLOB only ever contains `MarketType.PERP` orders: the APIs below still accept a `marketType` parameter for compatibility, but a `MarketType.SPOT` query only ever returns an empty book.

Three kinds of integration read it. Market makers quote against the current best bid and ask and react to order flow. Keeper and filler bots scan it for matchable perp orders to fill for the fee reward. Orderbook UIs render the aggregated L2 or L3 view of a perp market.

## SDK Usage

A local DLOB is assembled from three subscribers, and all three must be running before any query returns a useful answer.

| Class | Role |
|---|---|
| `OrderSubscriber` | The raw feed. Subscribes to every open user order over websocket, polling, or gRPC, and emits `orderCreated`, `userUpdated`, and `updateReceived`. |
| `SlotSubscriber` | The clock. Tracks the current Solana slot, which the book needs to price auctions and drop expired orders. |
| `DLOBSubscriber` | The book. Rebuilds an aggregated `DLOB` from the order feed on a fixed interval and serves L2 and L3 views off it. |
| `DLOB` | The data structure itself, with bid and ask sides and the query methods. Reach it through `dlobSubscriber.getDLOB()` rather than constructing one. |

`UserMap` is the related cache for *accounts* rather than orders. Liquidation bots and anything that has to watch positions across the whole protocol use it instead of, or alongside, `OrderSubscriber`. See [SDK Internals](/developers/velocity-sdk/sdk-internals.md#usermap-for-multiple-users).

### Setting up a local DLOB

The full sequence, in dependency order. `updateFrequency` is the rebuild interval in milliseconds: lower it for a market-making loop, raise it for a dashboard.

  
```js
import { DLOBSubscriber, OrderSubscriber, SlotSubscriber } from "@velocity-exchange/sdk";

// 1. Track the current slot, needed for order expiry and auction pricing.
const slotSubscriber = new SlotSubscriber(connection);
await slotSubscriber.subscribe();

// 2. Subscribe to all open orders across all users.
const orderSubscriber = new OrderSubscriber({
  velocityClient,
  subscriptionConfig: { type: "websocket" },
  fastDecode: true,
  decodeData: true,
});
await orderSubscriber.subscribe();

// 3. Build and maintain the book from the order stream.
const dlobSubscriber = new DLOBSubscriber({
  velocityClient,
  dlobSource: orderSubscriber, // feeds from the OrderSubscriber above
  slotSource: slotSubscriber,  // needed for order expiry and timing
  updateFrequency: 1000,       // rebuild every 1,000 ms
});
await dlobSubscriber.subscribe();

const dlob = dlobSubscriber.getDLOB();
```
  

Call `unsubscribe()` on all three on shutdown so the websocket handles and rebuild interval are released.

### Getting L2 orderbook data

Once subscribed, query the aggregated L2 orderbook: price levels with cumulative size at each.

> **Warning:**
>
> `dlob.getL2()` on its own returns resting-limit maker liquidity only: it does not include the vAMM. For a book that matches what a taker actually trades against, call `dlobSubscriber.getL2({ ..., includeVamm: true })` instead, or pass `getVammL2Generator(...)` output as `fallbackL2Generators` when calling `dlob.getL2()` directly.

  
```js
import { BASE_PRECISION, MarketType, PRICE_PRECISION, convertToNumber } from "@velocity-exchange/sdk";

const marketIndex = 0; // SOL-PERP

// Includes vAMM liquidity alongside resting-limit makers.
const l2 = dlobSubscriber.getL2({
  marketIndex,
  marketType: MarketType.PERP,
  includeVamm: true,
});

// l2.bids and l2.asks are arrays of { price: BN, size: BN }
console.log("Top bid:", convertToNumber(l2.bids[0].price, PRICE_PRECISION),
            "size:", convertToNumber(l2.bids[0].size, BASE_PRECISION));
console.log("Top ask:", convertToNumber(l2.asks[0].price, PRICE_PRECISION),
            "size:", convertToNumber(l2.asks[0].size, BASE_PRECISION));
```
  

### Getting best bid and ask

For the top of book without building the full L2 array:

  
```js
import { MarketType, PRICE_PRECISION, convertToNumber } from "@velocity-exchange/sdk";

const dlob = dlobSubscriber.getDLOB();
const marketIndex = 0;
const slot = slotSubscriber.getSlot();
const oraclePriceData = velocityClient.getMMOracleDataForPerpMarket(marketIndex, slot);

// Returns BN | undefined (undefined if no orders on that side)
const bestBid = dlob.getBestBid(marketIndex, slot, MarketType.PERP, oraclePriceData);
const bestAsk = dlob.getBestAsk(marketIndex, slot, MarketType.PERP, oraclePriceData);

if (bestBid && bestAsk) {
  console.log("Best bid:", convertToNumber(bestBid, PRICE_PRECISION));
  console.log("Best ask:", convertToNumber(bestAsk, PRICE_PRECISION));
  console.log("Spread:", convertToNumber(bestAsk.sub(bestBid), PRICE_PRECISION));
}
```
