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 the commons crate when you need Rust tooling around DLMM.

Install

If your project is in the same workspace as dlmm-sdk, depend on the local crate by path:
[dependencies]
commons = { path = "../dlmm-sdk/commons" }
The crate uses the generated Anchor client module from the DLMM IDL:
use commons::dlmm;
use commons::dlmm::accounts::{BinArray, BinArrayBitmapExtension, LbPair};
use commons::{derive_bin_array_pda, quote_exact_in, BinArrayExtension, LbPairExtension};

What It Provides

ModuleMain exportsUse
dlmmGenerated accounts, instructions, args, and types from the DLMM IDL.Build off-chain instructions or decode IDL account layouts.
quotequote_exact_in, quote_exact_out, get_bin_array_pubkeys_for_swap.Quote swaps in Rust before building a transaction.
pdaderive_lb_pair_with_preset_parameter_key, derive_bin_array_pda, derive_position_pda, derive_reserve_pda, and related helpers.Derive DLMM PDAs without duplicating seed logic.
extensionsLbPairExtension, BinArrayExtension, BinExtension, PositionExtension, DynamicPosition, LimitOrderExtension.Work with decoded account data using higher-level methods.
token_2022Transfer-fee math and transfer-hook account helpers.Build Token-2022-aware quotes and remaining account lists.
account_filtersposition_filter_by_wallet_and_pair, limit_order_filter_by_owner_and_pair.Query user positions and limit orders with memcmp filters.
rpc_client_extensionRpcClientExtension.Fetch and deserialize accounts with one async helper.

Program ID

The generated dlmm module points at the public DLMM program:
use commons::dlmm;

let program_id = dlmm::ID;
Mainnet and devnet use the same public program ID:
pub const DLMM_PROGRAM_ID: &str = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo";

Account Decoding

The generated commons::dlmm module exposes IDL account types such as LbPair, BinArray, BinArrayBitmapExtension, PositionV2, and LimitOrder. Use pod_read_unaligned_skip_disc for zero-copy account layouts that start with the Anchor 8-byte discriminator:
use commons::dlmm::accounts::LbPair;
use commons::pod_read_unaligned_skip_disc;
use solana_sdk::account::Account;

fn decode_lb_pair(account: &Account) -> anyhow::Result<LbPair> {
    pod_read_unaligned_skip_disc::<LbPair>(&account.data)
}
The helper checks that the account data is long enough, skips the discriminator, and reads exactly size_of::<T>() bytes.

PDA Helpers

Use commons::pda helpers instead of duplicating DLMM seeds:
use commons::{
    derive_bin_array_pda, derive_bin_array_bitmap_extension, derive_event_authority_pda,
    derive_oracle_pda, derive_position_pda, derive_reserve_pda,
};
use solana_sdk::pubkey::Pubkey;

let lb_pair = Pubkey::new_unique();
let owner = Pubkey::new_unique();
let token_x_mint = Pubkey::new_unique();

let (oracle, _) = derive_oracle_pda(lb_pair);
let (bitmap_extension, _) = derive_bin_array_bitmap_extension(lb_pair);
let (bin_array, _) = derive_bin_array_pda(lb_pair, 0);
let (reserve_x, _) = derive_reserve_pda(token_x_mint, lb_pair);
let (position, _) = derive_position_pda(lb_pair, owner, -34, 69);
let (event_authority, _) = derive_event_authority_pda();
Common derivation helpers:
HelperDerives
derive_lb_pair_with_preset_parameter_keyCurrent v2 permissionless pool PDA using PresetParameter2.
derive_lb_pair_pda2Legacy-style pool PDA from mints, bin_step, and base_factor.
derive_customizable_permissionless_lb_pairCustomizable permissionless pool PDA.
derive_permission_lb_pair_pdaPermissioned pool PDA.
derive_position_pdaPosition PDA from pool, base, lower bin ID, and width.
derive_bin_array_pdaBin array PDA by pool and bin array index.
derive_bin_array_bitmap_extensionBitmap extension PDA for out-of-default-range bin arrays.
derive_reserve_pdaPool reserve PDA for a token mint.
derive_reward_vault_pdaReward vault PDA for a reward index.
derive_token_badge_pdaToken badge PDA for Token-2022 pool creation.

Bin Arrays

DLMM liquidity is organized in bin arrays. BinArrayExtension gives you the same index and coverage helpers used by the quote code:
use commons::BinArrayExtension;
use commons::dlmm::accounts::BinArray;

let bin_array_index = BinArray::bin_id_to_bin_array_index(active_bin_id)?;
let (lower_bin_id, upper_bin_id) =
    BinArray::get_bin_array_lower_upper_bin_id(bin_array_index)?;

let account_metas =
    BinArray::get_bin_array_account_metas_coverage(lower_bin_id, upper_bin_id, lb_pair)?;
For swaps, use get_bin_array_pubkeys_for_swap. It walks the pool bitmap and, when supplied, the bitmap extension:
use commons::quote::get_bin_array_pubkeys_for_swap;

let swap_for_y = true;
let bin_arrays = get_bin_array_pubkeys_for_swap(
    lb_pair_pubkey,
    &lb_pair_state,
    bitmap_extension.as_ref(),
    swap_for_y,
    3,
)?;
swap_for_y = true means token X is swapped for token Y. swap_for_y = false means token Y is swapped for token X.

Swap Quotes

The quote functions simulate DLMM swap execution against decoded account state. They support:
  • Current pool status and activation checks.
  • Base and variable fee updates.
  • Bin traversal and bitmap extension lookup.
  • Limit-order liquidity when the pool supports it.
  • Token-2022 transfer fees on input and output mints.

Exact-In

use commons::quote::quote_exact_in;
use std::collections::HashMap;

let quote = quote_exact_in(
    lb_pair_pubkey,
    &lb_pair_state,
    amount_in,
    swap_for_y,
    bin_arrays_by_pubkey,
    bitmap_extension.as_ref(),
    &clock,
    &mint_x_account,
    &mint_y_account,
)?;

println!(
    "amount_out={} fee={} protocol_fee={}",
    quote.amount_out, quote.fee, quote.protocol_fee
);

Exact-Out

use commons::quote::quote_exact_out;

let quote = quote_exact_out(
    lb_pair_pubkey,
    &lb_pair_state,
    amount_out,
    swap_for_y,
    bin_arrays_by_pubkey,
    bitmap_extension.as_ref(),
    &clock,
    &mint_x_account,
    &mint_y_account,
)?;

println!(
    "amount_in={} fee={} protocol_fee={}",
    quote.amount_in, quote.fee, quote.protocol_fee
);
bin_arrays_by_pubkey is a HashMap<Pubkey, BinArray> keyed by the bin array accounts you fetched for the route. If the quote returns Active bin array not found or Pool out of liquidity, fetch more bin arrays in the swap direction.

Building Swap Instructions

The generated dlmm::client module can build account metas and instruction data. The integration tests in commons/tests/integration/test_swap.rs show the full pattern.
use anchor_lang::InstructionData;
use anchor_lang::ToAccountMetas;
use commons::dlmm;
use commons::dlmm::types::RemainingAccountsInfo;
use commons::derive_event_authority_pda;
use solana_sdk::instruction::{AccountMeta, Instruction};

let (event_authority, _) = derive_event_authority_pda();

let mut accounts = dlmm::client::accounts::Swap2 {
    lb_pair,
    oracle,
    bin_array_bitmap_extension: Some(bitmap_extension),
    reserve_x,
    reserve_y,
    user_token_in,
    user_token_out,
    token_x_mint,
    token_y_mint,
    host_fee_in: None,
    user,
    token_x_program,
    token_y_program,
    program: dlmm::ID,
    event_authority,
    memo_program: spl_memo::ID,
}
.to_account_metas(None);

accounts.extend(bin_array_pubkeys.into_iter().map(|pubkey| {
    AccountMeta::new(pubkey, false)
}));

let ix = Instruction {
    program_id: dlmm::ID,
    accounts,
    data: dlmm::client::args::Swap2 {
        amount_in,
        min_amount_out,
        remaining_accounts_info: RemainingAccountsInfo { slices: vec![] },
    }
    .data(),
};
For Token-2022 transfer hooks, populate remaining_accounts_info and append the extra account metas described in the next section.

