Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.meteora.ag/llms.txt

Use this file to discover all available pages before exploring further.

Use CPI when your Solana program must call DLMM directly, for example to swap, add or remove liquidity, claim fees or rewards, or manage a DLMM position as part of a larger protocol instruction.
The stable integration contract is the published DLMM IDL and the official SDK behavior. This page is cross-checked against the lb_clmm source so the account ordering and remaining-account notes match the on-chain handlers.

Program ID

pub const DLMM_PROGRAM_ID: Pubkey =
    pubkey!("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo");
The public program ID is the same for mainnet and devnet.

Generated Bindings

Prefer generated CPI bindings from the DLMM IDL. The lb_clmm handlers use Anchor accounts, optional accounts, generated instruction args, and #[event_cpi] on many v2 instructions.
Do not hand-type account order from memory. Generate bindings or compare against SDK-built instructions. Most CPI failures come from one misplaced optional account, event authority account, bin array, or Token-2022 remaining account.
If you use Anchor-generated CPI modules, the handler usually looks like:
let cpi_ctx = CpiContext::new(
    ctx.accounts.dlmm_program.to_account_info(),
    dlmm::cpi::accounts::Swap2 {
        // generated account struct fields
    },
)
.with_remaining_accounts(ctx.remaining_accounts.to_vec());

dlmm::cpi::swap2(
    cpi_ctx,
    amount_in,
    min_amount_out,
    remaining_accounts_info,
)?;
If your integration uses raw invoke / invoke_signed, build the same instruction data and account metas produced by the generated client.

CPI Instruction Scope

For the full lb_clmm instruction-family map, see DLMM Program Instructions. This page focuses on CPI implementation details for the v2 instructions most integrations call: swap2, swap_exact_out2, add_liquidity2, remove_liquidity2, claim_fee2, claim_reward2, and rebalance_liquidity. New CPI integrations should prefer v2 instructions because they support SPL Token, Token-2022, dynamic positions, and explicit remaining account metadata.

Event CPI Accounts

Several lb_clmm account structs use #[event_cpi], including Swap2, AddLiquidity2, RemoveLiquidity2, ClaimFee2, and ClaimReward2. Generated clients include extra event CPI accounts, commonly:
AccountWhy it appears
programThe DLMM program account used by Anchor event CPI emission.
event_authorityPDA derived from b"__event_authority" for the DLMM program.
When you inspect an SDK or generated Rust instruction, keep these accounts in the exact generated position. If they are missing or moved, Anchor account deserialization can fail before the DLMM handler reaches your intended logic.

Remaining Accounts Ordering

lb_clmm v2 handlers parse remaining accounts in two phases.
  1. They consume Token-2022 transfer-hook account groups according to RemainingAccountsInfo.slices.
  2. They consume the leftover accounts as instruction-specific accounts, usually bin arrays.
This ordering comes from src/utils/remaining_accounts_util.rs and calls like:
let parsed_transfer_hook_accounts = parse_remaining_accounts(
    &mut remaining_accounts,
    &remaining_accounts_info.slices,
    &[AccountsType::TransferHookX, AccountsType::TransferHookY],
)?;

// remaining_accounts now starts at the first bin array account.
The order of ctx.remaining_accounts must match the order of RemainingAccountsInfo.slices.
Slice typeUsed by
TransferHookXToken X transfers in swap, add liquidity, remove liquidity, claim fee.
TransferHookYToken Y transfers in swap, add liquidity, remove liquidity, claim fee.
TransferHookRewardSingle reward transfer in claim_reward2.
TransferHookMultiReward(index)Multi-reward flows.
TransferHookReferralHost/referral fee transfer in swaps.
If you have no transfer-hook accounts, pass RemainingAccountsInfo { slices: vec![] }, then put bin arrays first in remaining_accounts.

Instruction Account Notes

