All articles
Solana·April 14, 2026·6 min read

Building a Meteora DLMM Liquidity Bot on Solana: Full Walkthrough

Meteora's Dynamic Liquidity Market Maker uses bin-based concentrated liquidity that rebalances fee tiers dynamically, creating a different set of rebalancing triggers than Orca Whirlpools. This walkthrough covers SDK setup, bin position management, automated range shifting logic, and how to measure impermanent loss in real time.

Meteora DLMM (Dynamic Liquidity Market Maker) is not just another concentrated liquidity pool — its bin-based architecture means your position management logic has to be built around discrete price buckets rather than a continuous tick range. Building a Meteora DLMM liquidity bot on Solana requires a different mental model than Orca or Raydium CLMM, but the fee APY when positioned correctly makes it worth understanding deeply. This walkthrough covers everything from SDK initialization through live impermanent loss tracking in production.

How the Bin Model Differs From Tick-Based AMMs

Orca Whirlpools use tick arrays that a bot must track across position boundaries. Meteora DLMM instead divides the price space into fixed-width bins, each identified by a binId integer. Each bin has its own fee tier — the active bin (where the current price sits) charges the base fee plus a variable fee that scales with recent price volatility. When price moves into a new bin, fees automatically adjust. This matters for your bot because:

  • Fee revenue is bin-local: you only earn fees when the active price is inside a bin you have liquidity in.
  • Variable fee amplifies revenue during volatility: the vParameter (volatility accumulator) decays over time, so a bot that repositions into the active bin after a swing can catch elevated fees before they decay.
  • Bin step size is fixed at pool creation: a 10 bps bin step means each adjacent bin represents a 0.1% price move. Choose pools with bin steps that match the asset's typical hourly range — too narrow means constant rebalancing cost, too wide means your capital sits idle most of the time.

SDK Setup and On-Chain State

Install @meteora-ag/dlmm (currently 0.6.x series). The key entrypoint is DLMM.create(connection, poolAddress), which fetches and decodes the on-chain LbPair account and gives you access to pool state without manual account deserialization.

import DLMM from "@meteora-ag/dlmm";
import { Connection, PublicKey } from "@solana/web3.js";

const connection = new Connection(RPC_URL, "processed");
const dlmmPool = await DLMM.create(connection, new PublicKey(POOL_ADDRESS));

Two state objects matter most: dlmmPool.lbPair.activeId (the current active bin) and the bin arrays returned by dlmmPool.getBinsBetweenLowerAndUpperBound(lowerBin, upperBin, baseTokenAmount, quoteTokenAmount). Poll activeId on every slot via a websocket account subscription — not a timer — so you react to bin transitions in real time rather than on a fixed cadence.

Position Management: Opening and Shifting

To add liquidity, you construct a distribution strategy. The SDK exposes DLMM.calculateSpotDistribution, calculateBidAskDistribution, and calculateNormalDistribution. For a market-making bot, bid-ask distribution (concentrated at the edges of your range) usually outperforms spot for fee capture, but it carries more IL risk during trends. Start with spot and switch after you have baseline IL data.

const activeBin = await dlmmPool.getActiveBin();
const BINS_EACH_SIDE = 5;
const minBinId = activeBin.binId - BINS_EACH_SIDE;
const maxBinId = activeBin.binId + BINS_EACH_SIDE;

const { liquidityDistribution } = DLMM.calculateSpotDistribution(
  activeBin.binId,
  minBinId,
  maxBinId
);

const addLiqTx = await dlmmPool.addLiquidityByStrategy({
  positionPubKey: positionKeypair.publicKey,
  user: wallet.publicKey,
  totalXAmount: xAmount,
  totalYAmount: yAmount,
  strategy: {
    maxBinId,
    minBinId,
    strategyType: StrategyType.SpotBalanced,
  },
});

The rebalancing trigger for your bot should be bin distance from active, not time or percentage price move. A practical threshold: rebalance when Math.abs(activeBin.binId - positionCenterBin) > BINS_EACH_SIDE * 0.6. This means you shift when the active price has moved more than 60% of the way toward the edge of your range. At that point you close the current position, wait for the transaction to confirm, then open a new one centered on the current active bin.

