Skip to main content

Lamport-exact Meteora DLMM swap-quote library, validated against the on-chain program.

Project description

meteora-dlmm

Price Meteora DLMM swaps in Python — exactly, down to the lamport.

Give it a pool's on-chain account bytes and it returns the same amount_out the Meteora program would: the bin-by-bin walk, the variable fee that ramps as a swap crosses bins, and on-chain limit orders. It's pure Python with no dependencies, and every quote is validated against the program's own swapQuote — a check that ships inside the package, so you can prove it yourself:

pip install meteora-dlmm
python -m meteora_dlmm.selftest
              in         sdk_out         lib_out    diff
      1000000000        79109650        79109650       0
     10000000000       790983110       790983110       0
    100000000000      7901542802      7901542802       0
   1000000000000     58859103727     58859103727       0

max |diff| = 0 lamports  ->  PASS: library reproduces the on-chain program exactly.

Why

  • It's Python. Most Solana DLMM tooling is TypeScript or Rust. If you work in Python — quant research, backtests, data pipelines — this fills the gap.
  • One fetch, then local. Decode a pool once from raw account bytes, then price as many swaps as you like in-process, with no further RPC round-trips. Good for routing, liquidation math, backtests, and dashboards.
  • No dependencies. The library is pure standard library.

Install

pip install meteora-dlmm

The RPC example additionally needs a Solana client:

pip install "meteora-dlmm[rpc]"

Quickstart

from meteora_dlmm import PoolState, quote

# bytes from getAccountInfo(pool) and getMultipleAccounts(bin_arrays)
pool = PoolState.from_accounts(lb_pair_bytes, bin_array_byte_list, decimals_x=9, decimals_y=6)

result = quote(pool, amount_in=1_000_000_000, swap_for_y=True)   # sell 1 SOL (X) for USDC (Y)
print(result.amount_out, result.bins_crossed)

swap_for_y=True spends token X (price falls); False spends token Y (price rises). quote() also takes an optional timestamp (unix seconds) for the fee's decay reference and defaults to now.

Fetching a live pool

You bring the RPC bytes; the library does the rest. With any Solana client:

from solana.rpc.api import Client
from solders.pubkey import Pubkey
from meteora_dlmm import PoolState, quote, array_index_of

rpc = Client("https://mainnet.helius-rpc.com/?api-key=YOUR_KEY")
pool_addr = Pubkey.from_string("HTvjzsfX3yU6BUodCjZ5vZkUrAxMDTrBs3CJaq43ashR")

lb_pair = rpc.get_account_info(pool_addr).value.data
# derive + fetch the BinArrays around the active bin (see the repo example for the full helper),
# then:
pool = PoolState.from_accounts(lb_pair, bin_array_bytes, decimals_x=9, decimals_y=6)
print(quote(pool, 1_000_000_000, swap_for_y=True).amount_out)

Runnable end-to-end versions, including the BinArray-fetch helper, are in examples/ in the repo.

You must fetch enough BinArrays

quote() only knows about the bins you hand it. If a swap is big enough to walk past the last BinArray you fetched, the honest answer is "I don't know" — not a smaller number. So it raises:

from meteora_dlmm import quote, InsufficientBinArrays, array_index_of

try:
    result = quote(pool, amount_in, swap_for_y=True)
except InsufficientBinArrays as e:
    # e.bin_id        - the first bin we couldn't see
    # e.remaining_in  - input still unfilled
    # e.partial       - the Quote we got before running out (amount_out is a LOWER BOUND)
    fetch_more(array_index_of(e.bin_id))   # then re-quote

Prefer a partial over an exception? Pass strict=False and check the result:

result = quote(pool, amount_in, swap_for_y=True, strict=False)
if not result.complete:
    print(f"lower bound only; {result.remaining_in} unfilled, need bin {result.missing_bin_id}")

If you fetched every BinArray the pool has (arrays exist only where liquidity was placed), there's no window to run off the end of — a swap that runs short means the pool is genuinely drained. Say so with exhaustive=True and quote() stops raising:

pool = PoolState.from_accounts(lb_pair, all_bin_arrays, 9, 6, exhaustive=True)
result = quote(pool, huge_amount, swap_for_y=True)
# result.complete is True and result.remaining_in > 0  ->  pool drained; this fill is exact

Read the two fields together:

complete remaining_in Meaning
True 0 Full fill. amount_out is exact.
True > 0 Pool drained. amount_out is exact — it's all the pool had.
False > 0 Ran out of data, not liquidity. amount_out is a lower bound. Fetch more, re-quote.

API

  • PoolState.from_accounts(lb_pair, bin_arrays, decimals_x, decimals_y, lb_pair_key=None, exhaustive=False) — decode a pool from raw bytes. Pass lb_pair_key (32 bytes) to assert the BinArrays belong to that pool. Pass exhaustive=True if bin_arrays is every array the pool has, so a short fill is reported as a drained pool rather than raising.
  • quote(pool, amount_in, swap_for_y, timestamp=None, support_limit_order=True, strict=True)Quote(amount_out, bins_crossed, complete, remaining_in, missing_bin_id). Raises InsufficientBinArrays when strict and the walk leaves the loaded bin window.
  • PoolState.is_loaded(bin_id), PoolState.loaded_bin_range(), array_index_of(bin_id) — which bins you actually hold.
  • total_fee(...), StaticParams, VariableParams — the fee model, if you want it directly.
  • DecodeError on malformed or mismatched accounts.

Accuracy & scope

python -m meteora_dlmm.selftest proves the exact match on a committed 1bp SOL/USDC capture (X→Y, full fills across the fee ramp) with no RPC key. Against real executed on-chain swaps the library scores a median 0.0001% (both directions).

Not yet backed by a committed fixture, so don't take them on trust: other bin steps and pairs (the math is bin-step-independent, so they should pass), the Y→X direction against swapQuote, and the limit-order processed_order tier. Token-2022 transfer-fee pools are out of scope — the library assumes a zero transfer fee, which is correct for SOL/USDC and most pairs but not for mints with a transfer-fee extension.

Full detail: LIMITATIONS.md, validation/README.md, CHANGELOG.md.

License

MIT. Not affiliated with Meteora.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

meteora_dlmm-0.3.0.tar.gz (276.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

meteora_dlmm-0.3.0-py3-none-any.whl (139.9 kB view details)

Uploaded Python 3

File details

Details for the file meteora_dlmm-0.3.0.tar.gz.

File metadata

  • Download URL: meteora_dlmm-0.3.0.tar.gz
  • Upload date:
  • Size: 276.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for meteora_dlmm-0.3.0.tar.gz
Algorithm Hash digest
SHA256 cfa46247e2e78ad38390403cd6a9ffcb3445ce9bc97810161135e0d8695da7a5
MD5 acb71ec6a14fb62f8ead418a7f748f3d
BLAKE2b-256 b675875ee6929e5cedb079fc491bfe6dfc46bcfe3547d92ad4efee8dffff7215

See more details on using hashes here.

File details

Details for the file meteora_dlmm-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: meteora_dlmm-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 139.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for meteora_dlmm-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e5121af28125855358501a789c04d374fe88206f96e23204471405d798c89427
MD5 ce1cc5f59da2880e85b952cb355e440b
BLAKE2b-256 6f19cd74d5981dd93ad859f3452b8db106b781aade7e74a3b070bcc4f93c1a1e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page