The job is state maintenance, not fast reads alone

A DLMM automation loop may rebalance a position, monitor a price range, classify swaps, or prepare some other bounded action. Whatever the strategy, the infrastructure has the same responsibilities:

  1. Discover the pool and load a coherent starting snapshot.
  2. Observe the accounts that can change the decision.
  3. Apply updates in slot order and reject duplicates.
  4. Detect disconnects, dead slots, and local state gaps.
  5. Reconcile the local view against an authoritative RPC read.
  6. Build, simulate, sign, submit, and verify an action under explicit limits.

The strategy decides whether to act. The infrastructure decides whether the state is trustworthy enough to permit that action.

A low-latency stream is useful only when the bot can prove what it has seen, what it may have missed, and which snapshot its next transaction is based on.

This guide uses the official Meteora DLMM TypeScript SDK for pool access and the official Yellowstone gRPC interface for streaming concepts. Read those projects as the source of truth for current package versions, protobuf fields, and program behavior.

A practical two-path architecture

Do not force one interface to do every job.

PathUse it forDo not assume
JSON-RPC plus Meteora SDKPool discovery, startup snapshot, quotes, transaction construction, simulation, reconciliationRepeated polling will provide a stable low-latency event feed
Yellowstone gRPCAccount writes, relevant transactions, slots, and live incremental updatesEvery connection has infinite retention or can replace authoritative state
Transaction senderSubmission and route policy for a signed transactionAcceptance means the transaction landed
Signature and state reconciliationConfirming outcome and rebuilding local truthA successful simulation guarantees inclusion

The shape is:

dlmm-control-loop.txt
RPC bootstrap -> local pool model
                     |
Yellowstone updates -> reducer -> strategy gate -> build and simulate -> sign -> send
                           |                                      |              |
                           +-> gap detector                       +-> reject     +-> verify
                           |
periodic RPC snapshot -----+-> reconcile or pause

For a broader comparison of polling, WebSockets, gRPC, and preprocessed signals, see Which Solana Stream Do You Actually Need?.

Step 1: bootstrap with the official Meteora SDK

Install the SDK and its Solana dependencies using the versions listed in the official repository:

install.sh
npm install @meteora-ag/dlmm @solana/web3.js

Create the pool client from a known LB pair address, then read the initial pool state. Keep the RPC endpoint and the gRPC endpoint in the same region when possible, so the snapshot and live stream are less likely to describe materially different tips of the chain.

bootstrap-pool.ts
import DLMM from "@meteora-ag/dlmm";
import { Connection, PublicKey } from "@solana/web3.js";
 
const connection = new Connection(process.env.SOLANA_RPC_URL!, {
  commitment: "confirmed",
  wsEndpoint: process.env.SOLANA_WS_URL,
});
 
const lbPair = new PublicKey(process.env.METEORA_LB_PAIR!);
const pool = await DLMM.create(connection, lbPair);
const activeBin = await pool.getActiveBin();
 
const bootstrap = {
  lbPair: lbPair.toBase58(),
  activeBinId: activeBin.binId,
  price: activeBin.price,
  observedAtMs: Date.now(),
};
 
console.log(bootstrap);

This is a bootstrap, not a permanent cache. Save the slot associated with the snapshot if your RPC method exposes it. Your stream consumer needs a boundary that answers a basic question: which updates happened after the state I loaded?

At startup, also resolve the bin array accounts that cover the range your system cares about. DLMM distributes liquidity across discrete bins, so watching only the pair account may tell you that the active bin moved without giving you every liquidity value needed for a decision. Derive the relevant bin arrays from current pool state and the strategy range, then update that watch set when the active range moves.

Step 2: subscribe to the smallest useful account set

Yellowstone supports account filters by public key, owner, data size, and memory comparison. It also supports transaction filters using account_include, account_exclude, and account_required. The exact semantics are documented in the Yellowstone repository.

For a DLMM state loop, begin with explicit account keys:

  • the LB pair account;
  • the bin array accounts around the active range;
  • position accounts owned by the bot, if position state is part of the decision;
  • token accounts needed to enforce inventory limits.

Avoid subscribing to the entire program unless you genuinely need every pool. A broad subscription increases bandwidth, decoding work, memory pressure, and the chance that useful updates wait behind irrelevant ones in your own process.

The request below uses the official Yellowstone client shape. watchedAccounts should come from the Meteora bootstrap and range selection step, not from a hardcoded production list.

subscribe-dlmm-accounts.ts
import Client, {
  CommitmentLevel,
  SubscribeRequest,
} from "@triton-one/yellowstone-grpc";
 
const client = new Client(
  process.env.SOLANA_GRPC_URL!,
  process.env.SOLANA_GRPC_TOKEN!,
  { "grpc.max_receive_message_length": 64 * 1024 * 1024 },
);
 
