fix(uniswap): CLMM executeSwap reverts "Too little received" — sqrtPriceLimitX96 set to average execution price - #675
Merged
Conversation
…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 SummaryThis PR fixes Uniswap CLMM swaps that could stop at the quote’s average execution price and subsequently revert. Major changes:
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| 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]
Reviews (2): Last reviewed commit: "Merge branch 'development' into fix/unis..." | Re-trigger Greptile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
POST /connectors/uniswap/clmm/execute-swapencodes the quote's average execution price assqrtPriceLimitX96: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
amountOutMinimumcheck, 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 theslippagePctthe caller passes (slippage only widensamountOutMinimum, never the price limit).Repro (mainnet, USDM1/USDC 0.01% pool
0x6f161ad0e297ecb9d1b33c048272ccc964cb4b6a)priceImpactPct: 0.065,slippagePct: 1→ reverted twice on-chain with "Too little received": 0x4a3de6b2, 0x8fe6a647sqrtPriceLimitX96corresponds to price 1.043903 = exactly the quote's average execution price, −6.5 bps from spot (= the quoted price impact, no buffer).eth_callwith the identical calldata butsqrtPriceLimitX96 = 0fills completely at the quoted 924.96 USDC.Fix
Pass
sqrtPriceLimitX96: '0'(no limit). Slippage protection already comes fromamountOutMinimum/amountInMaximum, which this route computes from the caller'sslippagePct— the same convention the Universal Router path in this repo already uses (universal-router.tspassessqrtPriceLimitX96: 0). Also removes the now-unusedencodeSqrtRatioX96import and a strayimport { re } from 'mathjs'.Testing
pnpm typecheckandpnpm lintpass.jest test/connectors/uniswap: 39/39 tests pass, unchanged from pristinedevelopment. (5 suites fail to load on pristinedevelopmenttoo — pre-existingdist/src/templates/root.ymlENOENT in the test env, unrelated.)🤖 Generated with Claude Code
https://claude.ai/code/session_01JNCRsj5xiEafcCYebDdUmz