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/Positionentity 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/BinArrayderivebytemuck::Pod/Zeroableand are#[repr(C)]— laid out for zero-copy reinterpretation of raw account bytes, if you’re decoding directly from a fetchedAccountInfoin Rust.wasm: swaps those derives forserde/tsify, so the same structs cross a WASM boundary as plain JS objects — this is the mode the TypeScript SDK’s build uses.
Errors
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.