const stream = await client.subscribe();
 
const request: SubscribeRequest = {
  accounts: {
    dlmmState: {
      account: watchedAccounts,
      owner: [],
      filters: [],
    },
  },
  slots: {
    slotStatus: { filterByCommitment: false },
  },
  transactions: {},
  transactionsStatus: {},
  blocks: {},
  blocksMeta: {},
  entry: {},
  accountsDataSlice: [],
  commitment: CommitmentLevel.PROCESSED,
  ping: undefined,
  fromSlot: undefined,
};
 
stream.write(request);

The official protobuf uses snake case on the wire while generated TypeScript clients commonly use camel case. Confirm field names against the installed client version before deploying.

When transaction subscriptions help

Account writes are the right default when the action depends on current pool state. Add a filtered transaction stream when you need to classify swaps, attribute state changes, or measure which instructions preceded a move.

Use the Meteora program ID in account_include, then narrow the result in your process to the LB pair and instruction variants you support. Set vote and failed explicitly. A transaction stream adds context, but it should not silently become the source of truth for balances or current bin state.

Step 3: reduce updates into an ordered local model

Do not call the strategy directly from the gRPC event handler. The handler should validate, record, and reduce each update into a local model. The strategy reads only a model marked healthy.

pool-state-reducer.ts
type AccountUpdate = {
  pubkey: string;
  slot: bigint;
  writeVersion: bigint;
  data: Uint8Array;
};
 
type Version = { slot: bigint; writeVersion: bigint };
 
const versions = new Map<string, Version>();
 
function shouldApply(update: AccountUpdate): boolean {
  const previous = versions.get(update.pubkey);
  if (!previous) return true;
  if (update.slot > previous.slot) return true;
  return update.slot === previous.slot &&
    update.writeVersion > previous.writeVersion;
}
 
function apply(update: AccountUpdate) {
  if (!shouldApply(update)) return;
 
  const decoded = decodeKnownDlmmAccount(update.pubkey, update.data);
  localPoolState.apply(decoded, update.slot);
  versions.set(update.pubkey, {
    slot: update.slot,
    writeVersion: update.writeVersion,
  });
}

The pair of account key, slot, and write version gives the reducer an idempotency boundary. A replay or reconnect may deliver data the process has already seen. Dropping an exact duplicate is correct. Dropping a newer write because it shares a slot is not.

Keep the raw account bytes or a hash long enough to diagnose a bad decode. Parser failures should quarantine the affected pool and alert an operator. They should never fall through to a default price or empty liquidity value.

Step 4: handle disconnects, replay, and forks

A production stream will disconnect eventually. Plan the recovery path before enabling execution.

Yellowstone includes a ping field for keeping long-lived subscriptions active behind load balancers. Its current protobuf also includes from_slot for replay where the server supports a retained range. Neither feature removes the need for a fresh state check.

Use this recovery sequence:

  1. Mark the local model unhealthy as soon as the stream closes or stops advancing.
  2. Disable new execution, but continue verifying already submitted signatures.
  3. Reconnect with bounded exponential backoff and jitter.
  4. Request replay from the last safely applied slot when supported.
  5. Deduplicate replayed account writes in the reducer.
  6. Fetch a fresh SDK or RPC snapshot for the pair and relevant bin arrays.
  7. Compare the snapshot with the reduced state.
  8. Re-enable execution only after the watch set and state agree.

Slot notifications also expose status changes. A slot observed at processed can later be marked dead. Keep processed data useful for responsiveness, but reconcile consequential decisions at the commitment level your risk policy requires. The distinctions are covered in Solana Commitment Levels: Processed vs Confirmed vs Finalized.

Step 5: reconcile even when the stream looks healthy

Silence is not proof of correctness. Schedule a periodic RPC snapshot and compare at least:

  • active bin ID;
  • pool parameters used by quoting or range logic;
  • balances and position state;
  • watched bin array membership;
  • the most recent processed and confirmed slots;
  • the last verified transaction outcome.

If a mismatch changes the next action, pause and rebuild the model. Do not patch one field and assume the rest of the snapshot is coherent.

A simple health gate can make the requirement explicit:

execution-health-gate.ts
type Health = {
  streamConnected: boolean;
  gapDetected: boolean;
  lastUpdateAgeMs: number;
  rpcReconciledAtMs: number;
  watchedRangeCurrent: boolean;
};
 
function mayExecute(health: Health, nowMs: number): boolean {
  const snapshotAgeMs = nowMs - health.rpcReconciledAtMs;
  return health.streamConnected &&
    !health.gapDetected &&
    health.lastUpdateAgeMs < 2_000 &&
    snapshotAgeMs < 15_000 &&
    health.watchedRangeCurrent;
}

The values above are examples, not universal defaults. Choose thresholds from the cadence and failure budget of your application, then test them during deliberate disconnects and delayed RPC responses.