InstructionMain accountsRemaining accounts after transfer-hook slices
swap2, swap_exact_out2, swap_with_price_impact2lb_pair, optional bin_array_bitmap_extension, reserves, user token accounts, token mints, oracle, optional host_fee_in, user signer, token programs, memo program, event CPI accounts.Bin arrays for the swap route, in the order expected by the quote.
add_liquidity2position, lb_pair, optional bitmap extension, user token X/Y, reserves, mints, sender signer, token programs, event CPI accounts.Bin arrays covering the bins in LiquidityParameter.amounts.
remove_liquidity2position, lb_pair, optional bitmap extension, user token X/Y, reserves, mints, sender signer, token programs, memo program, event CPI accounts.Bin arrays covering the BinLiquidityReduction bins, in ascending bin order.
claim_fee2lb_pair, position, sender signer, reserves, user token X/Y, mints, token programs, memo program, event CPI accounts.Bin arrays from min_bin_id through max_bin_id.
claim_reward2lb_pair, position, sender signer, reward vault, reward mint, user reward token account, token program, memo program, event CPI accounts.Bin arrays from min_bin_id through max_bin_id.
rebalance_liquidityPosition, pool, token, reserve, and authority accounts for the rebalance wrapper.Transfer-hook slices, reward accounts, bin arrays, resize accounts, and other operation-specific accounts in the order produced by SDK simulation.

Swap2 CPI Shape

This shape mirrors swap2 on the program.
use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct SwapThroughDlmm<'info> {
    /// CHECK: DLMM program account.
    pub dlmm_program: AccountInfo<'info>,

    /// CHECK: Validated by DLMM.
    #[account(mut)]
    pub lb_pair: AccountInfo<'info>,

    /// CHECK: Optional. Use the generated optional-account representation.
    #[account(mut)]
    pub bin_array_bitmap_extension: AccountInfo<'info>,

    /// CHECK: DLMM reserve token accounts.
    #[account(mut)]
    pub reserve_x: AccountInfo<'info>,
    /// CHECK: DLMM reserve token accounts.
    #[account(mut)]
    pub reserve_y: AccountInfo<'info>,

    /// CHECK: User token accounts.
    #[account(mut)]
    pub user_token_in: AccountInfo<'info>,
    /// CHECK: User token accounts.
    #[account(mut)]
    pub user_token_out: AccountInfo<'info>,

    /// CHECK: Pool token mints.
    pub token_x_mint: AccountInfo<'info>,
    /// CHECK: Pool token mints.
    pub token_y_mint: AccountInfo<'info>,

    /// CHECK: DLMM oracle.
    #[account(mut)]
    pub oracle: AccountInfo<'info>,

    /// CHECK: Optional host fee account.
    #[account(mut)]
    pub host_fee_in: AccountInfo<'info>,

    pub user: Signer<'info>,

    /// CHECK: SPL Token or Token-2022 program for token X.
    pub token_x_program: AccountInfo<'info>,
    /// CHECK: SPL Token or Token-2022 program for token Y.
    pub token_y_program: AccountInfo<'info>,

    /// CHECK: Memo program.
    pub memo_program: AccountInfo<'info>,

    /// CHECK: Event CPI program account generated by Anchor.
    pub event_program: AccountInfo<'info>,
    /// CHECK: Event authority PDA generated by Anchor.
    pub event_authority: AccountInfo<'info>,
}
Then call the generated CPI and forward remaining accounts:
let remaining_accounts_info = RemainingAccountsInfo { slices: vec![] };

let cpi_ctx = CpiContext::new(
    ctx.accounts.dlmm_program.to_account_info(),
    dlmm::cpi::accounts::Swap2 {
        lb_pair: ctx.accounts.lb_pair.to_account_info(),
        bin_array_bitmap_extension: Some(
            ctx.accounts.bin_array_bitmap_extension.to_account_info(),
        ),
        reserve_x: ctx.accounts.reserve_x.to_account_info(),
        reserve_y: ctx.accounts.reserve_y.to_account_info(),
        user_token_in: ctx.accounts.user_token_in.to_account_info(),
        user_token_out: ctx.accounts.user_token_out.to_account_info(),
        token_x_mint: ctx.accounts.token_x_mint.to_account_info(),
        token_y_mint: ctx.accounts.token_y_mint.to_account_info(),
        oracle: ctx.accounts.oracle.to_account_info(),
        host_fee_in: None,
        user: ctx.accounts.user.to_account_info(),
        token_x_program: ctx.accounts.token_x_program.to_account_info(),
        token_y_program: ctx.accounts.token_y_program.to_account_info(),
        memo_program: ctx.accounts.memo_program.to_account_info(),
        program: ctx.accounts.event_program.to_account_info(),
        event_authority: ctx.accounts.event_authority.to_account_info(),
    },
)
.with_remaining_accounts(ctx.remaining_accounts.to_vec());