Token-2022

commons::token_2022 handles two separate concerns:
HelperUse
get_epoch_transfer_feeRead the active transfer-fee config for a mint account and epoch.
calculate_transfer_fee_excluded_amountConvert a transfer-fee-included amount into the amount that reaches the receiver.
calculate_transfer_fee_included_amountCompute the pre-fee amount required for a target post-fee amount.
get_extra_account_metas_for_transfer_hookFetch transfer-hook extra account metas for one mint.
get_potential_token_2022_related_ix_data_and_accountsBuild RemainingAccountsSlice data and account metas for liquidity or reward actions.
For v2 instructions that may transfer Token-2022 mints, pass the extra account metas after the main accounts and encode the matching RemainingAccountsInfo:
use commons::{
    get_potential_token_2022_related_ix_data_and_accounts, ActionType,
};
use commons::dlmm::types::RemainingAccountsInfo;

let token_2022_accounts =
    get_potential_token_2022_related_ix_data_and_accounts(
        &lb_pair_state,
        rpc_client,
        ActionType::Liquidity,
    )
    .await?;

let remaining_accounts_info = if let Some((slices, metas)) = token_2022_accounts {
    accounts.extend(metas);
    RemainingAccountsInfo { slices }
} else {
    RemainingAccountsInfo { slices: vec![] }
};
Use ActionType::Reward(index) when building remaining accounts for a reward claim or reward-aware flow.

Position And Limit Order Reads

Use DynamicPosition::parse when you need a backend-friendly view of a position. It combines the base PositionV2 account, dynamic extension bytes, pool state, bin arrays, fee checkpoints, and reward checkpoints.
use commons::dlmm::accounts::PositionV2;
use commons::{pod_read_unaligned_skip_disc, DynamicPosition};
use std::collections::HashMap;

let position = pod_read_unaligned_skip_disc::<PositionV2>(&position_account.data)?;
let parsed = DynamicPosition::parse(
    &position,
    &position_account.data,
    &lb_pair_state,
    &bin_arrays_by_index,
    current_timestamp,
)?;

println!(
    "x={} y={} fee_x={} fee_y={}",
    parsed.total_x_amount, parsed.total_y_amount, parsed.fee_x, parsed.fee_y
);
Use account filters when scanning positions or limit orders:
use commons::{limit_order_filter_by_owner_and_pair, position_filter_by_wallet_and_pair};

let position_filters = position_filter_by_wallet_and_pair(owner, lb_pair);
let limit_order_filters = limit_order_filter_by_owner_and_pair(owner, lb_pair);

Testing

The commons crate includes integration tests that load DLMM program fixtures and serialized account data into solana-program-test:
TestWhat it covers
tests/integration/test_swap.rsExact-in and exact-out swap quotes and generated Swap2 / SwapExactOut2 instructions.
tests/integration/test_swap_token2022.rsToken-2022 swap behavior.
tests/integration/test_swap_quote_with_limit_order.rsQuote behavior when limit order liquidity is part of the pool.
Run the commons tests from the SDK repository:
cargo test -p commons --test integration

Best Practices

PracticeDetail
Fetch fresh state before quotingQuotes depend on active_id, fees, bin liquidity, limit orders, token transfer fees, and the current clock.
Fetch enough bin arraysget_bin_array_pubkeys_for_swap takes a count. Larger swaps may need more arrays, but every extra array increases account and compute pressure.
Include bitmap extension when neededPass Some(&bitmap_extension) when route discovery can leave the default bitmap range.
Keep slippage explicitUse quote.amount_out or quote.amount_in to derive user-facing min_amount_out or max_in_amount; do not send unbounded swaps.
Treat Token-2022 separatelyTransfer fees change quoted amounts, and transfer hooks require extra account metas and matching RemainingAccountsInfo.
Compare with TypeScript SDK outputWhen in doubt, build the same transaction with @meteora-ag/dlmm and compare account order, remaining accounts, and instruction data.