Skip to content

Execution

Minimum Output vs Price Limits in Uniswap v3

A 500-unit input budget can finish, partially execute or revert. Follow a checked Uniswap v3 example to distinguish output acceptance from a pool price boundary and reconcile the remainder.

An ivory pivoting scoop holds pebbles above a shallow dish, with a lime wedge stopping its tilt.
Illustration for execution; examples in this article are educational.

In Uniswap v3, amountOutMinimum is a router check on total output, while sqrtPriceLimitX96 bounds how far the pool’s price may move during a swap. Reaching the price boundary can leave input unspent. The call succeeds only if the output it produced still meets the minimum. Choose both constraints together, then reconcile the tokens actually spent, received and left over.

The distinction matters when an instruction says “spend up to this amount without moving the pool beyond this price.” A price boundary can preserve that limit while leaving the spending plan unfinished. An output floor can then accept or reject the partial result.

This article follows exactInputSingle in Uniswap’s v3-periphery v1.3.0 SwapRouter, alongside v3-core v1.0.0. These are named implementations for an educational comparison. Match your actual router and interface before applying their behavior elsewhere.

Where each check runs

The released v3 single-swap interface places the input amount, minimum output and square-root price limit in one parameter structure. They answer different questions.

ControlUnitQuestion it answers
amountInRaw input-token amountWhat input amount is submitted to the exact-input swap?
amountOutMinimumRaw output-token amountIs the returned total output sufficient?
sqrtPriceLimitX96Encoded pool square-root priceHow far may the pool price move in this direction?

The router invokes the pool and receives the resulting output amount. It then checks whether that amount is at least amountOutMinimum; otherwise the swap call reverts. The output floor therefore judges the result of the attempted execution. It does not tell the pool where to stop along its price path. SwapRouter v1.3.0.

Inside the pool, the swap loop continues while an amount remains and the square-root price has not reached the specified limit. A reachable boundary can end that loop with input still available. That is an ordinary stopping condition in the v3-core v1.0.0 pool, rather than an automatic minimum-output failure.

Read the boundary in pool units

The encoded price uses the square root of the raw token1-to-token0 amount ratio, scaled by 2^96. It is not a percentage tolerance or a chart price pasted into an integer field. Token order and decimal scales determine how a human quote maps to it. The pool action interface defines this representation and the two swap directions.

With token0 as input and token1 as output, the encoded pool price decreases, so the boundary is a lower limit. With token1 as input, it increases, so the boundary is an upper limit. A displayed reciprocal quote reverses the human direction; it does not change the pool’s token order.

The pool also requires the limit to be strictly on the appropriate side of the starting encoded price and within its supported bounds. A boundary already passed when execution begins can therefore fail the pool’s validation, rather than produce a zero-sized successful swap. Those checks appear before the swap loop in the released pool source.

In the scoped router, supplying zero for sqrtPriceLimitX96 selects a direction-specific boundary near the protocol extreme. Zero means no custom price boundary through that router convention; it does not remove the pool’s bounds or make the output floor unnecessary. The router implementation performs that substitution before calling the pool.

One budget, three outcomes

Consider an original hypothetical pool with ordinary ERC-20 tokens A and B. A is token0, B is token1, and they have equal decimals. Define P as B per A. The trader supplies B to acquire A, so P rises.

Assume constant active liquidity L = 10,000 across the entire compared path, starting P = 1, and a 500 B gross input budget. L is the liquidity parameter in these normalized units, not dollar TVL. Use an illustrative fixed input fee of 0.30%. Ignore gas, token transfer taxes, competing transactions and integer rounding. This is a continuous teaching calculation; output floors below are whole-token display equivalents, not raw transaction parameters.

For a move from square-root price a to b, net B input is L × (b − a). A output is L × (1/a − 1/b). These within-range relationships correspond to the token deltas in SqrtPriceMath v1.0.0. The separate concentrated-liquidity path example explains what changes when active liquidity varies; here it stays fixed to isolate the two guards.

First impose an upper boundary at P = 1.050625, whose square root is 1.025. Reaching it requires 10,000 × (1.025 − 1) = 250 B of net input. The resulting output is 10,000 × (1 − 1/1.025) ≈ 243.902439 A.

