JIT Liquidity on Meteora DLMM: How MEV Bots Front Swaps
A senior engineer's guide to Meteora DLMM JIT liquidity MEV: seeding single-sided bins one slot before a whale swap to capture fees, and how to build the bot.
Meteora's DLMM concentrates liquidity into discrete price bins, and every one of those bins is a single account you can deposit into or withdraw from atomically within one Solana transaction. That property is the whole game. If you can see a large swap coming and land your own instruction in the same slot, one leg ahead of it, you can seed the exact active bin the swap will trade against, collect the fee on that volume, and pull your liquidity back out before the price drifts. This is JIT liquidity, and on DLMM it's cleaner than the Uniswap v3 version because bins have zero slippage inside a single price step — the fee is a flat basis-point cut of the swap, not something you earn by eating adverse selection across a curve.
Why DLMM bins make JIT tractable
A DLMM pool is a set of bins spaced by a bin_step (in basis points). The active bin holds both tokens; every bin above it holds only the quote token, every bin below only the base. A swap walks the active bin first, and if it exhausts that bin's reserves, it moves to the next one, paying the pool's fee at each step.
For a JIT provider, this means you don't need to supply a range. You supply into one bin — the active bin, or the specific bin the incoming swap will cross into — as single-sided liquidity. If a fat buy is coming, you deposit the quote-side token so you're the counterparty that gets filled, and you earn swap_amount * fee_bps on whatever volume routes through your position.
The fee on Meteora DLMM isn't fixed either. It's base_fee + variable_fee, where the variable component scales with recent volatility (the pool tracks a volatility_accumulator). A large swap right after a quiet period pays close to the base rate; a large swap into an already-moving market pays materially more. Your JIT edge is fattest exactly when the pool is calm and someone drops a whale order — low variable fee for them, but you're capturing the base on size you didn't have to warehouse.
The mechanics, concretely
Say WSOL/USDC with bin_step = 10 (10 bps per bin), base fee 20 bps, active bin at price 152.30. A pending transaction wants to buy 4,000 SOL of USDC-quoted size. If you front it by depositing ~610,000 USDC of single-sided liquidity into the active bin one instruction earlier, that swap fills against your bin first. You earn roughly notional * 0.0020 on the portion that clears your reserves — on the order of low-thousands of dollars for a single large fill — then you withdraw in the trailing instruction of a bundle. Your inventory exposure lasts microseconds of wall-clock and, more importantly, zero blocks if you bundle correctly.
The catch is that "one instruction earlier" is doing a lot of work. You are racing every other searcher watching the same flow.
Detecting the flow
You can't JIT what you can't see before it lands. Two viable signal sources:
- Shredstream for the earliest possible view of transactions as they propagate, before they're even in a confirmed block. Getting wired into it is its own latency project; we've written up the searcher latency edge from Jito ShredStream and it's the single biggest determinant of whether your JIT lands.
- Geyser plugin streams over gRPC for account and transaction updates with minimal deserialization overhead. If you're standing up this pipeline, the mechanics of Yellowstone gRPC Geyser mempool streaming cover the subscription filters and backpressure handling you'll need.
Your detector is a filter on transactions touching the Meteora DLMM program with a swap instruction whose input amount, decoded from the instruction data, clears a size threshold. Decode the accounts to identify the exact pool and the direction, compute which bin(s) the swap will cross, and decide in single-digit milliseconds whether the expected fee beats your cost to land.
// pseudo-detector inside a geyser tx callback
fn evaluate(tx: &Transaction, pools: &PoolCache) -> Option<JitPlan> {
let ix = tx.find_ix(METEORA_DLMM_PROGRAM)?;
let swap = decode_swap(&ix.data)?; // amount_in, min_out, direction
let pool = pools.get(&ix.accounts[POOL_IDX])?;
if swap.amount_in < pool.jit_threshold { return None; }
let target_bin = pool.active_id; // or projected crossing bin
let fee_bps = pool.base_fee + pool.variable_fee_now();
let gross = swap.amount_in * fee_bps / 10_000;
let cost = TIP + RENT_CHURN + PRIORITY_FEE;
(gross > cost * SAFETY).then(|| JitPlan {
pool: pool.key, bin: target_bin,
deposit_side: swap.opposite_side(),
deposit_amount: sized_to_fill(&swap, pool),
})
}
Landing it atomically
You never want to be caught holding single-sided inventory in a bin if the swap doesn't execute. The whole position must be a bundle: deposit → (victim swap) → withdraw, all-or-nothing. On Solana that means a Jito bundle where your deposit is ordered before the target swap and your withdraw after it, and the bundle either lands whole or not at all.
Three failure modes will eat you alive if you skip them:
- Partial fills. If your deposited reserves are larger than the swap consumes, you're left holding tokens in the bin and your withdraw returns mixed inventory at a price that may have moved. Size your deposit to the swap, not to the bin.
- Bin desync. The active bin ID can change between when you observed it and when your tx lands. Read
active_idas late as possible and include a check; a stale target bin means you deposited liquidity nobody swaps through. - Tip war. Every searcher who saw the same shred is bidding. Your tip has to be dynamic — a function of the expected fee capture, not a constant — or you'll either overpay into unprofitability or lose the slot.
That last point is where most JIT strategies quietly bleed. If you tip 40% of expected fee and someone tips 55%, they land and you don't, and you've spent CU simulating for nothing. Deciding what to protect and how to route bundles is a genuinely hard tradeoff; our comparison of Solana MEV protection across Helius, Temporal, and Jito gets into which routes actually give you the ordering guarantees JIT depends on.
The economics nobody puts in the docs
Rent churn is real. Opening a position account and closing it every bundle isn't free, and at high frequency the rent-exempt lamports you cycle through add up against your fee capture. Batch where you can, reuse position accounts across bundles for the same pool, and don't JIT sub-threshold swaps just because you technically can.
The other quiet cost is compute-unit budgeting. A deposit-swap-withdraw bundle against DLMM is CU-heavy, and if you underrequest, your bundle drops on a CU exceed and you've still paid the tip auction's opportunity cost. Simulate the exact CU on a recent slot and pad it by a real margin.
DLMM JIT is a latency-bound, inventory-neutral strategy that rewards teams who've already solved the boring infrastructure — a clean shred feed, a fast decoder, dynamic tipping, and atomic bundling. If you get all four right, it's one of the few Solana MEV plays where your capital at risk is measured in microseconds. If you're building this out and want the streaming and bundling backbone handled properly, that's exactly what our Solana MEV and arbitrage bot engineering work is built for.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article