Private Pools
Private pools (also known as dark pools or private mempool) route transactions directly to block producers without entering the public mempool, preventing MEV searchers from seeing and manipulating your trades.
How Private Pools Work
Private Transmission
Transactions sent directly to validators without public mempool exposure
Confidential Execution
Trade details remain private until block inclusion
MEV Protection
Prevents frontrunning, sandwich attacks, and other MEV exploitation
// Send private transaction via Flashbots
const { FlashbotsBundleProvider } = require('@flashbots/ethers-provider-bundle');
async function sendPrivateTransaction(tx) {
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider, // ethers provider
wallet, // signer
'https://relay.flashbots.net' // flashbots relay
);
// Prepare private transaction
const privateTx = {
to: tx.to,
data: tx.data,
gasLimit: tx.gasLimit,
gasPrice: tx.gasPrice,
nonce: tx.nonce,
value: tx.value
};
// Send via Flashbots (private mempool)
const signedTx = await wallet.signTransaction(privateTx);
const flashbotsTransaction = {
signedTransaction: signedTx
};
const response = await flashbotsProvider.sendPrivateTransaction(
flashbotsTransaction,
3600 // 1 hour timeout
);
return response;
}
// Usage example
const privateTx = await sendPrivateTransaction({
to: UNISWAP_ROUTER,
data: swapTxData,
gasLimit: 300000,
gasPrice: 20000000000, // 20 gwei
nonce: await wallet.getTransactionCount()
});
Speed Trade-off
May result in slower inclusion times compared to public mempool
Gas Costs
Sometimes requires higher gas prices for reliable inclusion
Limited Availability
Not all private services cover all types of transactions
Commit-Reveal Scheme
Commit-reveal schemes prevent MEV by revealing transaction details only after they have been included in a block, eliminating the opportunity for frontrunning.
Implementation Pattern
Commit Phase
Submit hash of intended transaction with deposit to prove commitment
Wait Period
Wait for block inclusion and minimum delay period
Reveal Phase
Reveal original transaction details to execute trade
Execution
Contract validates reveal and executes trade atomically
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract CommitRevealSwap {
struct Commitment {
address trader;
uint256 amount;
uint256 deadline;
bool revealed;
}
mapping(bytes32 => Commitment) public commitments;
mapping(address => uint256) public deposits;
uint256 public constant REVEAL_DELAY = 2; // 2 blocks
uint256 public constant COMMITMENT_DURATION = 100; // 100 blocks
event Committed(bytes32 indexed commitmentHash, address indexed trader);
event Revealed(bytes32 indexed commitmentHash, uint256 amount);
event Executed(bytes32 indexed commitmentHash, address indexed trader);
// Commit phase: hash transaction details
function commitTransaction(
bytes32 commitmentHash
) external payable {
require(commitments[commitmentHash].trader == address(0), "Already committed");
commitments[commitmentHash] = Commitment({
trader: msg.sender,
amount: msg.value,
deadline: block.number + COMMITMENT_DURATION,
revealed: false
});
emit Committed(commitmentHash, msg.sender);
}
// Reveal phase: submit original transaction data
function revealTransaction(
uint256 amount,
uint256 maxSlippage,
uint256 deadline
) external {
bytes32 commitmentHash = keccak256(abi.encodePacked(
msg.sender,
amount,
maxSlippage,
deadline
));
Commitment storage commitment = commitments[commitmentHash];
require(commitment.trader == msg.sender, "Invalid commitment");
require(!commitment.revealed, "Already revealed");
require(block.number >= commitment.deadline - REVEAL_DELAY, "Too early");
commitment.revealed = true;
emit Revealed(commitmentHash, amount);
}
// Execute trade after reveal delay
function executeTrade(
bytes32 commitmentHash,
address swapRouter,
bytes calldata swapData
) external {
Commitment storage commitment = commitments[commitmentHash];
require(commitment.revealed, "Not revealed");
require(block.number >= commitment.deadline - REVEAL_DELAY, "Reveal delay not met");
// Execute swap
(bool success,) = swapRouter.call(swapData);
require(success, "Swap failed");
// Refund any excess
uint256 balance = address(this).balance;
if (balance > 0) {
payable(commitment.trader).transfer(balance);
}
emit Executed(commitmentHash, commitment.trader);
}
}
Time Delays
Time delays prevent MEV by ensuring transactions cannot be included in the immediate next block, giving time for market participants to react and preventing frontrunning.
Delay Strategies
Minimum Block Delay
Transactions can only execute after N blocks have passed since submission
Time-locked Execution
Trades can only be executed during specific time windows
Random Delays
Introduce randomness to prevent predictable timing attacks
contract TimeDelayedDEX {
struct DelayedSwap {
address trader;
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 minAmountOut;
uint256 deadline;
bool executed;
}
uint256 public constant MIN_DELAY_BLOCKS = 3;
mapping(bytes32 => DelayedSwap) public delayedSwaps;
event SwapRequested(
bytes32 indexed swapId,
address indexed trader,
uint256 executionBlock
);
function requestSwap(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut
) external returns (bytes32 swapId) {
swapId = keccak256(abi.encodePacked(
msg.sender,
tokenIn,
tokenOut,
amountIn,
block.timestamp,
block.number
));
uint256 executionBlock = block.number + MIN_DELAY_BLOCKS;
uint256 deadline = executionBlock + 100; // 100 block window
delayedSwaps[swapId] = DelayedSwap({
trader: msg.sender,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountIn: amountIn,
minAmountOut: minAmountOut,
deadline: deadline,
executed: false
});
emit SwapRequested(swapId, msg.sender, executionBlock);
}
function executeSwap(bytes32 swapId) external {
DelayedSwap storage swap = delayedSwaps[swapId];
require(swap.trader != address(0), "Invalid swap");
require(!swap.executed, "Already executed");
require(block.number >= swap.deadline - 100, "Too early");
require(block.number <= swap.deadline, "Expired");
swap.executed = true;
// Transfer tokens from trader
IERC20(swap.tokenIn).transferFrom(
swap.trader,
address(this),
swap.amountIn
);
// Execute swap
(uint256 amountOut) = executeInternalSwap(
swap.tokenIn,
swap.tokenOut,
swap.amountIn
);
require(amountOut >= swap.minAmountOut, "Slippage too high");
// Transfer output to trader
IERC20(swap.tokenOut).transfer(swap.trader, amountOut);
}
}
Slippage Protection
While not preventing MEV directly, tight slippage limits can minimize the impact of sandwich attacks and price manipulation by limiting how much the price can move against you.
Slippage Strategies
Fixed Percentage
Set maximum allowed slippage as percentage of trade value
Dynamic Slipping
Adjust slippage limits based on market conditions and volatility
Price Impact Analysis
Calculate expected price impact before executing trades
class SlippageProtection {
constructor(priceOracle, volatilityCalculator) {
this.priceOracle = priceOracle;
this.volatilityCalculator = volatilityCalculator;
}
async calculateOptimalSlippage(token, tradeSize) {
// Get current volatility
const volatility = await this.volatilityCalculator.get24hVolatility(token);
// Get liquidity depth
const liquidity = await this.getLiquidityDepth(token);
// Calculate price impact
const priceImpact = await this.calculatePriceImpact(token, tradeSize);
// Determine safe slippage limits
let maxSlippage;
if (volatility > 0.1) { // High volatility
maxSlippage = 0.01; // 1%
} else if (volatility > 0.05) { // Medium volatility
maxSlippage = 0.005; // 0.5%
} else { // Low volatility
maxSlippage = 0.002; // 0.2%
}
// Adjust for liquidity
if (liquidity < tradeSize * 2) {
maxSlippage *= 0.5; // Halve slippage for low liquidity
}
// Adjust for price impact
if (priceImpact > 0.02) { // 2% price impact
maxSlippage *= 0.3; // Very tight slippage
}
return Math.max(0.001, Math.min(0.02, maxSlippage)); // 0.1% to 2% range
}
async executeProtectedTrade(trade, maxSlippage) {
const slippage = await this.calculateOptimalSlippage(
trade.token,
trade.amount
);
// Use the more conservative slippage
const finalSlippage = Math.min(maxSlippage, slippage);
const tx = await this.prepareTransaction({
...trade,
slippageTolerance: finalSlippage
});
return this.sendPrivateTransaction(tx);
}
}
Batch Auctions
Batch auctions collect all transactions for a trading pair over a time period and execute them at the same price, eliminating opportunities for frontrunning and MEV extraction.
How Batch Auctions Work
Order Collection
Collect all buy and sell orders during batch window
Order Matching
Match orders using single clearing price mechanism
Price Setting
Calculate fair price that maximizes trade volume
Atomic Settlement
Execute all matched trades at the same time
Flashbots
Flashbots is a decentralized, open-source infrastructure for private transaction relay that prevents MEV extraction while keeping your transaction details confidential.
Validator Network
Network of validators who commit to keep transactions private
Bundle Support
Support for transaction bundles with guaranteed execution order
Open Source
Fully open source and auditable code for transparency
Eden Network
Eden Network provides a tiered private mempool service with different levels of MEV protection and priority for various user types.
Service Tiers
Eden Members
Basic MEV protection with reduced priority in the private pool
Eden Executors
Enhanced protection with higher priority and custom gas strategies
EDEN Delegates
Premium tier with highest protection and priority execution
ARPA (Anti-Recovery PRotection Algorithm)
ARPA is a privacy-preserving random number generation service that enables fair transaction ordering without revealing transaction details.
MEV Inspect
MEV Inspect is an analytics platform that provides real-time monitoring and analysis of MEV activity across the Ethereum network, helping users understand and avoid MEV-related risks.
Monitoring Capabilities
Real-time Analytics
Track MEV activity patterns and identify high-risk periods
Risk Assessment
Assess MEV risk levels for different transaction types
Protection Guidance
Get recommendations for optimal protection strategies