Metadata-Version: 2.4
Name: meteora-dlmm
Version: 0.3.0
Summary: Lamport-exact Meteora DLMM swap-quote library, validated against the on-chain program.
Author: nirkt
License: MIT
Project-URL: Homepage, https://github.com/nirkt/meteora-dlmm-py
Project-URL: Repository, https://github.com/nirkt/meteora-dlmm-py
Project-URL: Issues, https://github.com/nirkt/meteora-dlmm-py/issues
Project-URL: Changelog, https://github.com/nirkt/meteora-dlmm-py/blob/main/CHANGELOG.md
Keywords: solana,meteora,dlmm,defi,amm,quote,liquidity
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: rpc
Requires-Dist: solana>=0.30; extra == "rpc"
Dynamic: license-file

# meteora-dlmm

Price [Meteora DLMM](https://www.meteora.ag/) 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:

```bash
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

```bash
pip install meteora-dlmm
```

The RPC example additionally needs a Solana client:

```bash
pip install "meteora-dlmm[rpc]"
```

## Quickstart

```python
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:

```python
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/](https://github.com/nirkt/meteora-dlmm-py/tree/main/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:

```python
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:

```python
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:

```python
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](https://github.com/nirkt/meteora-dlmm-py/blob/main/LIMITATIONS.md),
[validation/README.md](https://github.com/nirkt/meteora-dlmm-py/blob/main/validation/README.md),
[CHANGELOG.md](https://github.com/nirkt/meteora-dlmm-py/blob/main/CHANGELOG.md).

## License

MIT. Not affiliated with Meteora.
