Skip to content

swapQuoteExactOut reports stale startPrice when active bin has zero matching liquidity #288

Description

@maa105

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions