Commit-Reveal, Batching, and Other On-Chain Defenses
The MEV mitigations described in this section live at the contract design layer. They do not depend on user behavior or wallet choice; they make the protocol structurally resistant to certain MEV patterns.
Commit-Reveal Schemes
The core idea: a user commits to an action by submitting a hash of their intent in transaction 1, then reveals the actual intent in transaction 2 (in a later block). Because the commit-stage transaction reveals nothing about the user's intent, searchers cannot front-run it.
Minimal sketch:
struct Commit {
bytes32 hash;
uint256 blockNumber;
address user;
}
mapping(bytes32 => Commit) public commits;
function commit(bytes32 commitHash) external {
commits[commitHash] = Commit(commitHash, block.number, msg.sender);
}
function reveal(uint256 amount, uint256 minOut, bytes32 salt) external {
bytes32 h = keccak256(abi.encodePacked(amount, minOut, salt, msg.sender));
Commit memory c = commits[h];
require(c.user == msg.sender, "no commit");
require(block.number > c.blockNumber, "same block");
require(block.number <= c.blockNumber + REVEAL_WINDOW, "expired");
delete commits[h];
_executeSwap(amount, minOut);
}
Strengths:
- Sandwiches are structurally impossible: the swap's parameters are not known until reveal, and at reveal time the trade executes in a single transaction that searchers cannot front-run with knowledge of the parameters.
- Works on any chain with a public mempool.
Weaknesses and audit notes:
- User UX is awful: two transactions, two gas costs, two latencies.
- Reveal failures lock funds or value: if the user fails to reveal in time, the commit may need recovery logic (refund? forfeit?).
- Withdrawal-stage commits can themselves be censored: an adversary can refuse to include reveals from specific addresses if they control enough block space. This is usually theoretical but exists.
- Doesn't help with back-running in many cases — once the reveal hits the chain, the resulting state change is observable.
- Statistical analysis of commit timing, gas price, and reveal patterns can sometimes link commits to reveals before they happen.
Commit-reveal is appropriate for occasional high-value operations (NFT mints, governance votes, large auctions) where the UX cost is justified. It is rarely appropriate for routine DEX swaps.
Batch Auctions
A batch auction collects user orders over a window (a block, several blocks, or a continuous interval) and clears them simultaneously at a uniform price. Within a batch, no order can sandwich another because the clearing price is the same for everyone.
Examples in production:
- CowSwap (CoW Protocol): Users submit signed off-chain intents; solvers compete to find the best batch settlement; the winning solver pays a fee to settle the batch on-chain at a uniform clearing price. Sandwiches are eliminated by construction.
- Penumbra (Cosmos): Sealed-bid batch auctions for DEX functionality.
- Various L2s exploring batch settlement in their sequencer design.
Strengths:
- Eliminates sandwich MEV at the protocol level.
- "Coincidence of wants" (CoW) matching can give users better prices than direct AMM execution because counterparty swaps can be netted internally.
- Users sign intents off-chain; gas costs are batched.
Weaknesses:
- Latency: Orders execute at batch boundaries, not immediately.
- Solver trust: Most batch-auction systems rely on a solver network to find the best settlement; solver collusion is a (mitigated, but real) concern.
- Complexity: The protocol surface is larger; audit scope expands.
Batch auctions are the strongest known structural defense against sandwich MEV for routine trading. Increasingly seen on L2s and within meta-aggregators.
Slippage Bounds and Deadlines
The minimum any swap-like function must implement:
minAmountOut(ormaxAmountIn): The user signs the worst acceptable outcome. The contract enforces it. This bounds the searcher's profit window to the slippage tolerance.deadline: The user signs a block timestamp after which the transaction is invalid. This prevents stale transactions from being held back and executed when the price has moved against the user.
Audit checklist for every swap-like function:
-
minAmountOut(or equivalent) is a required parameter, not optional. -
The check
require(out >= minAmountOut, "slippage")is performed after the swap completes. -
deadlineis a required parameter, andrequire(block.timestamp <= deadline, "expired")is enforced. -
Helper / router / aggregator contracts do not allow callers to set these to zero or
type(uint256).max. - Default UI values are tight (≤ 1%, ideally ≤ 0.5% for liquid assets) and visible to the user.
A surprising number of helper contracts pass minAmountOut = 0 or deadline = type(uint256).max internally. These are critical findings.
Sealed-Bid Mechanisms
For one-shot value-discovery events (auctions, IDOs, large mints):
- Sealed-bid first-price auctions: Bidders submit hashed bids; reveals are simultaneous; highest reveals win.
- Vickrey auctions (sealed-bid second-price): Bidders submit hashed bids; the highest bidder wins but pays the second-highest price. Game-theoretic optimum: bid your true value.
- VRF-driven random selection: When all bids exceed a threshold, the winner is selected randomly using a VRF (Chainlink VRF or equivalent), eliminating the gas race.
Audit notes:
- The hash commitment must include a
salt/noncechosen by the bidder; without it, a small bid space (e.g., integer values in a known range) can be brute-forced. - The reveal window must be bounded; missing reveals must have a deterministic outcome (forfeit, refund, etc.).
- VRF requests must be funded and the callback must be re-entrancy-safe.
Atomic Composition
A defense often available "for free" in EVM: combine the user's action with its required price observation into a single atomic transaction.
Bad pattern:
[block N] oracle.update(newPrice)
[block N] user calls protocol.act() using the new price ← searcher sandwich window
Better pattern:
[block N] user calls protocol.actWithPriceProof(priceProof) {
verify priceProof
act on the verified price
}
The price proof can come from an off-chain signed source (Pyth Pull, Chainlink Data Streams, Redstone, EIP-712 signed quotes). The user fetches it just-in-time and includes it in their transaction. There is no window during which a searcher can move the price between observation and action.
This is the modern best practice for any protocol whose actions depend on a recent off-chain price. It is also the design pattern audited contracts increasingly use for liquidations, options exercises, and perp settlements.
On-Chain VRF for Tie-Breaking
When N callers are competing for a single reward (rebase trigger, mint slot, lottery), a VRF-based selection eliminates the gas race that would otherwise dissipate the reward to validators.
function requestRebase() external {
require(canRebase(), "not yet");
// request VRF; callback selects winner among recent registrants
}
Audit notes:
- VRF requests cost gas; the cost should be borne by someone (protocol treasury, the requester, the eventual winner).
- VRF callback must be re-entrancy-safe and must handle the case where the callback comes much later than the request (state may have changed).
- The set of eligible participants must be defined before the VRF result is known; otherwise the design is exploitable.
Limit Orders, Conditional Orders, Intents
A growing pattern: users sign off-chain orders that get filled when conditions are met. Examples include 1inch Fusion, Uniswap X, CoWSwap, and various "intent" protocols.
The user signs once; a network of solvers / fillers competes to execute the order; the user gets a price guarantee (or no fill). MEV is largely internalized by the solvers, and a portion is often rebated to the user.
Audit considerations for any intent-based protocol:
- Signature verification: EIP-712 typed-data signatures, with explicit chain ID, expiry, nonce, and order parameters.
- Order replay protection: Orders cancellable on-chain and on-relay; once-filled orders cannot be re-filled.
- Solver authentication: Are solvers permissioned, or open? If open, what prevents a single solver from filling against itself unfavorably?
- Settlement honesty: Does the on-chain settlement actually match the off-chain order's terms? Slippage checks on the user's behalf are essential.
Choosing the Right Defense
| Concern | Cheapest effective defense |
|---|---|
| Swap sandwiches | Tight slippage + private mempool (Flashbots Protect) for retail; batch auctions (CowSwap) for sophisticated users |
| Oracle sandwiches | Pull oracles with atomic price proof |
| Mint sniping | Allow-list, sealed-bid auction, or Dutch auction |
| Liquidation racing | Dutch-auction liquidations or accept it as normal cost-of-business |
| First-caller rewards | VRF tie-breaking or pro-rata distribution to registered participants |
| Time-sensitive triggers | Accept the snipe; ensure the reward is small relative to the protocol benefit |
| Generalized adversarial ordering | Move to intent-based / off-chain matching (CowSwap, Uniswap X) |
There is no universal solution. The defense that fits depends on the protocol's UX tolerance, latency budget, and the value-density of the actions being protected.