Skip to content

fix(uniswap): CLMM executeSwap reverts "Too little received" — sqrtPriceLimitX96 set to average execution price - #675

Merged
fengtality merged 2 commits into
developmentfrom
fix/uniswap-clmm-sqrt-price-limit
Aug 11, 2026
Merged

fix(uniswap): CLMM executeSwap reverts "Too little received" — sqrtPriceLimitX96 set to average execution price#675
fengtality merged 2 commits into
developmentfrom
fix/uniswap-clmm-sqrt-price-limit

Conversation

@fengtality

Copy link
Copy Markdown
Contributor

Problem

POST /connectors/uniswap/clmm/execute-swap encodes the quote's average execution price as sqrtPriceLimitX96:

sqrtPriceLimitX96: encodeSqrtRatioX96(
  quote.trade.executionPrice.numerator,
  quote.trade.executionPrice.denominator,
).toString(),

During any swap, the pool's marginal price crosses the trade's own average price partway through the fill. SwapRouter02 stops consuming input when the limit is reached (a price limit is a partial-fill boundary, not a revert), the partial output then fails the amountOutMinimum check, and the transaction reverts with "Too little received" — burning the gas. This is near-guaranteed for any swap with more than ~a tick of price impact, regardless of the slippagePct the caller passes (slippage only widens amountOutMinimum, never the price limit).

Repro (mainnet, USDM1/USDC 0.01% pool 0x6f161ad0e297ecb9d1b33c048272ccc964cb4b6a)

  • SELL 886 USDM1, quoted 924.96 USDC out, priceImpactPct: 0.065, slippagePct: 1 → reverted twice on-chain with "Too little received": 0x4a3de6b2, 0x8fe6a647
  • Decoding the failed calldata: sqrtPriceLimitX96 corresponds to price 1.043903 = exactly the quote's average execution price, −6.5 bps from spot (= the quoted price impact, no buffer).
  • eth_call with the identical calldata but sqrtPriceLimitX96 = 0 fills completely at the quoted 924.96 USDC.
  • After this patch the same 886-token SELL executed successfully on the first attempt: 0x2636c0a0, out 924.963937 USDC (matching the quote).

Fix

Pass sqrtPriceLimitX96: '0' (no limit). Slippage protection already comes from amountOutMinimum / amountInMaximum, which this route computes from the caller's slippagePct — the same convention the Universal Router path in this repo already uses (universal-router.ts passes sqrtPriceLimitX96: 0). Also removes the now-unused encodeSqrtRatioX96 import and a stray import { re } from 'mathjs'.

Testing

  • pnpm typecheck and pnpm lint pass.
  • jest test/connectors/uniswap: 39/39 tests pass, unchanged from pristine development. (5 suites fail to load on pristine development too — pre-existing dist/src/templates/root.yml ENOENT in the test env, unrelated.)
  • Live mainnet verification above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JNCRsj5xiEafcCYebDdUmz

…eSwap

The CLMM execute-swap route encoded the quote's *average* execution price
as sqrtPriceLimitX96. The pool's marginal price crosses the trade's own
average partway through any swap, so the router stops consuming input at
the limit and the partial output fails the amountOutMinimum check —
reverting "Too little received". This is near-guaranteed for any swap
with more than ~a tick of price impact and wastes the reverted tx's gas.

Verified on mainnet against the USDM1/USDC 0.01% pool: an 886-token SELL
reverted twice with the limit set (decoded limit == the quote's average
execution price, −6.5 bps from spot), while the identical calldata with
sqrtPriceLimitX96=0 fills completely at the quoted amount.

Pass 0 (no limit) instead — slippage protection already comes from
amountOutMinimum / amountInMaximum, the same convention the Universal
Router path uses. Also drop the now-unused encodeSqrtRatioX96 and stray
mathjs imports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JNCRsj5xiEafcCYebDdUmz
@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes Uniswap CLMM swaps that could stop at the quote’s average execution price and subsequently revert. Major changes:

  • Sets sqrtPriceLimitX96 to zero so the router can complete the quoted swap.
  • Retains slippage protection through amountOutMinimum for exact-input swaps and amountInMaximum for exact-output swaps.
  • Removes imports made unused by the fix.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/connectors/uniswap/clmm-routes/executeSwap.ts Replaces the average-price swap boundary with the router’s no-limit value while preserving the existing amount-based slippage guards.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CLMM swap request] --> B[Generate quote with slippage tolerance]
    B --> C{Swap side}
    C -->|Exact input| D[Set amountOutMinimum]
    C -->|Exact output| E[Set amountInMaximum]
    D --> F[Execute with sqrtPriceLimitX96 = 0]
    E --> F
    F --> G[Router enforces amount-based slippage bound]
Loading

Reviews (2): Last reviewed commit: "Merge branch 'development' into fix/unis..." | Re-trigger Greptile

@fengtality
fengtality merged commit 19ed6f8 into development Aug 11, 2026
5 checks passed
@fengtality
fengtality deleted the fix/uniswap-clmm-sqrt-price-limit branch August 11, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant