Skip to main content
@meteora-ag/dlmm-pro-sdk builds DLMM Pro transactions and reads DLMM Pro accounts. It also quotes swaps with the same math as the program.

DLMM Pro SDK

Source, tests and the full function reference (docs.md).

npm Package

Published versions of @meteora-ag/dlmm-pro-sdk.

Prerequisites

The package is developed and published with Node 22, and its CI runs on it. The pages here were also run on Node 24. These are the versions package.json declares, with the versions the repository’s lockfile resolves in brackets. The installation commands below add the first three packages next to the SDK. The rest come with the SDK or are only needed for type checking and running scripts.

Installation

The SDK uses @solana/web3.js 1.x and bn.js for amounts. @solana/spl-token is only needed if you read mints yourself, as the example below does. To type check your code, also install typescript, @types/bn.js and @types/node as dev dependencies.

TypeScript Configuration

This tsconfig.json type-checks every script on these pages with TypeScript 7.0.2. Check your own copy with npx tsc --noEmit.
  • module ESNext with moduleResolution Bundler is the configuration the examples were checked with. module and moduleResolution both set to NodeNext also type-checks the .mts scripts.
  • Top-level await compiled with the target set to ES2022 and to ES2020.
  • skipLibCheck is needed. Without it tsc reports an error inside the declarations of @solana/buffer-layout, a dependency of web3.js, that has nothing to do with your code.
  • The scripts type-check without esModuleInterop, but the SDK’s own configuration sets it and import BN from "bn.js" is written with it in mind.
  • process.env needs @types/node installed and "types": ["node"] in the tsconfig.
  • The scripts use top-level await, so they must run as ES modules. A .ts file in a CommonJS package fails under tsx with Top-level await is currently not supported with the "cjs" output format. Rename it to .mts or set "type": "module" in package.json.

CommonJS and ESM

The package ships both builds. Its exports map sends import to dist/index.js and require to dist/index.cjs, and both expose the same API.
TypeScript types depend on how you resolve modules:
  • With module set to CommonJS and moduleResolution set to Bundler, a .cts file can import the package normally.
  • With module and moduleResolution set to Node16, a CommonJS TypeScript file that imports the package fails with TS1479. The package declares "type": "module" and has one set of type declarations, which TypeScript reads as an ES module, so it refuses the require call it would emit. Import the package from an .mts file instead, switch to NodeNext or Bundler, or use a dynamic import(), which type-checks under Node16 (with the same types setting as above, because it reads process.env) and runs.

Create a Client

One client covers every pool. Its services group the builders and readers: The program ID is the same on mainnet and devnet, so the same client works on both; point the connection at the cluster you want. new DlmmProClient(connection, commitment, programId) takes an optional commitment (default "confirmed") and program ID for other deployments.

Fund a Devnet Wallet and Find a Pool

To try the SDK without real funds, point RPC_URL at devnet, https://api.devnet.solana.com, and use a throwaway wallet. This script creates one and prints its address and a WALLET_SECRET_KEY line for your shell.
The public devnet endpoint no longer serves airdrops. requestAirdrop answers with a 410 error that says the RPC faucet is closed and points to https://faucet.solana.com, so request SOL there for the address. A wallet with no SOL fails the first transaction: see Troubleshooting. Then pick a pool. This script lists the pools on the cluster by their account discriminator and prints the first ten that have liquidity and have swaps and deposits enabled. The static config with index 0 exists on devnet too, so you can also create your own pool from tokens you hold.
Set POOL_ADDRESS to one of the printed addresses. A few things decide whether the quote and swap script works against it:
  • Devnet tokens are test tokens. The script sells one whole token X, so you need a balance of the pool’s token X, or pick a pool whose token X is wrapped SOL: the builder wraps the SOL this script sells. The second half of the wrapped SOL example shows a purchase paid in SOL.
  • Liquidity is thin on many test pools. When one whole token X is more than the pool can take, the script stops with Not enough liquidity for this input. Lower inputAmount.
  • extensions is a bitmask: 1 is LiquidityMining and 2 is ActivationAccess. A pool with activation rules can reject swaps until its activation point with InvalidActivationAccess.

Quote and Swap

This script reads a pool, prints its price, quotes selling one whole token X, and sends the swap with 0.5% slippage. It uses top-level await, so save it as swap.mts and run it with npx tsx swap.mts.
A few things to note:
  • quoteExactIn loads the tick arrays the swap will cross and returns them; pass them to swap unchanged. If quote.amountLeft is not zero, the loaded arrays ran out of liquidity and the swap would fail. Raise maxTickArrays or reduce the input.
  • The quote reads the fee at the current time. Scheduled and dynamic fees can change before the transaction lands, which is one more reason to leave room in minimumOutputAmount.
  • transferFeeExcludedOutAmount is what the wallet receives after any Token 2022 transfer fee on the output mint, and it is what minimumOutputAmount is compared with.

