swap_exact_in/swap_exact_out instruction on the router’s behalf. This page covers the
integration-specific pitfalls that aren’t obvious from the instruction reference alone.
Off-chain: decode via the IDL, not hand-mirrored structs
Don’t hand-mirror thePool/BinArray/instruction-argument structs as local Borsh types — a
future field reorder on-chain would silently break decoding instead of failing to compile. Use
anchor_lang::declare_program! against the published IDL instead: this generates real typed
structs with correct discriminators and field layouts straight from the same IDL published on
mainnet, so a schema mismatch becomes a compile error.
BinArray/Bin specifically are #[repr(C)] zero-copy accounts on-chain, so a direct
bytemuck::try_pod_read_unaligned reinterpret of the raw account bytes is sound and fast for
those two types. Pool is not laid out identically to
picon-dlmm-quote’s PoolState (different field
sets, not #[repr(C)] on either side), so converting between them needs an explicit
field-by-field copy, not a cast.
Fetch the right bin-array window, not a symmetric one
This is the single easiest mistake to make when integrating a router venue.MAX_BIN_ARRAYS_PER_TRAVERSAL (6) is the cap per swap direction, not “arrays fetched on
each side of the active bin.” Fetching active ± 6 (13 arrays total) and handing that whole
window to a single-direction swap instruction overshoots the on-chain cap by one and fails with
InvalidBinArray.
The correct window, matching the SDK’s own generateBinArrayIndicesForSwap: each direction’s
window is the active array itself, plus up to 4 arrays behind it on the deep-traversal side,
plus exactly 1 ahead of it as a drift margin — asymmetric per direction. Fetch the union of
both directions once per refresh (9 arrays total), then slice down to the exact
MAX_BIN_ARRAYS_PER_TRAVERSAL-sized directional window before quoting or building an
instruction for a specific direction.
Account ordering
The swap instruction’s token-account and token-program pairs are always in X/Y order on-chain, never input/output order. A router that places “input mint’s account” at the X slot happens to work forx_to_y swaps (where input coincidentally is X) and fails outright for
y_to_x swaps.
Transfer hooks: resolve them yourself outside the SDK
Picon DLMM’s on-chain program fully supports Token-2022 transfer-hook mints in the swap instruction — but if you’re hand-building the instruction outside the SDK (which resolves this automatically), you need to resolve the hook’sExtraAccountMetaList yourself and append the
resulting accounts to the TransferHookX/TransferHookY remaining-accounts group. The
spl-transfer-hook-interface/spl-tlv-account-resolution crates do this natively in Rust; the
TypeScript SDK’s own hand-rolled resolver (resolveTransferHookAccountsX/
resolveTransferHookAccountsY, written because @solana/kit doesn’t ship one either) is a
working reference if you’re porting the logic elsewhere.