MEV与交易优化
1. MEV基础概念
1.1 什么是MEV
MEV (Maximal Extractable Value,最大可提取价值) 是指通过在区块中包含、排除或重新排序交易而可以获取的价值,超过标准区块奖励和Gas费用。
MEV的核心特征:
- 交易顺序依赖:价值来源于控制交易在区块中的位置
- 信息不对称:提前知道待处理交易的内容
- 时间敏感性:机会窗口通常很短暂
- 竞争激烈:多个MEV搜索者竞争同一机会
1.2 MEV的类型
MEV类型分类
├── 套利 (Arbitrage)
│ ├── DEX套利
│ ├── 跨链套利
│ └── 清算套利
├── 抢跑 (Front-running)
│ ├── 交易抢跑
│ └── NFT抢跑
├── 夹击 (Sandwich Attack)
│ ├── 普通夹击
│ └── 多层夹击
├── 清算 (Liquidation)
│ ├── 借贷协议清算
│ └── 合成资产清算
└── 时间强盗 (Time-bandit)
└── 区块重组攻击
1.3 MEV的影响
正面影响:
- 提高市场效率
- 维护协议偿付能力(清算)
- 价格发现机制
负面影响:
- 增加用户交易成本
- 网络拥堵
- 共识层面的风险
- 不公平的价值提取
2. DEX套利原理
2.1 跨DEX套利
当同一交易对在不同DEX上价格不同时,可以通过买低卖高获利。
示例:
Uniswap: 1 ETH = 2000 USDC
Sushiswap: 1 ETH = 2010 USDC
套利流程:
1. 在Uniswap买入1 ETH,花费2000 USDC
2. 在Sushiswap卖出1 ETH,获得2010 USDC
3. 利润 = 2010 - 2000 = 10 USDC (扣除Gas费)
2.2 三角套利
利用三种代币之间的价格差异进行套利。
示例:
ETH/USDC = 2000
LINK/USDC = 10
ETH/LINK = 205
套利路径:
1. 用1000 USDC买入0.5 ETH
2. 用0.5 ETH买入102.5 LINK
3. 用102.5 LINK卖出1025 USDC
4. 利润 = 1025 - 1000 = 25 USDC
2.3 套利合约实现
// contracts/FlashArbitrage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
interface IFlashLoanReceiver {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
}
contract FlashArbitrage is Ownable, IFlashLoanReceiver {
address public immutable WETH;
IUniswapV2Router02 public immutable uniswapRouter;
IUniswapV2Router02 public immutable sushiswapRouter;
event ArbitrageExecuted(
address indexed token0,
address indexed token1,
uint256 profit
);
constructor(
address _weth,
address _uniswapRouter,
address _sushiswapRouter
) Ownable(msg.sender) {
WETH = _weth;
uniswapRouter = IUniswapV2Router02(_uniswapRouter);
sushiswapRouter = IUniswapV2Router02(_sushiswapRouter);
}
// 简单的双DEX套利
function simpleArbitrage(
address token0,
address token1,
uint256 amountIn,
bool buyOnUniswap
) external onlyOwner {
require(amountIn > 0, "Invalid amount");
IERC20(token0).transferFrom(msg.sender, address(this), amountIn);
IUniswapV2Router02 buyRouter = buyOnUniswap ? uniswapRouter : sushiswapRouter;
IUniswapV2Router02 sellRouter = buyOnUniswap ? sushiswapRouter : uniswapRouter;
// 第一步:在DEX A买入
IERC20(token0).approve(address(buyRouter), amountIn);
address[] memory path = new address[](2);
path[0] = token0;
path[1] = token1;
uint256[] memory amounts = buyRouter.swapExactTokensForTokens(
amountIn,
0,
path,
address(this),
block.timestamp
);
uint256 token1Amount = amounts[1];
// 第二步:在DEX B卖出
IERC20(token1).approve(address(sellRouter), token1Amount);
path[0] = token1;
path[1] = token0;
amounts = sellRouter.swapExactTokensForTokens(
token1Amount,
amountIn, // 至少要回本
path,
address(this),
block.timestamp
);
uint256 finalAmount = amounts[1];
require(finalAmount > amountIn, "No profit");
uint256 profit = finalAmount - amountIn;
// 转回利润给所有者
IERC20(token0).transfer(msg.sender, finalAmount);
emit ArbitrageExecuted(token0, token1, profit);
}
// 三角套利
function triangularArbitrage(
address token0,
address token1,
address token2,
uint256 amountIn,
address[] memory routers
) external onlyOwner {
require(routers.length == 3, "Need 3 routers");
IERC20(token0).transferFrom(msg.sender, address(this), amountIn);
// 第一步: token0 -> token1
IERC20(token0).approve(routers[0], amountIn);
address[] memory path1 = new address[](2);
path1[0] = token0;
path1[1] = token1;
uint256[] memory amounts1 = IUniswapV2Router02(routers[0])
.swapExactTokensForTokens(
amountIn,
0,
path1,
address(this),
block.timestamp
);
// 第二步: token1 -> token2
uint256 token1Amount = amounts1[1];
IERC20(token1).approve(routers[1], token1Amount);
address[] memory path2 = new address[](2);
path2[0] = token1;
path2[1] = token2;
uint256[] memory amounts2 = IUniswapV2Router02(routers[1])
.swapExactTokensForTokens(
token1Amount,
0,
path2,
address(this),
block.timestamp
);
// 第三步: token2 -> token0
uint256 token2Amount = amounts2[1];
IERC20(token2).approve(routers[2], token2Amount);
address[] memory path3 = new address[](2);
path3[0] = token2;
path3[1] = token0;
uint256[] memory amounts3 = IUniswapV2Router02(routers[2])
.swapExactTokensForTokens(
token2Amount,
amountIn,
path3,
address(this),
block.timestamp
);
uint256 finalAmount = amounts3[1];
require(finalAmount > amountIn, "No profit");
IERC20(token0).transfer(msg.sender, finalAmount);
emit ArbitrageExecuted(token0, token2, finalAmount - amountIn);
}
// 闪电贷套利
function flashArbitrage(
address flashLoanProvider,
address token0,
address token1,
uint256 flashLoanAmount,
bool buyOnUniswap
) external onlyOwner {
// 准备闪电贷参数
bytes memory params = abi.encode(token0, token1, buyOnUniswap);
address[] memory assets = new address[](1);
assets[0] = token0;
uint256[] memory amounts = new uint256[](1);
amounts[0] = flashLoanAmount;
uint256[] memory modes = new uint256[](1);
modes[0] = 0; // 不开启债务模式
// 执行闪电贷
// ILendingPool(flashLoanProvider).flashLoan(...);
}
// 闪电贷回调
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external override returns (bool) {
require(initiator == address(this), "Unauthorized");
(address token0, address token1, bool buyOnUniswap) =
abi.decode(params, (address, address, bool));
uint256 amountOwed = amounts[0] + premiums[0];
// 执行套利
IUniswapV2Router02 buyRouter = buyOnUniswap ? uniswapRouter : sushiswapRouter;
IUniswapV2Router02 sellRouter = buyOnUniswap ? sushiswapRouter : uniswapRouter;
IERC20(token0).approve(address(buyRouter), amounts[0]);
address[] memory path = new address[](2);
path[0] = token0;
path[1] = token1;
uint256[] memory swapAmounts = buyRouter.swapExactTokensForTokens(
amounts[0],
0,
path,
address(this),
block.timestamp
);
uint256 token1Amount = swapAmounts[1];
IERC20(token1).approve(address(sellRouter), token1Amount);
path[0] = token1;
path[1] = token0;
swapAmounts = sellRouter.swapExactTokensForTokens(
token1Amount,
amountOwed,
path,
address(this),
block.timestamp
);
// 批准还款
IERC20(assets[0]).approve(msg.sender, amountOwed);
return true;
}
// 提取利润
function withdraw(address token) external onlyOwner {
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(msg.sender, balance);
}
}
3. 抢跑攻击 (Front-running)
3.1 抢跑原理
抢跑是指通过设置更高的Gas价格,使自己的交易在目标交易之前被打包。
正常情况:
Block N: [Tx1, Tx2, Tx3]
抢跑情况:
用户提交Tx2 (gasPrice: 50 gwei)
机器人看到Tx2,提交Tx2' (gasPrice: 100 gwei)
Block N: [Tx1, Tx2', Tx2, Tx3]
3.2 抢跑检测与执行
// scripts/frontrun-detector.ts
import { ethers } from "ethers";
import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle";
class FrontRunDetector {
private provider: ethers.WebSocketProvider;
private wallet: ethers.Wallet;
private flashbotsProvider: FlashbotsBundleProvider;
// 监控的DEX路由器地址
private monitoredDexes = [
"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", // Uniswap V2
"0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F", // Sushiswap
];
constructor(rpcUrl: string, privateKey: string) {
this.provider = new ethers.WebSocketProvider(rpcUrl);
this.wallet = new ethers.Wallet(privateKey, this.provider);
}
async init() {
this.flashbotsProvider = await FlashbotsBundleProvider.create(
this.provider,
this.wallet,
"https://relay.flashbots.net",
"goerli" // or "mainnet"
);
}
// 监听mempool中的待处理交易
async monitorMempool() {
console.log("Monitoring mempool for opportunities...");
this.provider.on("pending", async (txHash) => {
try {
const tx = await this.provider.getTransaction(txHash);
if (!tx || !tx.to) return;
// 检查是否是DEX交易
if (this.monitoredDexes.includes(tx.to.toLowerCase())) {
await this.analyzeTrade(tx);
}
} catch (error) {
// 交易可能已被打包,忽略错误
}
});
}
// 分析交易
private async analyzeTrade(tx: ethers.TransactionResponse) {
try {
const iface = new ethers.Interface([
"function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)",
"function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)",
"function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)",
]);
const decodedData = iface.parseTransaction({
data: tx.data,
value: tx.value
});
if (!decodedData) return;
console.log("Detected swap:", {
method: decodedData.name,
from: tx.from,
gasPrice: ethers.formatUnits(tx.gasPrice || 0, "gwei"),
});
// 评估是否值得抢跑
const profitable = await this.evaluateFrontrun(tx, decodedData);
if (profitable) {
await this.executeFrontrun(tx);
}
} catch (error) {
// 解析失败,可能不是swap交易
}
}
// 评估抢跑是否有利可图
private async evaluateFrontrun(
originalTx: ethers.TransactionResponse,
decodedData: ethers.TransactionDescription
): Promise<boolean> {
// 简化的评估逻辑
const amountIn = decodedData.args[0];
const path = decodedData.args[1] || decodedData.args[2];
// 检查交易大小
const minTradeSize = ethers.parseEther("1"); // 1 ETH或等值代币
if (amountIn < minTradeSize) {
return false;
}
// 模拟交易,计算潜在利润
// 这里需要具体的DEX合约接口来获取价格影响
return true; // 简化示例
}
// 执行抢跑
private async executeFrontrun(originalTx: ethers.TransactionResponse) {
console.log("Executing frontrun...");
// 创建抢跑交易
const frontrunTx = {
to: originalTx.to,
data: originalTx.data,
value: originalTx.value,
gasPrice: (originalTx.gasPrice || 0n) + ethers.parseUnits("10", "gwei"),
gasLimit: originalTx.gasLimit,
};
try {
const tx = await this.wallet.sendTransaction(frontrunTx);
console.log("Frontrun tx sent:", tx.hash);
const receipt = await tx.wait();
console.log("Frontrun successful in block:", receipt?.blockNumber);
} catch (error: any) {
console.error("Frontrun failed:", error.message);
}
}
// 使用Flashbots避免失败交易上链
private async executeFrontrunWithFlashbots(
originalTx: ethers.TransactionResponse,
targetBlockNumber: number
) {
const frontrunTx = {
signer: this.wallet,
transaction: {
to: originalTx.to,
data: originalTx.data,
value: originalTx.value,
gasLimit: originalTx.gasLimit,
},
};
const signedBundle = await this.flashbotsProvider.signBundle([frontrunTx]);
const simulation = await this.flashbotsProvider.simulate(
signedBundle,
targetBlockNumber
);
if ("error" in simulation) {
console.error("Simulation error:", simulation.error);
return;
}
console.log("Simulation success:", simulation);
const bundleSubmission = await this.flashbotsProvider.sendRawBundle(
signedBundle,
targetBlockNumber
);
console.log("Bundle submitted:", bundleSubmission.bundleHash);
}
}
// 使用示例
async function main() {
const detector = new FrontRunDetector(
process.env.WSS_RPC_URL!,
process.env.PRIVATE_KEY!
);
await detector.init();
await detector.monitorMempool();
}
main().catch(console.error);
4. 三明治攻击 (Sandwich Attack)
4.1 三明治攻击原理
三明治攻击是指在目标交易前后分别插入交易,从价格波动中获利。
攻击流程:
1. 检测到大额买单 (受害者要买入Token)
2. 在受害者交易前买入Token (抬高价格)
3. 受害者以更高价格买入
4. 在受害者交易后卖出Token (获利)
示例:
Block N:
- 攻击者买入 (gasPrice: 高)
- 受害者买入 (gasPrice: 中)
- 攻击者卖出 (gasPrice: 高)
4.2 三明治攻击实现
// contracts/SandwichBot.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SandwichBot is Ownable {
IUniswapV2Router02 public immutable router;
struct SandwichParams {
address tokenIn;
address tokenOut;
uint256 frontrunAmount;
uint256 minProfit;
}
event SandwichExecuted(
address indexed tokenIn,
address indexed tokenOut,
uint256 profit
);
constructor(address _router) Ownable(msg.sender) {
router = IUniswapV2Router02(_router);
}
// 执行三明治攻击
function executeSandwich(
SandwichParams calldata params,
bytes calldata victimTxData
) external onlyOwner {
// 第一步:frontrun - 买入代币
uint256 amountOut = _frontrun(
params.tokenIn,
params.tokenOut,
params.frontrunAmount
);
// 等待受害者交易执行(实际上这一步在链上无法实现,需要链下控制)
// 这里假设我们在同一个区块内提交了两笔交易,通过Gas price控制顺序
// 第二步:backrun - 卖出代币
uint256 profit = _backrun(
params.tokenOut,
params.tokenIn,
amountOut,
params.frontrunAmount
);
require(profit >= params.minProfit, "Insufficient profit");
emit SandwichExecuted(params.tokenIn, params.tokenOut, profit);
}
function _frontrun(
address tokenIn,
address tokenOut,
uint256 amountIn
) private returns (uint256) {
IERC20(tokenIn).approve(address(router), amountIn);
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = tokenOut;
uint256[] memory amounts = router.swapExactTokensForTokens(
amountIn,
0,
path,
address(this),
block.timestamp
);
return amounts[1];
}
function _backrun(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 originalAmount
) private returns (uint256) {
IERC20(tokenIn).approve(address(router), amountIn);
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = tokenOut;
uint256[] memory amounts = router.swapExactTokensForTokens(
amountIn,
originalAmount, // 至少要回本
path,
address(this),
block.timestamp
);
return amounts[1] - originalAmount; // 计算利润
}
// 提取资金
function withdraw(address token) external onlyOwner {
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(owner(), balance);
}
receive() external payable {}
}
4.3 三明治检测与防护
// scripts/sandwich-detector.ts
import { ethers } from "ethers";
interface PendingSwap {
hash: string;
from: string;
to: string;
value: bigint;
gasPrice: bigint;
data: string;
timestamp: number;
}
class SandwichDetector {
private provider: ethers.WebSocketProvider;
private pendingSwaps: Map<string, PendingSwap> = new Map();
constructor(rpcUrl: string) {
this.provider = new ethers.WebSocketProvider(rpcUrl);
}
async detectSandwiches() {
this.provider.on("pending", async (txHash) => {
try {
const tx = await this.provider.getTransaction(txHash);
if (!tx) return;
// 记录swap交易
if (this.isSwapTransaction(tx)) {
this.pendingSwaps.set(txHash, {
hash: txHash,
from: tx.from,
to: tx.to || "",
value: tx.value,
gasPrice: tx.gasPrice || 0n,
data: tx.data,
timestamp: Date.now(),
});
}
} catch (error) {
// ignore
}
});
this.provider.on("block", async (blockNumber) => {
await this.analyzeBlock(blockNumber);
});
}
private isSwapTransaction(tx: ethers.TransactionResponse): boolean {
const swapSelectors = [
"0x38ed1739", // swapExactTokensForTokens
"0x7ff36ab5", // swapExactETHForTokens
"0x18cbafe5", // swapExactTokensForETH
];
return swapSelectors.some(selector =>
tx.data.toLowerCase().startsWith(selector)
);
}
private async analyzeBlock(blockNumber: number) {
const block = await this.provider.getBlock(blockNumber, true);
if (!block || !block.transactions) return;
const transactions = block.transactions as ethers.TransactionResponse[];
// 检测三明治模式
for (let i = 1; i < transactions.length - 1; i++) {
const prev = transactions[i - 1];
const curr = transactions[i];
const next = transactions[i + 1];
if (this.isSandwich(prev, curr, next)) {
console.log("Sandwich attack detected!");
console.log("Frontrun tx:", prev.hash);
console.log("Victim tx:", curr.hash);
console.log("Backrun tx:", next.hash);
await this.calculateSandwichProfit(prev, curr, next);
}
}
// 清理旧的pending交易
this.cleanupPendingSwaps();
}
private isSandwich(
tx1: ethers.TransactionResponse,
tx2: ethers.TransactionResponse,
tx3: ethers.TransactionResponse
): boolean {
// 检查是否是同一个发送者
if (tx1.from !== tx3.from) return false;
// 检查是否都是swap交易
if (!this.isSwapTransaction(tx1) ||
!this.isSwapTransaction(tx2) ||
!this.isSwapTransaction(tx3)) {
return false;
}
// 检查Gas价格模式
const gas1 = tx1.gasPrice || 0n;
const gas2 = tx2.gasPrice || 0n;
const gas3 = tx3.gasPrice || 0n;
// 攻击者的交易通常有更高的Gas
return gas1 > gas2 && gas3 > gas2;
}
private async calculateSandwichProfit(
frontrun: ethers.TransactionResponse,
victim: ethers.TransactionResponse,
backrun: ethers.TransactionResponse
) {
try {
const frontrunReceipt = await this.provider.getTransactionReceipt(frontrun.hash);
const backrunReceipt = await this.provider.getTransactionReceipt(backrun.hash);
if (!frontrunReceipt || !backrunReceipt) return;
// 分析logs来计算实际交换量
// 这里需要解析Transfer事件
console.log("Sandwich profit analysis:");
console.log("Frontrun gas cost:",
ethers.formatEther((frontrunReceipt.gasUsed * frontrun.gasPrice!) || 0));
console.log("Backrun gas cost:",
ethers.formatEther((backrunReceipt.gasUsed * backrun.gasPrice!) || 0));
} catch (error) {
console.error("Error calculating profit:", error);
}
}
private cleanupPendingSwaps() {
const now = Date.now();
const timeout = 60000; // 1分钟
for (const [hash, swap] of this.pendingSwaps.entries()) {
if (now - swap.timestamp > timeout) {
this.pendingSwaps.delete(hash);
}
}
}
}
// 使用
async function main() {
const detector = new SandwichDetector(process.env.WSS_RPC_URL!);
await detector.detectSandwiches();
console.log("Sandwich detector running...");
}
main().catch(console.error);
5. Flashbots 与 MEV 保护
5.1 Flashbots 原理
Flashbots是一个研究和开发组织,旨在减轻MEV的负面影响。
核心概念:
- Bundle:一组交易的集合,要么全部执行,要么全部不执行
- Private Transaction:不进入公开mempool的交易
- MEV-Geth:修改版的以太坊客户端,支持bundle
- Searcher:寻找MEV机会的参与者
- Builder:构建区块的参与者
5.2 使用 Flashbots
// scripts/flashbots-example.ts
import { ethers } from "ethers";
import { FlashbotsBundleProvider, FlashbotsBundleResolution } from "@flashbots/ethers-provider-bundle";
async function main() {
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const authSigner = new ethers.Wallet(process.env.PRIVATE_KEY!);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
// 连接到Flashbots relay
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
authSigner,
"https://relay.flashbots.net",
"mainnet"
);
// 获取当前区块号
const block = await provider.getBlock("latest");
const targetBlockNumber = block!.number + 1;
// 创建交易
const tokenAddress = "0x..."; // ERC20代币地址
const token = new ethers.Contract(
tokenAddress,
["function transfer(address to, uint256 amount) returns (bool)"],
wallet
);
const transaction = await token.transfer.populateTransaction(
"0xRecipientAddress",
ethers.parseEther("100")
);
// 创建bundle
const signedTransactions = await flashbotsProvider.signBundle([
{
signer: wallet,
transaction: transaction,
},
]);
// 模拟bundle
const simulation = await flashbotsProvider.simulate(
signedTransactions,
targetBlockNumber
);
console.log("Simulation result:", simulation);
if ("error" in simulation) {
console.error("Simulation failed:", simulation.error);
return;
}
// 发送bundle
const bundleSubmission = await flashbotsProvider.sendRawBundle(
signedTransactions,
targetBlockNumber
);
console.log("Bundle submitted:", bundleSubmission.bundleHash);
// 等待bundle被打包
const waitResponse = await bundleSubmission.wait();
if (waitResponse === FlashbotsBundleResolution.BundleIncluded) {
console.log("Bundle included in block!");
} else if (waitResponse === FlashbotsBundleResolution.BlockPassedWithoutInclusion) {
console.log("Bundle not included in target block");
} else if (waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) {
console.log("Nonce too high");
}
}
main().catch(console.error);
5.3 MEV Bot 完整实现
// scripts/mev-bot.ts
import { ethers } from "ethers";
import { FlashbotsBundleProvider } from "@flashbots/ethers-provider-bundle";
interface ArbitrageOpportunity {
tokenIn: string;
tokenOut: string;
amountIn: bigint;
expectedProfit: bigint;
dexPath: string[];
}
class MEVBot {
private provider: ethers.WebSocketProvider;
private wallet: ethers.Wallet;
private flashbotsProvider: FlashbotsBundleProvider;
private arbitrageContract: ethers.Contract;
// DEX路由器
private dexRouters = {
uniswap: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
sushiswap: "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F",
};
// 监控的交易对
private watchedPairs = [
{ token0: "WETH", token1: "USDC" },
{ token0: "WETH", token1: "USDT" },
{ token0: "WETH", token1: "DAI" },
];
constructor(
rpcUrl: string,
privateKey: string,
arbitrageContractAddress: string
) {
this.provider = new ethers.WebSocketProvider(rpcUrl);
this.wallet = new ethers.Wallet(privateKey, this.provider);
this.arbitrageContract = new ethers.Contract(
arbitrageContractAddress,
[
"function simpleArbitrage(address token0, address token1, uint256 amountIn, bool buyOnUniswap) external",
],
this.wallet
);
}
async init() {
this.flashbotsProvider = await FlashbotsBundleProvider.create(
this.provider,
this.wallet,
"https://relay.flashbots.net",
"mainnet"
);
console.log("MEV Bot initialized");
}
async start() {
console.log("Starting MEV Bot...");
// 监听新区块
this.provider.on("block", async (blockNumber) => {
await this.scanForOpportunities(blockNumber);
});
// 监听mempool
this.monitorMempool();
}
private async scanForOpportunities(blockNumber: number) {
console.log(`Scanning block ${blockNumber}...`);
for (const pair of this.watchedPairs) {
const opportunity = await this.findArbitrageOpportunity(
pair.token0,
pair.token1
);
if (opportunity && opportunity.expectedProfit > ethers.parseEther("0.01")) {
console.log("Opportunity found:", opportunity);
await this.executeArbitrage(opportunity, blockNumber + 1);
}
}
}
private async findArbitrageOpportunity(
token0: string,
token1: string
): Promise<ArbitrageOpportunity | null> {
try {
// 获取两个DEX的价格
const uniswapPrice = await this.getPrice(
this.dexRouters.uniswap,
token0,
token1
);
const sushiswapPrice = await this.getPrice(
this.dexRouters.sushiswap,
token0,
token1
);
const priceDiff = uniswapPrice > sushiswapPrice
? (uniswapPrice - sushiswapPrice) * 10000n / sushiswapPrice
: (sushiswapPrice - uniswapPrice) * 10000n / uniswapPrice;
// 价格差异需要大于0.5%
if (priceDiff < 50n) {
return null;
}
const amountIn = ethers.parseEther("1"); // 1 ETH或等值代币
const expectedProfit = await this.calculateProfit(
token0,
token1,
amountIn,
uniswapPrice > sushiswapPrice
);
if (expectedProfit <= 0n) {
return null;
}
return {
tokenIn: token0,
tokenOut: token1,
amountIn,
expectedProfit,
dexPath: uniswapPrice > sushiswapPrice
? ["sushiswap", "uniswap"]
: ["uniswap", "sushiswap"],
};
} catch (error) {
return null;
}
}
private async getPrice(
router: string,
token0: string,
token1: string
): Promise<bigint> {
const routerContract = new ethers.Contract(
router,
[
"function getAmountsOut(uint amountIn, address[] memory path) view returns (uint[] memory amounts)",
],
this.provider
);
const amounts = await routerContract.getAmountsOut(
ethers.parseEther("1"),
[token0, token1]
);
return amounts[1];
}
private async calculateProfit(
token0: string,
token1: string,
amountIn: bigint,
buyOnUniswap: boolean
): Promise<bigint> {
// 简化的利润计算
// 实际应该考虑滑点、Gas费用等
const buyRouter = buyOnUniswap
? this.dexRouters.uniswap
: this.dexRouters.sushiswap;
const sellRouter = buyOnUniswap
? this.dexRouters.sushiswap
: this.dexRouters.uniswap;
const buyAmount = await this.getAmountOut(buyRouter, token0, token1, amountIn);
const sellAmount = await this.getAmountOut(sellRouter, token1, token0, buyAmount);
const gasCost = ethers.parseEther("0.005"); // 估算Gas费用
return sellAmount > amountIn + gasCost ? sellAmount - amountIn - gasCost : 0n;
}
private async getAmountOut(
router: string,
tokenIn: string,
tokenOut: string,
amountIn: bigint
): Promise<bigint> {
const routerContract = new ethers.Contract(
router,
[
"function getAmountsOut(uint amountIn, address[] memory path) view returns (uint[] memory amounts)",
],
this.provider
);
const amounts = await routerContract.getAmountsOut(amountIn, [
tokenIn,
tokenOut,
]);
return amounts[1];
}
private async executeArbitrage(
opportunity: ArbitrageOpportunity,
targetBlock: number
) {
console.log("Executing arbitrage...");
try {
const buyOnUniswap = opportunity.dexPath[0] === "uniswap";
const transaction = await this.arbitrageContract.simpleArbitrage.populateTransaction(
opportunity.tokenIn,
opportunity.tokenOut,
opportunity.amountIn,
buyOnUniswap
);
// 使用Flashbots发送
const signedBundle = await this.flashbotsProvider.signBundle([
{
signer: this.wallet,
transaction: transaction,
},
]);
const simulation = await this.flashbotsProvider.simulate(
signedBundle,
targetBlock
);
if ("error" in simulation) {
console.error("Simulation failed:", simulation.error);
return;
}
console.log("Simulation successful, submitting bundle...");
const bundleSubmission = await this.flashbotsProvider.sendRawBundle(
signedBundle,
targetBlock
);
console.log("Bundle submitted:", bundleSubmission.bundleHash);
const resolution = await bundleSubmission.wait();
console.log("Bundle resolution:", resolution);
} catch (error: any) {
console.error("Arbitrage execution failed:", error.message);
}
}
private monitorMempool() {
// 监听待处理交易,寻找sandwich机会
this.provider.on("pending", async (txHash) => {
// 实现sandwich检测逻辑
});
}
}
// 启动MEV Bot
async function main() {
const bot = new MEVBot(
process.env.WSS_RPC_URL!,
process.env.PRIVATE_KEY!,
process.env.ARBITRAGE_CONTRACT!
);
await bot.init();
await bot.start();
console.log("MEV Bot is running...");
}
main().catch(console.error);
6. MEV 防护策略
6.1 用户端防护
// contracts/AntiMEV.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
contract AntiMEVRouter {
IUniswapV2Router02 public immutable router;
// 每个用户的最后交易时间
mapping(address => uint256) public lastTradeTime;
// 最小交易间隔(防止抢跑)
uint256 public constant MIN_TRADE_INTERVAL = 12 seconds; // 1个区块
// 最大滑点保护
uint256 public constant MAX_SLIPPAGE = 50; // 0.5%
event TradeExecuted(
address indexed user,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut
);
constructor(address _router) {
router = IUniswapV2Router02(_router);
}
// 受保护的swap函数
function protectedSwap(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 deadline
) external returns (uint256) {
// 1. 时间间隔检查
require(
block.timestamp >= lastTradeTime[msg.sender] + MIN_TRADE_INTERVAL,
"Trade too frequent"
);
// 2. 获取预期输出量
address[] memory path = new address[](2);
path[0] = tokenIn;
path[1] = tokenOut;
uint256[] memory expectedAmounts = router.getAmountsOut(amountIn, path);
uint256 expectedOut = expectedAmounts[1];
// 3. 计算最小可接受输出(带滑点保护)
uint256 minAmountOut = (expectedOut * (10000 - MAX_SLIPPAGE)) / 10000;
// 4. 转入代币
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
IERC20(tokenIn).approve(address(router), amountIn);
// 5. 执行交易
uint256[] memory amounts = router.swapExactTokensForTokens(
amountIn,
minAmountOut,
path,
msg.sender,
deadline
);
// 6. 更新最后交易时间
lastTradeTime[msg.sender] = block.timestamp;
emit TradeExecuted(msg.sender, tokenIn, tokenOut, amountIn, amounts[1]);
return amounts[1];
}
// 使用TWAP价格而非即时价格
function getTWAPPrice(
address pair,
address token,
uint256 period
) public view returns (uint256) {
// 实现TWAP逻辑
// 这需要记录历史价格数据
return 0; // 简化示例
}
}
6.2 协议端防护
// contracts/MEVProtectedDEX.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract MEVProtectedDEX is ReentrancyGuard {
struct Order {
address trader;
address tokenIn;
address tokenOut;
uint256 amountIn;
uint256 minAmountOut;
uint256 deadline;
uint256 submitBlock;
}
mapping(bytes32 => Order) public orders;
// 订单必须等待的区块数(防止抢跑)
uint256 public constant ORDER_DELAY = 2;
event OrderSubmitted(bytes32 indexed orderId, address indexed trader);
event OrderExecuted(bytes32 indexed orderId, uint256 amountOut);
event OrderCancelled(bytes32 indexed orderId);
// 提交订单(不立即执行)
function submitOrder(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
uint256 deadline
) external returns (bytes32) {
bytes32 orderId = keccak256(
abi.encodePacked(
msg.sender,
tokenIn,
tokenOut,
amountIn,
block.timestamp
)
);
orders[orderId] = Order({
trader: msg.sender,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountIn: amountIn,
minAmountOut: minAmountOut,
deadline: deadline,
submitBlock: block.number
});
// 转入代币
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
emit OrderSubmitted(orderId, msg.sender);
return orderId;
}
// 执行订单(需要等待)
function executeOrder(bytes32 orderId) external nonReentrant {
Order memory order = orders[orderId];
require(order.trader != address(0), "Order not found");
require(block.number >= order.submitBlock + ORDER_DELAY, "Order locked");
require(block.timestamp <= order.deadline, "Order expired");
delete orders[orderId];
// 执行交易逻辑
uint256 amountOut = _swap(
order.tokenIn,
order.tokenOut,
order.amountIn
);
require(amountOut >= order.minAmountOut, "Slippage too high");
IERC20(order.tokenOut).transfer(order.trader, amountOut);
emit OrderExecuted(orderId, amountOut);
}
// 取消订单
function cancelOrder(bytes32 orderId) external {
Order memory order = orders[orderId];
require(order.trader == msg.sender, "Not your order");
require(block.timestamp > order.deadline, "Not expired yet");
delete orders[orderId];
// 退回代币
IERC20(order.tokenIn).transfer(order.trader, order.amountIn);
emit OrderCancelled(orderId);
}
function _swap(
address tokenIn,
address tokenOut,
uint256 amountIn
) private returns (uint256) {
// 简化的swap逻辑
// 实际应该实现完整的AMM
return amountIn; // 简化示例
}
}
7. 监控与分析工具
7.1 MEV 数据分析
// scripts/mev-analytics.ts
import { ethers } from "ethers";
interface MEVTransaction {
blockNumber: number;
txIndex: number;
hash: string;
from: string;
to: string;
value: bigint;
gasPrice: bigint;
gasUsed: bigint;
type: "frontrun" | "sandwich" | "arbitrage" | "liquidation";
profit?: bigint;
}
class MEVAnalytics {
private provider: ethers.JsonRpcProvider;
private mevTransactions: MEVTransaction[] = [];
constructor(rpcUrl: string) {
this.provider = new ethers.JsonRpcProvider(rpcUrl);
}
async analyzeBlocks(startBlock: number, endBlock: number) {
console.log(`Analyzing blocks ${startBlock} to ${endBlock}...`);
for (let i = startBlock; i <= endBlock; i++) {
await this.analyzeBlock(i);
}
this.generateReport();
}
private async analyzeBlock(blockNumber: number) {
const block = await this.provider.getBlock(blockNumber, true);
if (!block || !block.transactions) return;
const txs = block.transactions as ethers.TransactionResponse[];
for (let i = 0; i < txs.length; i++) {
const mevType = await this.detectMEVType(txs, i);
if (mevType) {
const receipt = await this.provider.getTransactionReceipt(txs[i].hash);
this.mevTransactions.push({
blockNumber,
txIndex: i,
hash: txs[i].hash,
from: txs[i].from,
to: txs[i].to || "",
value: txs[i].value,
gasPrice: txs[i].gasPrice || 0n,
gasUsed: receipt?.gasUsed || 0n,
type: mevType,
});
}
}
}
private async detectMEVType(
txs: ethers.TransactionResponse[],
index: number
): Promise<MEVTransaction["type"] | null> {
// 检测sandwich
if (index > 0 && index < txs.length - 1) {
if (this.isSandwich(txs[index - 1], txs[index], txs[index + 1])) {
return "sandwich";
}
}
// 检测arbitrage
if (await this.isArbitrage(txs[index])) {
return "arbitrage";
}
// 检测liquidation
if (await this.isLiquidation(txs[index])) {
return "liquidation";
}
return null;
}
private isSandwich(
tx1: ethers.TransactionResponse,
tx2: ethers.TransactionResponse,
tx3: ethers.TransactionResponse
): boolean {
return tx1.from === tx3.from &&
(tx1.gasPrice || 0n) > (tx2.gasPrice || 0n) &&
(tx3.gasPrice || 0n) > (tx2.gasPrice || 0n);
}
private async isArbitrage(tx: ethers.TransactionResponse): Promise<boolean> {
// 检查是否包含多次swap
// 简化检测逻辑
return false;
}
private async isLiquidation(tx: ethers.TransactionResponse): Promise<boolean> {
// 检查是否调用了liquidation函数
return tx.data.includes("0xbd6d894d"); // liquidate selector
}
private generateReport() {
console.log("\n=== MEV Analysis Report ===\n");
const typeCount = this.mevTransactions.reduce((acc, tx) => {
acc[tx.type] = (acc[tx.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log("MEV Transaction Types:");
for (const [type, count] of Object.entries(typeCount)) {
console.log(` ${type}: ${count}`);
}
const totalGasUsed = this.mevTransactions.reduce(
(sum, tx) => sum + (tx.gasUsed * tx.gasPrice),
0n
);
console.log(`\nTotal Gas Spent: ${ethers.formatEther(totalGasUsed)} ETH`);
console.log(`Total MEV Transactions: ${this.mevTransactions.length}`);
// 导出数据
const fs = require("fs");
fs.writeFileSync(
"mev-report.json",
JSON.stringify(this.mevTransactions, (_, v) =>
typeof v === "bigint" ? v.toString() : v
, 2)
);
console.log("\nDetailed report saved to mev-report.json");
}
}
async function main() {
const analytics = new MEVAnalytics(process.env.RPC_URL!);
const latestBlock = await analytics["provider"].getBlockNumber();
await analytics.analyzeBlocks(latestBlock - 100, latestBlock);
}
main().catch(console.error);
8. 实战案例分析
8.1 历史MEV案例
0xb4e16 清算案例
- 区块: 12345678
- 利润: 32 ETH
- 类型: Compound清算
- 策略: 闪电贷 + 清算奖励
Uniswap V2 套利案例
- 区块: 12456789
- 利润: 5.2 ETH
- 类型: 跨DEX套利
- 路径: Uniswap -> Sushiswap -> Uniswap
NFT 抢跑案例
- 区块: 13567890
- 利润: 15 ETH
- 类型: NFT mint抢跑
- Gas费: 30 ETH
9. 总结与最佳实践
9.1 MEV搜索者建议
技术栈
- 使用高性能RPC节点
- 部署本地以太坊节点
- 使用WebSocket实时监控
- 实现高效的数据结构
策略优化
- 准确的利润计算
- Gas价格优化
- 使用Flashbots避免失败
- 多策略组合
风险管理
- 设置止损机制
- 监控竞争对手
- 预留Gas费缓冲
- 定期审计合约
9.2 普通用户防护建议
交易设置
- 设置合理的滑点保护
- 使用私有RPC
- 分批次交易
- 避免高峰时段
工具使用
- 使用MEV保护的RPC
- 通过Flashbots发送交易
- 使用限价单而非市价单
协议选择
- 选择有MEV保护的协议
- 使用批量拍卖机制
- 考虑L2解决方案
MEV是区块链生态系统中不可避免的现象,理解其原理和防护措施对于DeFi参与者至关重要。