dlmm::cpi::swap2(cpi_ctx, amount_in, min_amount_out, remaining_accounts_info)?;
If your program signs for the DLMM user account with a PDA, use CpiContext::new_with_signer and pass your signer seeds. The token account owner must still match the authority DLMM expects for the transfer.

Token-2022 Transfer Hooks

For Token-2022 mints with transfer hooks, include the extra account metas in ctx.remaining_accounts and describe them with RemainingAccountsInfo.slices. Example ordering for swap2 with token X and token Y transfer hooks:
let remaining_accounts_info = RemainingAccountsInfo {
    slices: vec![
        RemainingAccountsSlice {
            accounts_type: AccountsType::TransferHookX,
            length: x_hook_accounts_len,
        },
        RemainingAccountsSlice {
            accounts_type: AccountsType::TransferHookY,
            length: y_hook_accounts_len,
        },
    ],
};
Then pass remaining accounts in this exact order:
  1. Token X transfer-hook accounts.
  2. Token Y transfer-hook accounts.
  3. Bin array accounts for the swap route.
For host/referral fee transfers, include TransferHookReferral when the host fee mint requires hook accounts.

Optional Accounts

Some generated clients represent optional accounts as Option<AccountInfo>. Others use an explicit placeholder account when constructing raw account metas.
Optional accountRequired when
bin_array_bitmap_extensionThe route may touch bin array indexes outside the default bitmap range.
host_fee_inA swap includes host/referral fee collection.
When using generated Anchor CPI bindings, follow the generated optional-account type. When using raw Instruction construction, compare against the TypeScript SDK or commons generated client output for the exact placeholder behavior.

Off-Chain Account Planning

Use the Typescript SDK or commons crate to plan accounts before calling your CPI program.
const swapYtoX = true
const binArrays = await pool.getBinArrayForSwap(swapYtoX, 4)
const quote = pool.swapQuote(inAmount, swapYtoX, slippageBps, binArrays, false)

const tx = await pool.swap({
  inToken,
  outToken,
  inAmount: quote.consumedInAmount,
  minOutAmount: quote.minOutAmount,
  lbPair: pool.pubkey,
  user: user.publicKey,
  binArraysPubkey: quote.binArraysPubkey,
})

const dlmmIx = tx.instructions.find((ix) =>
  ix.programId.equals(new PublicKey("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo")),
)

console.log(dlmmIx?.keys.map((key) => ({
  pubkey: key.pubkey.toBase58(),
  isSigner: key.isSigner,
  isWritable: key.isWritable,
})))
Mirror this account order in the accounts your program forwards to DLMM.

Common CPI Failures

ErrorLikely cause
InvalidRemainingAccountSliceA RemainingAccountsInfo slice type is not valid for that instruction.
InsufficientRemainingAccountsSlice lengths require more accounts than were forwarded.
DuplicatedRemainingAccountTypesThe same transfer-hook slice type was listed twice.
MissingRemainingAccountForTransferHookA Token-2022 transfer hook exists but the hook accounts were not provided.
NoTransferHookProgramTransfer-hook accounts were supplied for a mint without a transfer hook.
InvalidBinArray / NonContinuousBinArraysBin arrays are missing, out of order, or not the arrays expected for the target bin range.
BitmapExtensionAccountIsNotProvidedA route needs the bitmap extension account but the optional account was omitted.
ExceededAmountSlippageToleranceThe quoted amount is stale or slippage bounds are too tight.
PoolDisabledThe pool status or activation state does not allow the action.

Best Practices

CheckDetail
Quote before CPIQuote off-chain with the TypeScript SDK or commons, then pass explicit bounds into your program.
Forward generated orderUse generated bindings or compare against an SDK instruction. Main account order is not negotiable.
Put transfer hooks before bin arraysparse_remaining_accounts consumes hook slices first, then handlers consume leftover bin arrays.
Include event CPI accountsv2 handlers with #[event_cpi] expect generated event accounts.
Handle Token-2022 per mintToken X and token Y can use different token programs and different hook requirements.
Budget computeSwaps across many bin arrays, wide positions, claims across many bins, and Token-2022 hooks can be compute-heavy.
Avoid stale stateRe-fetch pool, bitmap extension, bin arrays, mints, and clock before quoting.
Test with real poolsSimulate on devnet and compare logs against a direct SDK-built transaction.