Users get worse swap prices than quoted
“The UI quoted one price, but the swap filled noticeably worse. On the explorer, the same bot bought just before the user's transaction and sold just after, in the same block.”
THE PRINCIPLE
The minimum output is the real price you agreed to. Whatever slippage you allow, a sandwich bot will try to take.
FIRST MOVES
- Check the swap calldata for the minimum-output field (amountOutMinimum, or amountInMaximum for exact-output). Zero, or a value derived from a stale or on-chain spot quote, is an open invitation.
- Compute the minimum from a fresh off-chain quote and a small, pair-appropriate tolerance; show the user both the expected and the guaranteed amount.
- Set a real deadline from wall-clock time a few minutes ahead. block.timestamp as the deadline always passes, so a transaction left pending can fill much later at a worse price.
- Send swaps through a private transaction RPC such as Flashbots Protect, or route through an intent or RFQ system, so the order never sits in the public mempool.
- Watch for same-block buy and sell transactions around your users' swaps to measure how often it happens.
TOOLS: THEN → NOW
A fixed default slippage for every pairTolerance based on the pair's liquidity and volatility, still capped low by default
Public mempool submissionPrivate or protected RPC endpoints (Flashbots Protect and similar) that hide transactions from sandwich bots
Direct pool swapsStill common; intent-based routing (for example UniswapX or CoW Protocol) moves execution off the public mempool
PATTERN SNAPSHOT
// Off-chain quote, then a hard floor and a wall-clock deadline in the calldata.
const expectedOut = await quoteExactIn(tokenIn, tokenOut, amountIn); // fresh, off-chain
const slippageBps = 50n; // 0.5%
const amountOutMinimum = (expectedOut * (10_000n - slippageBps)) / 10_000n;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 5 * 60);
const hash = await protectedWallet.writeContract({ // wallet client on a private RPC
address: router, abi: routerAbi, functionName: "swapExactIn",
args: [tokenIn, tokenOut, amountIn, amountOutMinimum, deadline],
});CLOSE THE AI. EXPLAIN THIS.
A swap sets amountOutMinimum to 0 and deadline to block.timestamp. What exactly does each of those allow, and which one does private submission not fix?RELATED SYMPTOMS
SOURCES
Guide reviewed