Need RPC, Yellowstone gRPC, and a sender for one Solana bot?

rpc edge provides the read, streaming, and transaction submission paths. Bring the pool, region, expected filters, and workload so the plan can be sized around the actual control loop.

View plans and pricing →

Step 6: build and submit with explicit safety limits

The official Meteora SDK can construct pool instructions and transactions. Keep construction behind a policy boundary. Before signing, check:

  • the pool and token mints match the configured allowlist;
  • the quote or active bin is no older than the strategy permits;
  • inventory after the action remains inside a hard limit;
  • minimum received, maximum spent, and price impact bounds are explicit;
  • the recent blockhash and fee policy are fresh;
  • simulation succeeds against an appropriate commitment;
  • only the intended instructions and accounts are present.

Sign locally. Never put a private key in a stream filter, URL, log line, or support request.

After submission, distinguish these states:

submission-lifecycle.txt
accepted by sender != received by leader
received by leader  != executed
executed            != confirmed
confirmed           != finalized

An accepted response means the sender accepted the bytes and began its configured route policy. It does not prove inclusion. Track the signature, inspect the execution error, refresh affected accounts, and make retries idempotent. See Why Solana Transactions Fail and Landing Transactions on Solana for the full send and verification path.

Do not retry a state-dependent DLMM instruction blindly. The active bin, balances, blockhash, and valid price boundary may have changed since the first attempt. A retry should return to the policy gate and rebuild from fresh state.

What to measure in production

Measure the complete loop instead of one endpoint round trip:

StageUseful metric
Streamupdate age, disconnect count, reconnect duration, replayed updates
Reducerduplicate count, out-of-order count, decode failures, watched range changes
Reconciliationsnapshot mismatch count, time spent unhealthy, processed to confirmed drift
Decisiondecisions evaluated, policy rejections, stale-state rejections
Submissionaccepted count, landed count, execution errors, expiry, confirmation time
Outcomeinventory drift, realized fees and costs, slippage, strategy-specific risk measures

Keep provider latency separate from local decode, decision, signing, network, and confirmation time. Otherwise an optimization in one stage can hide a regression in another. A fuller telemetry model is available in Observability for a Solana Trading Stack.

A safe implementation order

Build the system in stages:

  1. Bootstrap one pool and print a verified active bin.
  2. Stream the pair and relevant bin arrays with execution disabled.
  3. Persist versions and prove duplicate handling with replayed fixtures.
  4. Disconnect the stream deliberately and verify that the health gate closes.
  5. Add periodic RPC reconciliation and dead-slot handling.
  6. Build and simulate transactions without signing or sending.
  7. Add local signing and a strict allowlist.
  8. Submit small bounded actions and verify every signature.
  9. Expand the pool set only after per-pool state and metrics are isolated.

This order makes unsafe ambiguity visible early. It also separates a data correctness problem from a strategy problem and a sender problem.

Bottom line

A Meteora DLMM bot is a stateful Solana application. Bootstrap through RPC and the official Meteora SDK. Stream the smallest relevant account set through Yellowstone gRPC. Reduce updates idempotently, detect gaps, reconcile on a schedule, and close the execution gate whenever state is uncertain. Then build and simulate against fresh state, sign locally, submit through a measured path, and verify the result independently.

That architecture does not promise a profitable strategy. It gives the strategy a controlled, observable, and recoverable way to interact with a live DLMM pool.

Frequently asked questions

Can I build a Meteora DLMM bot with Yellowstone gRPC?
Yes. Use JSON-RPC and the official Meteora DLMM SDK to discover and bootstrap a pool, then use Yellowstone gRPC to receive relevant account or transaction updates. Keep a periodic RPC reconciliation loop because a live stream is not a complete state store by itself.
Which Meteora DLMM accounts should a bot monitor?
Start with the LB pair account and the bin array accounts that cover the price range relevant to your position or decision. The exact set changes with the active bin and your strategy range, so derive it from current pool state and refresh the subscription when that range changes.
Should a DLMM bot subscribe to accounts or transactions?
Use account updates when the decision depends on current pool state such as the active bin and liquidity distribution. Use transaction updates when you need to classify swaps or instructions. Many production systems use both, with account state as the decision input and transactions as context and telemetry.
How should a DLMM bot recover after a gRPC disconnect?
Reconnect with bounded exponential backoff, request replay from a known slot when the provider supports from_slot, deduplicate replayed updates, and refresh authoritative pool state through RPC before enabling execution again.
Does faster infrastructure make a DLMM strategy profitable?
No. Infrastructure can reduce observation and submission delay, but it cannot create a profitable strategy or remove inventory, fee, slippage, adverse selection, smart contract, or transaction failure risk. Validate the strategy separately and enforce explicit execution limits.