Critical: never open the new position in the same transaction as the close unless you have precise price-impact modeling. Fee overhead on both legs can easily exceed 0.15 SOL for a wide position. Keep a gas reserve account topped up above 0.5 SOL and monitor it in your alerting stack alongside IL.

Automated Range-Shifting Logic

The rebalancing loop in production looks roughly like this:

  1. Subscribe to lbPair account changes via connection.onAccountChange.
  2. On each update, decode the new activeId and compare against your stored position center.
  3. If drift exceeds threshold, push a rebalance task onto a serial queue — never run two rebalances concurrently.
  4. The rebalance task: (a) fetch current position bins and amounts, (b) compute expected fees owed, (c) close position and claim fees in a single transaction using dlmmPool.removeLiquidity, (d) open new position centered on the new active bin.

A common mistake is using commitment: "confirmed" for the close but "processed" for reading state before the open. This creates a race where your bot opens a position based on stale active-bin data. Use "confirmed" for all state reads that feed position decisions. The extra 400ms is worth it — a miscentered open is a guaranteed IL loss before the first fee is earned.

For a tighter feedback loop on Solana transaction landing, send rebalance transactions with priority fees calibrated to the 75th-percentile recent fee for the pool's program, not a static value. A stale position costs more than a high-fee transaction does.

Measuring Impermanent Loss in Real Time

IL on DLMM is harder to isolate than on a 50/50 constant-product pool because fee revenue accumulates per bin and you earn variable fees that aren't reflected in position token amounts until you claim. The correct approach is to track value in, value out across the full position lifetime and benchmark against a hold baseline.

At position open, snapshot:

  • token amounts deposited (xIn, yIn)
  • price at open (P0 = activeBin.price)

At any time T, IL in USD is:

holdValue = xIn * P_T + yIn * USD_stable
positionValue = currentX * P_T + currentY * USD_stable + claimedFees
IL = holdValue - positionValue
netPnL = claimedFees - IL

Fetch currentX and currentY from dlmmPool.getPositionsByUserAndLbPair(userPubKey, poolPubKey) — this returns per-bin amounts so you can see exactly which bins still have active liquidity. Store snapshots in a local Postgres table every 5 minutes and alert when cumulative IL exceeds claimed fees by more than 15%. That's the signal that your range is too wide or your rebalance threshold is too tight for the current volatility regime.

For a market-making bot on Solana DEXes that touches multiple pools, running this accounting per-pool in parallel is non-trivial. A shared position-tracking service with a websocket feed is the cleanest architecture once you're running more than two pools simultaneously.

What Goes Wrong in Production

A few failure modes that only show up after real capital is deployed:

Bin liquidity fragmentation — if you rebalanced many times and some old positions were only partially closed (due to partial-fill on fee claims), you end up with dust amounts scattered across 20+ bin positions. Write a cleanup job that scans for positions with less than $1 of value and sweeps them.

Price manipulation at rebalance — DLMM pools with thin liquidity can be pushed into a new bin right before your rebalance transaction lands, centering your new position one bin off. Use the active bin from the "confirmed" state fetched inside your rebalance handler, not the one that triggered the event.

Fee-claim timing — Meteora accumulates fees in the position account, not automatically in your wallet. Bots that never claim fees underestimate their IL and overestimate losses. Claim fees at every rebalance and track claimed amounts separately.

Running a production DLMM bot also benefits from the same MEV awareness that matters everywhere on Solana — understand how Solana MEV affects on-chain liquidity positions before sizing up. Sandwich attacks around your rebalance transactions are rare but possible on volatile assets with thin pools.


If you want a production-grade Meteora DLMM liquidity bot built and operated for your pool — position management, IL tracking, alerting, and full source — get in touch at /contact to scope it out.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Solana#Meteora#DLMM#Liquidity Bot#Concentrated Liquidity#DeFi#Market Making