slippageBps and maxImpactBps are both basis points, both make a trade safer,
and both appear on the same request. They are not variants of one idea, and
setting one does not do the other’s job.
The short version: slippage protects you from other people, impact protects
you from yourself.
Written against swap-router at d88da91. Every figure below was measured on a
Base fork at blocks 50,807,273 to 50,807,275, minutes apart: the pool moves
between them, which is exactly why each measurement names its block.
Slippage never changes the trade
The same 50,000 USDC into WETH, quoted twice, at 10 bps and at 50 bps of slippage:
Five times the slippage, and the trade is identical: same input, same output,
same impact. The only number that moved is the floor.
That is the whole of it.
slippageBps is a statement about what you will
still accept, not an instruction about what to trade. It becomes
minAmountOut, which the aggregation router enforces on chain, and if the
market has moved past it by the time your transaction mines the swap reverts.
The floor is a haircut on
amountOutAfterFee: the output after the
protocol fee, not before it. A floor computed from the gross figure is one the
router cannot meet, and it would revert every swap for a reason that reads like
slippage.Impact is the cost you cause
Price impact is what your own size does to the pool as it walks the book. It is already in the quote above: that 50,000 USDC trade moves the price 0.87 bps, and no slippage setting changes that number: slippage is applied after impact has been paid.maxImpactBps is the bound on it. The service quotes your amount, and if the
impact it reports exceeds the cap it bisects the offer down until it fits,
then answers with the smaller trade. You are not refused; you are given less
than you asked for, at a better average price, and told so.
Measured at block 50,807,274 and 50,807,275, offering 450,000 USDC both times:
The cap cut the offer to 82.29% of itself and landed the impact at exactly 500
pips, the boundary, found by search rather than by scaling. The answer says so:
sizedDown is deliberately not folded into fill. A limit fill stops at a price
and carries a sqrtPriceLimitX96 to execute it; this is an ordinary swap of a
smaller amount, and reusing that shape would hand back a price limit nothing
solved for. amountIn is already the reduced figure, so the quote can be
built and sent as it stands: the block only explains why it is smaller than
what you asked for.
It searches for the largest size that fits rather than accepting the first
one that does. Impact is convex: depth is concentrated, so scaling by
cap / impact overshoots downward hard. Measured on Base: 3,000,000 USDC capped
at 5 bps came back as 237,675 by that ratio, while the true 5 bps maximum on that
pair was 2,317,948. A tenth of the volume, every step defensible.
Neither one substitutes for the other
Slippage does not stop you causing impact. A trade with 12 bps of impact and 50 bps of slippage sails through. The impact is inside the quote, so the floor is computed from the already-worsened price. You paid it, and nothing complained. A cap does not protect you from time.maxImpactBps is measured at quote
time, on a snapshot. If somebody else moves the market between your quote and
your transaction mining, the cap has nothing to say about it: only
minAmountOut stands between you and the new price.
Sending both
They compose, and the order is what makes them coherent:maxImpactBpsacts first, on the offer. The bisection settles on a smaller amount.slippageBpsthen prices that amount.minAmountOutis derived from the quote of the reduced trade, not of the one you asked for.
InsufficientOutput, and it is the failure this ordering exists to prevent.
The same 450,000 USDC offer, capped at 5 bps, priced at two tolerances: block
50,807,275 for both:
Identical trades. The cap decided the size and slippage decided nothing but the
floor, which is the whole page in one pair of rows.
limitRate with slippageBps is a different story and is refused: with a
limit the floor is the caller’s own rate, so a tolerance would be a second and
conflicting guarantee. slippageBps with maxImpactBps has no such conflict,
which is exactly why it is allowed.
Read impact in pips, not bps
priceImpactBps is truncated. On the trade above it reads 0 while
priceImpactPips reads 87: 0.87 bps.
Comparing a cap against the truncated figure is wrong by up to a whole basis
point, which is 2% of a 50 bps cap and 100% of a 1 bps cap, and
maxImpactBps: 1 is precisely the request “fill me at near-zero impact”. The
service enforces the cap in pips for that reason. Show pips to anybody who has
to act on the number.
The confusion is not only conceptual. In the service,
max_price_impact_bps and
max_slippage_bps were adjacent u32 parameters until they were bundled into a
struct, because a caller could transpose them and no compiler would notice.Where costBps fits
A third bound, and a third question, see Bounded execution.
costBps also bounds the size, like maxImpactBps, but on a different basis: it
is the marginal rate the book is walked to, over direct pools, rather than
the average the quote reports over any route. Because a trade stopped at a
marginal rate delivers an average about half as far from spot, costBps: 25 is
roughly maxImpactBps: 10, not 25.
Does capping mean trading in pieces?
Capping impact makes each trade smaller, which raises the obvious next question: if you then send the rest, have you saved anything? Sent immediately, no, and the per-slice impact figures make it look as though you have. That question has its own page: Does splitting cost less?.In a plan
Aswap.router step names them as max_slippage and max_impact_bps, both in
basis points:
max_slippage is required when the step states no price of its own: the
minimum has to come from what the venue would pay, and deriving it from the
input amount is meaningless across two tokens with different decimals. State a
limit_price instead and it becomes the floor, at which point max_slippage
has nothing left to do.