With the illustrative input fee, the gross amount spent is 250 ÷ 0.997 ≈ 250.752257 B. The fee is approximately 0.752257 B, leaving 500 − 250.752257 ≈ 249.247743 B unspent. Fees are charged in the input calculation in the scoped SwapMath implementation; its actual arithmetic also rounds integer amounts.

For comparison, remove the custom boundary and spend the entire 500 B budget in the same model. Net input becomes 498.5 B. Square-root price reaches 1 + 498.5/10,000 = 1.04985, giving terminal P = 1.1021850225 and approximately 474.829738 A of output.

Hypothetical instructionAttempted A outputB input settledResult
No custom boundary; minimum 470 A474.829738500.000000Passes; budget spent
Boundary P = 1.050625; minimum 240 A243.902439250.752257Passes; input remains
Same boundary; minimum 470 A243.9024390; swap rolled backReverts; floor fails

The third row does not keep the attempted 243.902439 A as a partial acquisition. The failing router check rolls back that swap call’s asset changes. If another contract catches the failure and continues, its surrounding behavior must be inspected separately. This call-level rollback follows Solidity’s exception semantics. The Universal Router allow-revert guide examines a different execution model.

In the second row, success confirms that the floor passed, while almost half the budget remains unused. Reporting only “swap successful” would omit a material part of the outcome.

Compare average cost with the terminal price

In the first row, a 470 A floor against a fully spent 500 B budget implies a maximum gross average acquisition price of approximately 500/470 = 1.063830 B per A. The actual gross average is approximately 1.053009 B per A, which satisfies that condition.

Yet terminal pool P is about 1.102185, above both numbers. The swap traverses a curve: early input exchanges at more favorable pool prices than late input. A constraint on aggregate output therefore cannot establish the last marginal price reached.

The partial case separates these measures again. Its gross average is approximately 250.752257/243.902439 = 1.028084 B per A, while terminal P is 1.050625. That terminal pool ratio excludes the input fee. Treating it as a fee-inclusive average would misread the example.

This distinction resembles the difference between average execution and individual price permissions in market-order slippage caps. The mechanisms differ: the present example stops a continuous pool path rather than matching eligible order-book levels.

Where the unspent 249.247743 B goes

The remainder is an accounting question as well as an execution question. Two integration designs can produce the same pool swap but place the leftover tokens in different locations.

If an ordinary ERC-20 payment is pulled directly from the payer for the amount owed, the unspent portion stays with that payer. If a wrapper first transfers the full budget into itself, that wrapper can still hold the remainder after the swap. The router’s callback and PeripheryPayments v1.3.0 distinguish payment from the payer from payment using contract-held tokens.

Uniswap’s current v3 single-swap guide explicitly warns integrations using a nonzero price limit to handle unswapped tokens. Follow the actual custody path; the output recipient alone does not identify who holds the unused input.

Also distinguish unspent tokens from remaining spending permission. The ERC-20 standard defines an allowance as the amount a spender is still authorized to withdraw. A balance can remain while that permission persists. If a signature or approval is involved, the Permit2 versus ERC-2612 comparison helps identify the spender and permission scope. Neither output acceptance nor a pool price boundary substitutes for that check.

Write a receipt for the floor, boundary and remainder

A practical test should exercise both the accepted partial result and the rejected result. In this fixture, changing only the floor from 240 A to 470 A changes the outcome from a retained partial swap to a reverted call. A test that always consumes the entire budget misses the distinction.

Validation itemEvidence to retain
Match the implementationChain, target router, method and applicable interface
Preserve the quote statePool, block reference, direction, fee and observed price
Check the encoded boundaryToken order, decimals, intended human price and submitted integer
Check output acceptanceMinimum output and actual returned output in raw units
Reconcile the budgetActual input spent, input fee and unspent amount
Follow custody and permissionRemainder holder, refund behavior and residual allowance
Interpret failure scopeWhich call reverted and whether surrounding execution continued

Do not copy the single-pool parameter structure into a multihop method. In this released router, exactInput has no per-hop custom price-limit field in its interface, and the method implementation passes the zero sentinel to each internal hop. Inspect the target method before assuming it enforces the same instructions.

For the worked boundary, write the acceptance rule as “receive at least 240 A, with pool P no higher than 1.050625,” then record the separate budget result: approximately 250.752257 B spent and 249.247743 B remaining. That receipt shows both the accepted output and the capital still available.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.