From grimoire
Commit-reveal, slippage tolerance, and MEV-resistant patterns for DeFi smart contracts to prevent front-running and sandwich attacks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:prevent-front-runningThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Implement commit-reveal, strict slippage bounds, and MEV-resistant submission via Flashbots to prevent front-running, sandwich attacks, and transaction reordering that extracts value from users.
Implement commit-reveal, strict slippage bounds, and MEV-resistant submission via Flashbots to prevent front-running, sandwich attacks, and transaction reordering that extracts value from users.
Adopted by: OWASP Smart Contract Top 10 SC08 (Front-Running). Flashbots (ethereum.org/en/developers/docs/mev) is the canonical solution for MEV protection, used by Ethereum validators and adopted by Uniswap, Curve, and 1inch for protected routing. Uniswap V3's amountOutMinimum parameter is the standard slippage protection pattern. The Ethereum Foundation's MEV documentation mandates commit-reveal for any outcome dependent on transaction order.
Impact: Flashbots' MEV-Explore dashboard estimates cumulative MEV extracted from Ethereum exceeds $1.5B (2020–2024). Sandwich attacks are the most common user-facing MEV: an attacker sees a large swap in the mempool, buys before it (raising the price), lets the victim's swap execute at a worse price, then sells — consistently extracting value from victims with no capital risk. JaredFromSubway.eth (a prominent MEV bot) extracted $40M in 2023 from sandwich attacks alone. The 2022 Solana NFT mint exploits used front-running bots to win "fair" lotteries.
Why best: Transaction ordering in public mempools is not first-come-first-served — miners/validators can reorder, insert, or censor transactions within a block. Slippage bounds limit the price a swap can accept, making sandwiches unprofitable. Commit-reveal hides transaction intent until the block is sealed. Private mempool submission (Flashbots, MEV Blocker) routes transactions directly to validators, bypassing the public mempool entirely.
Sources: OWASP Smart Contract Top 10 SC08; Flashbots MEV documentation; Ethereum Foundation MEV guide; Uniswap V3 router documentation
Enforce strict slippage tolerance in AMM swaps:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
contract SlippageProtectedSwap {
ISwapRouter public immutable router;
constructor(address _router) {
router = ISwapRouter(_router);
}
function swapWithProtection(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut, // caller must specify minimum — never 0
uint24 fee
) external returns (uint256 amountOut) {
require(minAmountOut > 0, "Slippage protection required");
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: msg.sender,
deadline: block.timestamp + 20 minutes, // also require deadline
amountIn: amountIn,
amountOutMinimum: minAmountOut, // reverts if price moves beyond tolerance
sqrtPriceLimitX96: 0
});
amountOut = router.exactInputSingle(params);
}
}
Implement commit-reveal for order-sensitive operations:
contract CommitRevealAuction {
struct Commitment {
bytes32 hash;
uint256 blockNumber;
}
mapping(address => Commitment) public commitments;
uint256 private constant COMMIT_PERIOD = 20; // blocks
uint256 private constant REVEAL_DEADLINE = 40; // blocks after commit
// Phase 1: Submit hash(bid, salt) — intent hidden from mempool watchers
function commit(bytes32 commitment) external {
commitments[msg.sender] = Commitment({
hash: commitment,
blockNumber: block.number
});
}
// Phase 2: Reveal actual bid after commit period
function reveal(uint256 bid, bytes32 salt) external {
Commitment memory c = commitments[msg.sender];
require(block.number >= c.blockNumber + COMMIT_PERIOD, "Commit period not ended");
require(block.number <= c.blockNumber + REVEAL_DEADLINE, "Reveal deadline passed");
require(
keccak256(abi.encodePacked(bid, salt, msg.sender)) == c.hash,
"Invalid reveal"
);
// Process bid — all bids revealed before winner determined
_processBid(msg.sender, bid);
delete commitments[msg.sender];
}
}
Add deadline parameter to time-sensitive transactions:
function executeOperation(
address asset,
uint256 amount,
uint256 deadline // always require explicit deadline
) external {
require(block.timestamp <= deadline, "Transaction expired");
// Prevents delayed mining of a transaction when market conditions change
}
Use Flashbots for private transaction submission (off-chain):
// ethers.js + Flashbots SDK
import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle";
import { ethers } from "ethers";
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
ethers.Wallet.createRandom(), // reputation signer
"https://relay.flashbots.net"
);
const signedBundle = await flashbotsProvider.signBundle([{
signer: wallet,
transaction: {
chainId: 1,
to: CONTRACT_ADDRESS,
data: swapCalldata,
gasLimit: 200000,
maxFeePerGas: ethers.utils.parseUnits("50", "gwei"),
maxPriorityFeePerGas: ethers.utils.parseUnits("2", "gwei"),
}
}]);
// Submit directly to validators — bypasses public mempool
const bundleSubmission = await flashbotsProvider.sendRawBundle(
signedBundle,
targetBlockNumber
);
For NFT mints: random assignment after reveal:
// Prevent sniper bots from selecting rare traits at mint time
contract AntiSnipeMint {
bool public revealed = false;
uint256 public revealBlock;
uint256 public revealSeed;
function mint(uint256 quantity) external payable {
// Mint token IDs without revealing traits
_mintTokens(msg.sender, quantity);
}
function reveal() external {
require(!revealed && block.number > revealBlock);
// Use future block hash — not known at mint time
revealSeed = uint256(blockhash(revealBlock));
revealed = true;
}
function getTraits(uint256 tokenId) external view returns (uint256) {
require(revealed, "Not yet revealed");
return uint256(keccak256(abi.encodePacked(revealSeed, tokenId))) % NUM_TRAITS;
}
}
amountOutMinimum: 0 in swap calls — this accepts any price including one manipulated by a sandwicher.block.timestamp — a transaction sitting in the mempool for hours may execute under completely different market conditions.deadline: block.timestamp — using the current block's timestamp as the deadline means the transaction never expires, defeating the purpose.keccak256(bid) is brute-forceable for small bid spaces; always combine with a large random salt.npx claudepluginhub jeffreytse/grimoire --plugin grimoireProvides Solidity security patterns for reentrancy, token decimals, precision loss, with defensive code examples and pre-deploy audit checklist. Use before deploying or reviewing value-handling contracts.
Smart contract security audit guide for DeFi: 10 bug classes, pre-dive kill signals, Foundry PoC templates, and real Immunefi examples. Use for Solidity/Rust audits and target triage.
Security checklist for Solidity AMM contracts covering reentrancy, CEI ordering, donation/inflation attacks, oracle manipulation, slippage, admin controls, and integer math.