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:
| Account | Why it appears |
|---|
program | The DLMM program account used by Anchor event CPI emission. |
event_authority | PDA 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.
- They consume Token-2022 transfer-hook account groups according to
RemainingAccountsInfo.slices.
- 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 type | Used by |
|---|
TransferHookX | Token X transfers in swap, add liquidity, remove liquidity, claim fee. |
TransferHookY | Token Y transfers in swap, add liquidity, remove liquidity, claim fee. |
TransferHookReward | Single reward transfer in claim_reward2. |
TransferHookMultiReward(index) | Multi-reward flows. |
TransferHookReferral | Host/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
| Instruction | Main accounts | Remaining accounts after transfer-hook slices |
|---|
swap2, swap_exact_out2, swap_with_price_impact2 | lb_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_liquidity2 | position, 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_liquidity2 | position, 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_fee2 | lb_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_reward2 | lb_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_liquidity | Position, 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:
- Token X transfer-hook accounts.
- Token Y transfer-hook accounts.
- 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 account | Required when |
|---|
bin_array_bitmap_extension | The route may touch bin array indexes outside the default bitmap range. |
host_fee_in | A 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
| Error | Likely cause |
|---|
InvalidRemainingAccountSlice | A RemainingAccountsInfo slice type is not valid for that instruction. |
InsufficientRemainingAccounts | Slice lengths require more accounts than were forwarded. |
DuplicatedRemainingAccountTypes | The same transfer-hook slice type was listed twice. |
MissingRemainingAccountForTransferHook | A Token-2022 transfer hook exists but the hook accounts were not provided. |
NoTransferHookProgram | Transfer-hook accounts were supplied for a mint without a transfer hook. |
InvalidBinArray / NonContinuousBinArrays | Bin arrays are missing, out of order, or not the arrays expected for the target bin range. |
BitmapExtensionAccountIsNotProvided | A route needs the bitmap extension account but the optional account was omitted. |
ExceededAmountSlippageTolerance | The quoted amount is stale or slippage bounds are too tight. |
PoolDisabled | The pool status or activation state does not allow the action. |
Best Practices
| Check | Detail |
|---|
| Quote before CPI | Quote off-chain with the TypeScript SDK or commons, then pass explicit bounds into your program. |
| Forward generated order | Use generated bindings or compare against an SDK instruction. Main account order is not negotiable. |
| Put transfer hooks before bin arrays | parse_remaining_accounts consumes hook slices first, then handlers consume leftover bin arrays. |
| Include event CPI accounts | v2 handlers with #[event_cpi] expect generated event accounts. |
| Handle Token-2022 per mint | Token X and token Y can use different token programs and different hook requirements. |
| Budget compute | Swaps across many bin arrays, wide positions, claims across many bins, and Token-2022 hooks can be compute-heavy. |
| Avoid stale state | Re-fetch pool, bitmap extension, bin arrays, mints, and clock before quoting. |
| Test with real pools | Simulate on devnet and compare logs against a direct SDK-built transaction. |