What the Script Does

The script has four steps. The names in brackets are the values each one produces.
  1. Read the pool. fetchPool returns the decoded Pool account: the two mints, their token program flags (0 is the SPL Token program and 1 is Token 2022), the current tick tickCurrentId, the tick spacing, the fee settings and the pool’s status. getTokenProgram turns a flag into the program ID that getMint needs, and getMint gives the decimals. tickUiPrice converts the tick into a UI price, 1.0001 ** tick scaled by 10 ** (decimalsX - decimalsY), so the log line prints token Y per token X. The pool’s exact price lies between this tick and the next, so the printed price is close but not exact.
  2. Quote. inputAmount is one whole token X in atoms. client.pool.quoteExactIn reads the pool, its tick arrays, the mints for transfer fees and the clock, and runs the program’s swap math. It returns quote (the fields below), tickArrays (the addresses of the tick arrays the swap crosses, in the order the program wants them) and tickArrayBitmapExtension (an address when the swap needs it, otherwise null). swapper only matters before a pool with activation rules opens, when a whitelisted wallet may swap early. By default the quote loads the first 3 tick arrays with liquidity, and maxTickArrays raises that.
  3. Check amountLeft. A non-zero value means the loaded tick arrays ran out of liquidity before the input was used up. The swap would fail with NotConsumingFullAmountIn, so the script stops.
  4. Swap. client.pool.swap builds the transaction from the accounts the quote chose. minimumOutputAmount is the quoted output less 0.5%: muln(9_950).divn(10_000) multiplies by 9,950 and divides by 10,000, rounding down. The result is an unsigned Transaction. sendAndConfirmTransaction adds a recent blockhash, signs with the wallet, sends and waits for confirmation at the connection’s commitment. It returns the signature, and the script prints it.
The swap returns no amounts. What the swap actually moved is in the wallet’s balance changes and in the program’s EvtSwap event, see Read Events From a Transaction. When amountLeft is zero and nothing changes between the quote and the swap, the amounts match the quote.

Quote Result Fields

quote is a QuoteExactInResult. Amounts are in atoms. The output of the quote is already net of the trading fee.

Choosing Slippage

minimumOutputAmount is the floor you accept for transferFeeExcludedOutAmount. If the swap would pay less when it lands, the program fails it with ExceededSlippage (6019) and no swap happens. A floor of 0 accepts any output. Take the floor from a fresh quote.
Between the quote and the landing, the result can change for these reasons:
  • Other swaps move the price and use up liquidity in your direction.
  • The dynamic fee rises with volatility, so a pool that has just moved charges more.
  • A time-based fee schedule only falls, which helps you.
  • A Token 2022 transfer fee can change at an epoch boundary.
Start from the 0.5% in the script, 50 basis points. Widen it for a thin pool or a volatile pair, and tighten it for a deep pool. A tolerance that is too tight makes the swap fail whenever another swap lands first. One that is too wide gives away price. To judge the price impact of the size you are swapping, compare quote.nextTickId with the pool’s current tick: a large gap means the input crosses many ticks, and a smaller input or a pool with more liquidity gives a better price. Quote right before you build the swap. A quote only describes the pool at the time it was read.

How Builders Work

Every builder returns { tx, ... }, where tx is an unsigned Transaction with the fee payer set and no recent blockhash. sendAndConfirmTransaction fills in the blockhash. Each builder’s reference entry lists who must sign; new NFT mints (positions and limit orders) sign too.
  • Token accounts. User token accounts default to the wallet’s associated token accounts. A missing one is created in the same transaction, paid by the fee payer.
  • Wrapped SOL. When a wallet pays or receives SOL through its default token account, the builder wraps or unwraps it in the same transaction. An account you pass explicitly is used as is.
  • Accounts the program needs. swap takes its tick arrays from quoteExactIn, position builders derive theirs from the tickRanges you pass, and limit order builders resolve their own. The bitmap extension and Token 2022 transfer hook accounts are resolved when you leave them out.
  • Compute budget. Builders add no compute budget instructions. Opening or expanding a position with many ranges can need more than the default 200,000 compute units, so add ComputeBudgetProgram.setComputeUnitLimit to those transactions, and setComputeUnitPrice when you want a priority fee.
  • Operator and admin instructions are not wrapped. Build them with client.program.methods.<instruction>(...) if you need them.

Errors

Builders check their arguments before sending and throw PoolError. Its code uses the program’s numbering, so you can handle a builder error and a failed transaction the same way.
See Program Errors for every code.

Troubleshooting

These are the failures people hit first, as they appear and what to check. See Handle Errors to map a failed transaction to a program error name, and Program Errors for every code.

Next Steps

SDK Examples

Create a pool, open a shaped position, place limit orders and run rewards.

SDK Reference

Every service method and helper, with signers and defaults.