Skip to main content
If you only need accurate swap-quote math — no RPC fetching, no transaction building, no TypeScript — picon-dlmm-quote is the crate to reach for directly. It’s what actually powers Pool.quoteExactIn/quoteExactOut inside @picon-finance/dlmm-sdk, compiled to WebAssembly for that use case, but it’s a standalone, dependency-free Rust crate with zero coupling to the on-chain program — it takes already-decoded plain values in and returns a quote result out. Account decoding is entirely out of scope for it.
This is a deliberate duplication of the on-chain swap math, not an oversight — the on-chain program and an off-chain quote library have different pressures (compute-unit budget, zero-copy account layout, deployed-binary immutability vs. WASM portability), so coupling them with a dependency edge would make a change on either side a potential blast-radius change on the other.

When to use this instead of the full SDK

  • You’re integrating from native Rust (a router, an aggregator backend, an on-chain program that wants to simulate a quote) and don’t want a WASM boundary at all.
  • You’re integrating from a non-TypeScript, non-Rust environment where WASM is your only practical option, and you don’t need the SDK’s RPC/state-management/instruction-building layer — just the math.
  • You already manage your own account fetching/caching (e.g. inside a router that batches account fetches across many venues) and want to plug quoting directly into that, without the SDK’s own Pool/Position entity classes imposing their own caching model on top.

The core function

quote_exact_out is a fully separate, sibling function with the equivalent input/output shape mirrored for the opposite fixed side — not a flag on quote_exact_in, since the per-bin math genuinely branches on which side is fixed. Internally, both replicate the exact same bin-by-bin dynamic-fee decay-then-accumulate state machine the on-chain program runs (see Fees) — fee_rate_min/fee_rate_max exist specifically because a swap crossing many bins prices later bins at a higher dynamic-fee rate than earlier ones, so a single scalar fee rate would understate what a large trade actually pays on its later portion. pre_active_bin_id/post_active_bin_id let a caller compute price impact against the exact active bin a specific quote was taken against, even if the caller’s own live state has since moved — the quote carries its own bin-id snapshot rather than assuming the caller froze state at quote time.

Bounded reach, same as the real swap

bin_arrays is effectively capped by how many accounts you supply — the same MAX_BIN_ARRAYS_PER_TRAVERSAL (6) the real swap_exact_in instruction enforces. A quote that can’t fill within the supplied arrays returns QuoteError::InsufficientLiquidity, signaling the trade needs to be split across on-chain transactions — this is a correct, expected signal, not a bug to special-case away.

Feature flags: native vs. WASM

  • default (native): Bin/BinArray derive bytemuck::Pod/Zeroable and are #[repr(C)] — laid out for zero-copy reinterpretation of raw account bytes, if you’re decoding directly from a fetched AccountInfo in Rust.
  • wasm: swaps those derives for serde/tsify, so the same structs cross a WASM boundary as plain JS objects — this is the mode the TypeScript SDK’s build uses.

Errors

Implements std::error::Error natively. Under the wasm feature, it converts to a real thrown JS Error rather than surfacing as a silent undefined or a panic.
There is currently no automated cross-check against the on-chain program’s own test vectors — the manual-drift risk between the two implementations (on-chain vs. this crate) is accepted, not eliminated. If your integration is high-value, budget for your own verification against real on-chain execution — see Aggregators and routers for why this matters in practice.