Summary
swapQuoteExactOut (in ts-client/src/dlmm/index.ts) initializes startBinId = activeId before the bin walk and never updates it, even when the active bin contributes zero output. The startPrice (and therefore priceImpact) returned to the caller can correspond to a bin where no liquidity was actually consumed.
The asymmetric swapQuote (ExactIn) handles this correctly — it tracks startBin and only assigns it when a bin contributes a non-zero amountIn.
Why it matters now
Before lb_clmm 0.12.0, the active bin was guaranteed to hold MM liquidity, so startBinId = activeId was always accurate — the bug was latent. With limit orders introduced in 0.12, the active bin can now legitimately contribute zero output (e.g. drained MM + wrong-side limit orders), causing the outer loop to advance activeId without filling anything from the active bin. The reported startPrice then corresponds to a bin that contributed nothing.
Impact is limited to price / priceImpact reporting — actualInAmount / outAmount are correct.
Reproduction
A pool where:
parameters.functionType === LimitOrder (or Undetermined resolving to LO)
- The active bin has
amountX == 0 and amountY == 0
- The active bin has limit orders on the opposite side of the requested swap (i.e.
swapForY=true and limit_order_ask_side=true, or vice versa)
Then swapQuoteExactOut will walk past the active bin without consuming anything, but startPrice is still reported as getPriceOfBinByBinId(activeId, binStep).
Suggested fix
Mirror the swapQuote (ExactIn) pattern — track the first bin that actually contributes output:
```ts
// in swapQuoteExactOut, before the loop:
let startBinId: BN | null = null;
// inside the `if (!amountOut.isZero())` block:
if (!startBinId) {
startBinId = activeId;
}
// after the loop, throw if still null (insufficient liquidity edge case):
if (!startBinId) {
throw new DlmmSdkError("SWAP_QUOTE_INSUFFICIENT_LIQUIDITY", "...");
}
const startPrice = getPriceOfBinByBinId(startBinId.toNumber(), this.lbPair.binStep);
```
Reference
Bug location: ts-client/src/dlmm/index.ts#L5049 (commit `fe79f5fa` on `sync/release-0.12.0`)
Working pattern in the same file: swapQuote ExactIn variant, `let startBin: Bin | null = null` initialized then assigned inside `if (!amountIn.isZero())`.
Encountered while porting the new limit-order quote math.
Summary
swapQuoteExactOut(ints-client/src/dlmm/index.ts) initializesstartBinId = activeIdbefore the bin walk and never updates it, even when the active bin contributes zero output. ThestartPrice(and thereforepriceImpact) returned to the caller can correspond to a bin where no liquidity was actually consumed.The asymmetric
swapQuote(ExactIn) handles this correctly — it tracksstartBinand only assigns it when a bin contributes a non-zeroamountIn.Why it matters now
Before lb_clmm 0.12.0, the active bin was guaranteed to hold MM liquidity, so
startBinId = activeIdwas always accurate — the bug was latent. With limit orders introduced in 0.12, the active bin can now legitimately contribute zero output (e.g. drained MM + wrong-side limit orders), causing the outer loop to advanceactiveIdwithout filling anything from the active bin. The reportedstartPricethen corresponds to a bin that contributed nothing.Impact is limited to
price/priceImpactreporting —actualInAmount/outAmountare correct.Reproduction
A pool where:
parameters.functionType === LimitOrder(orUndeterminedresolving to LO)amountX == 0andamountY == 0swapForY=trueandlimit_order_ask_side=true, or vice versa)Then
swapQuoteExactOutwill walk past the active bin without consuming anything, butstartPriceis still reported asgetPriceOfBinByBinId(activeId, binStep).Suggested fix
Mirror the
swapQuote(ExactIn) pattern — track the first bin that actually contributes output:```ts
// in swapQuoteExactOut, before the loop:
let startBinId: BN | null = null;
// inside the `if (!amountOut.isZero())` block:
if (!startBinId) {
startBinId = activeId;
}
// after the loop, throw if still null (insufficient liquidity edge case):
if (!startBinId) {
throw new DlmmSdkError("SWAP_QUOTE_INSUFFICIENT_LIQUIDITY", "...");
}
const startPrice = getPriceOfBinByBinId(startBinId.toNumber(), this.lbPair.binStep);
```
Reference
Bug location: ts-client/src/dlmm/index.ts#L5049 (commit `fe79f5fa` on `sync/release-0.12.0`)
Working pattern in the same file:
swapQuoteExactIn variant, `let startBin: Bin | null = null` initialized then assigned inside `if (!amountIn.isZero())`.Encountered while porting the new limit-order quote math.