普通视图

发现新文章,点击刷新页面。
今天 — 2025年7月4日首页

Mempool 监听与抢先交易实战:从原理到 Flashbots 套利脚本(Uniswap V3 + Sepolia 测试网)

作者 木西
2025年7月3日 23:12

前言

未确认交易驻留内存池(Mempool)期间,释放链上意图:DEX 订单、清算触发、NFT 铸造。套利机器人(Searchers)通过实时监听、Gas 竞价及私有中继(Flashbots)实施原子套利(Atomic Arbitrage),在区块打包前完成价值捕获。

本文系统阐述:

  1. Mempool 监听协议:基于 WebSocket 的未决交易捕获机制,关键字段解析(from/to/value/data)与信号识别。
  2. 抢先交易工程:以 Uniswap V3 swapExactETHForTokens(函数选择器 0x7ff36ab5)为例,构建高优先级 EIP-1559 交易并实施交易插队(Transaction Insertion)。
  3. Flashbots 原子套利:利用私有中继提交交易 Bundle,实现 ETH→Token→ETH 无滑点循环套利,规避公开 Mempool 暴露。
  4. 合规与道德框架:技术能力与监管红线——在 MEV(矿工可提取价值)生态中平衡效率与公平,避免市场操纵。

监听Mempool(内存池)

主要作用:获取实时数据、优化交易执行、发现套利机会和保障安全

使用场景 监听Mempool的作用
套利机器人 发现DEX价格差异,执行闪电贷套利
清算机器人 监控借贷协议的健康因子,自动清算抵押品
NFT 抢购 监听NFT铸造交易,抢先提交购买请求
交易所风控 检测异常提现或可疑合约交互,冻结可疑资金

实例

说明:hardhat或Ganache本地节点未启用 WebSocket 或 HTTP 订阅所以本例子使用infura

未决交易:用户发出但没被矿工打包上链的交易

# sepolia链
const SEPOLIA_WSSURL = 'wss://sepolia.infura.io/ws/v3/{YourAPIKey}';//YourAPIKey在注册infura中获取apikey
const provider = new ethers.WebSocketProvider(SEPOLIA_WSSURL);//获取provider
//监测限制 实现一个节流限制请求频率
function ThrottleFn(fn, delay) {
    let timer;
    return function(){
        if(!timer) {
            fn.apply(this, arguments)
            timer = setTimeout(()=>{
                clearTimeout(timer)
                timer = null
            },delay)
        }
    }
}
//监听请求
let i = 0
provider.on("pending", ThrottleFn(async (txHash) => {
    if (txHash && i <= 50) {
        // 获取tx详情
        let tx = await provider.getTransaction(txHash);
        console.log(`\n[${(new Date).toLocaleTimeString()}] 监听Pending交易 ${i}: ${txHash} \r`);//获取交易hash
        console.log(tx);//读取交易详情 可以看到`blockHash`,`blockNumber`,和`transactionIndex`都为空 我们可以获取from、to、value等信息,我们可以对其进行挖掘分析等后续操作
        i++
        }
}, 1000))

抢先交易脚本

抢先交易的核心:

比你快,比你贵通过,更快的网络监听更高的 Gas 费出价,在目标交易被矿工打包前插入自己的交易,从而截获利润或优先执行,一句话总结用更快的代码和更高的 Gas 费,在目标交易上链前插入自己的交易,从而截获利润

实例

const signature = "swapExactETHForTokens(uint256,address[],address,uint256)";
// 2. 计算 Keccak-256 哈希,取前 4 字节(8 个十六进制字符)
const selector = ethers.id(signature).slice(0, 10);
console.log(selector); // 输出: 0x7ff36ab5
  • 代码实例
const { ethers } = require("hardhat");

// 1. 配置
const PRIVATE_KEY = "ac09xxxxxxxxx";//钱包私钥
const TARGET_CONTRACT = "0xC532a74256D3Db42D0Bf7a0400aEFDbad7694008";//使用的是Sepolia路由器 交换执行接口 Uniswap V2/V3/测试网(Sepolia)路由器
const TARGET_SELECTOR = "0x7ff36ab5";        // swapExactETHForTokens 选择器
// 2. 初始化
const SEPOLIA_MAINNET_WSSURL = 'wss://sepolia.infura.io/ws/v3/{YouAPIKey}';
const provider = new ethers.WebSocketProvider(SEPOLIA_MAINNET_WSSURL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);//钱包

// 3. 监听 pending 交易
provider.on("pending", async (txHash) => {
  try {
    const tx = await provider.getTransaction(txHash);
    if (!tx || tx.to?.toLowerCase() !== TARGET_CONTRACT.toLowerCase()) return;
    if (!tx.data.startsWith(TARGET_SELECTOR)) return;

    console.log(`[${new Date().toLocaleTimeString()}] 发现目标交易: ${txHash}`);

    // 4. 构造抢先交易
    const frontRunTx = {
      to: tx.to,
      data: tx.data,
      value: tx.value,
      maxPriorityFeePerGas: tx.maxPriorityFeePerGas * 1.2, // 提高 20%
      maxFeePerGas: tx.maxFeePerGas * 1.2,
      nonce: await wallet.getNonce(), // 使用钱包当前 nonce
      chainId: await provider.getNetwork().then(n => n.chainId),
      type: 2, // EIP-1559
    };

    // 5. 签名并广播
    
    const signedTx = await wallet.signTransaction(frontRunTx);
    const broadcast = await provider.sendTransaction(signedTx);
    console.log(`抢先交易已广播: ${broadcast.hash}`);
  } catch (error) {
    console.error("处理交易时出错:", error.message);
  }
});

console.log("开始监听 mempool...");
# 以上就是抢先交易dome完整的实例

Flashbots

什么是Flashbots:

通过私有通道、交易捆绑、拍卖机制和收益共享协议,为以太坊生态提供了一套透明、高效且公平的 MEV 解决方案。它既保护了普通用户免受抢跑和尾随,又为搜索者和矿工创造了新的收益来源,同时推动了区块构建的去中心化和网络整体效率的提升

Flashbots 解决的核心问题

问题类型 具体描述
抢跑与尾随 通过私有通道隐藏交易意图,防止交易被抢先或尾随,降低普通用户被剥削风险。
Gas 价格战 将公开 mempool 的 Gas 竞价转为 Bundle 小费,减少网络拥堵和费用波动。
失败交易成本 Bundle 内交易原子执行,失败即丢弃,不消耗链上 Gas,避免无效交易损失。
MEV 中心化 开放竞争性区块构建市场,防止少数实体垄断 MEV 提取,促进去中心化。

Flashbots 典型应用场景

场景类型 应用示例
套利机器人 跨 DEX 价格差套利,通过 Bundle 快速执行无滑点交易。
清算机器人 监控借贷协议健康因子,抢先清算抵押品,保障协议偿付能力。
隐私交易 机构/大额交易使用 Flashbots Protect 隐藏细节,避免狙击攻击。
验证者收益优化 质押节点运行 MEV-Boost,接入多构建者,最大化区块奖励并提升抗审查能力。

实例

说明:主要在sepolia上测试,要保证我们的交易钱包有足够的eth,要注意:

const { ethers } = require("hardhat");
const { FlashbotsBundleProvider } =require('@flashbots/ethers-provider-bundle');
async function main() {
  const AUTH_KEY="ac097xxxxxxxx";//账号1 钱包  声誉私钥(仅用于 Flashbots 签名)
  const sepolia_private="5025c087xxxxxx";//sepolia_private 私钥交易钱包(含资金,用于发送交易)

  // 1. 普通 RPC 提供者(Alchemy、Infura 等)
 
  const provider = new ethers.JsonRpcProvider("https://sepolia.infura.io/v3/{object——key}");

  // 2. 声誉私钥(仅用于签名请求,不存储资金)
  const authSigner = new ethers.Wallet(AUTH_KEY, provider);//声誉私钥

  // 3. 创建 Flashbots 提供者
  const flashbotsProvider = await FlashbotsBundleProvider.create(
    provider,
    authSigner,
    'https://relay-sepolia.flashbots.net', // Sepolia 测试网中继
    'sepolia'
  );
// PRIVATE_KEY
  // 4. 钱包(含资金,用于签名交易)
  const wallet = new ethers.Wallet(sepolia_private, provider);//钱包里要有eth余额不能为0

  // 5. 构造两笔 EIP-1559 交易
  const currentNonce = await wallet.getNonce('latest'); // 强制获取链上最新 nonce
  
  const tx1 = {
    to: '0x3C44CdDdxx'// 要转入的钱包地址
    value: ethers.parseEther('0.0001'),
    maxFeePerGas: ethers.parseUnits('100', 'gwei'),
    maxPriorityFeePerGas: ethers.parseUnits('50', 'gwei'),
    type: 2,
    chainId: 11155111, // Sepolia
    nonce: currentNonce,
  };

  const tx2 = {
    to: '0x3C44CdDdxx',//要转入的钱包地址
    value: ethers.parseEther('0.0002'),
    maxFeePerGas: ethers.parseUnits('100', 'gwei'),
    maxPriorityFeePerGas: ethers.parseUnits('50', 'gwei'),
    type: 2,
    chainId: 11155111,
    nonce: currentNonce + 1,
  };

  // 6. 组装 Bundle
  const bundle = [
    { signer: wallet, transaction: tx1 },
    { signer: wallet, transaction: tx2 },
  ];

  // 7. 获取目标区块号(下一个区块)
  const blockNumber = await provider.getBlockNumber();
  const targetBlockNumber = blockNumber + 1;
  //定义函数解决JSON.stringify(simulation, null, 2)超大数问题
  function serializeBigInt(obj) {
    return JSON.stringify(obj, (key, value) =>
      typeof value === 'bigint' ? value.toString() : value
    );
  }//
  // 8. 模拟
  const signedBundle = await flashbotsProvider.signBundle(bundle);
  const simulation = await flashbotsProvider.simulate(signedBundle, targetBlockNumber);
  console.log('Simulation result:', serializeBigInt(simulation));

  // 9. 发送
  const sendResult = await flashbotsProvider.sendBundle(bundle, targetBlockNumber);
  console.log('Bundle hash:', sendResult.bundleHash);

  // 10. 等待区块包含
  const waitResult = await sendResult.wait();
  console.log('Wait result:', waitResult);

  // 11. 获取收据
  const receipts = await sendResult.receipts();
  console.log('Receipts:', receipts);
}

main().catch(console.error);

总结

以上就是对Mempool 监听、抢先交易与 Flashbots 套利实战的全部内容。

昨天以前首页

基于 ethers.js 的区块链事件处理与钱包管理实践指南

作者 木西
2025年7月2日 18:40

前言

本文将围绕 事件检索与监听HD 钱包批量生成与加密存储静态调用与 callData 构造ERC 标准合约识别 等关键场景,结合代码示例与最佳实践,展示如何利用 ethers.js 完成从基础交互到高级功能的完整流程。无论是初学者还是有经验的开发者,都能通过本指南快速掌握 ethers.js 的核心用法,并将其应用于实际项目中

Event事件

检索事件

const { ethers } = require("hardhat");
async function SearchEvent() {
    try {
        const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
        const signer = await provider.getSigner();
        const TokenAddress = "0xxxxx";//合约地址
        const TokenABI =[]//合约的abi;
        const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//创建合约
        //读取合约
        const name = await TokenContract.name();
        console.log("Contract Name:", name);
        const symbol = await TokenContract.symbol();
        console.log("Contract Symbol:", symbol);
        const totalSupply = await TokenContract.totalSupply();
        console.log("Total Supply:", totalSupply.toString());
        //合约转eth
        const arr1="0xxxxxxxx"
        await TokenContract.transfer(arr1,10);//给arr1转10;
        
        const block = await provider.getBlockNumber()//得到当前block
         const transferEvents = await TokenContract.queryFilter('Transfer', block - x, block);//检索合约Transfer,从block - x,到block之间的解析事件
          console.log(`Transfer事件数量: ${transferEvents.length}`);
         //transferEvents是个数组,我们可以解析他的参数
         console.log(...transferEvents[0].args);//返回form,to ,value
       }catch (error) {
        console.error("Error:", error);
    }
    }

监听事件

//以上同上
TokenContract.on("Transfer", (from, to, value, event) => {
            console.log(`Transfer事件触发:`);
            console.log(`From: ${from}`);
            console.log(`To: ${to}`);
            console.log(`Value: ${value.toString()}`);
            console.log(` 从 ${from}=> 到 ${to} = ${value.toString()}`); 
            console.log(`Event Details:`, event);   
        });

过滤事件

设置过滤规则:contract.filters.EVENT_NAME( ...args )说明:EVENT_NAME:过滤事件,...args:过滤规则

基础规则汇总

规则 含义 示例
null 该位置不限制,匹配任意值 contract.filters.Transfer(null, addr)
单个值 必须完全匹配 contract.filters.Transfer(addr)
数组 至少匹配数组中任意一个值 contract.filters.Transfer(null, [addr1, addr2])
以上代码如上
//设置规则
# 规则1
let addr1="0xf39Fd6e51aad88F6F4ce6axxxxxxx"
let addr2="0x70997970C51812dc3A010C7xxxxxx"
let addr3="0xb0997970C51812dcxxxxxxxxxxxxx"
let rule1 = TokenContract.filters.Transfer(addr1);//过滤来自`addr1`地址的`Transfer`事件
let rule2 = TokenContract.filters.Transfer(null,addr2);//过滤所有发给 addr2`地址的`Transfer`事件
let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 `addr1`发给`addr2`的`Transfer`事件
let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 `addr1`发给`addr2`的`Transfer`事件
let rule4 = TokenContract.filters.Transfer(null,[addr2,addr3]);//过滤所有发给 addr2`地址的或者addr3`的Transfer`事件
# 其他就是各种组合使用了
# 过滤使用
TokenContract.on(rule1, (res) => {
  console.log('---------监听开始过滤--------');
  console.log(
    `${res.args[0]} -> ${res.args[1]} ${res.args[2]}`
  )
})
# 其他同上 把过滤规则给监听事件即可

批量生成HD钱包

BIP汇总
BIP编号 主要用途 典型格式示例
BIP-32 HD 钱包路径 m/44'/0'/0'/0/0
BIP-39 助记词生成种子 12/24 个单词
BIP-44 多币种路径 m/44'/60'/0'/0/0
BIP-49 隔离见证兼容地址 m/49'/0'/0'/0/0
BIP-84 原生隔离见证地址 m/84'/0'/0'/0/0
BIP-173 Bech32 地址编码 bc1q...
BIP-350 Taproot 地址编码 bc1p...
以BIP-44为例代码实践
  • 助记词生成
 const mnemonic = ethers.Mnemonic.entropyToPhrase(ethers.randomBytes(32))
  • 创建HD基钱包
    BIP-44
    基路格式:"m / purpose' / coin_type' / account' / change" 参数说明
    • m:主密钥(Master Key)
    • purpose':固定为 44'(表示遵循 BIP-44 多账户标准)
    • coin_type':币种标识(如 0' = BTC,60' = ETH,501' = SOL)详细可查看SLIP-44
    • account':账户编号(从 0' 开始)
    • change:比特币专用(0 = 外部地址,1 = 找零地址);其他链通常为 0
    • address_index:地址索引(从 0 开始)
 # BIP-44
 // 基路径:
 const basePath = "44'/60'/0'/0"
 # 生成第一对外的链接
 const baseWallet = ethers.HDNodeWallet.fromPhrase(mnemonic, basePath)
  • 批量生成
const WalletNumber = 10;//钱包数
 for (let i = 0; i < WalletNumber; i++) {
     let NewBaseWallet = baseWallet.derivePath(i.toString());
     console.log(`第${i+1}个钱包地址: ${baseWalletNew.address}`)
     wallets.push(baseWalletNew);//生成10个钱包
 }
console.log("钱包地址列表:", wallets.map(wallet => wallet.address));
  • 加密JSON保存
async function saveWalletJson() {
 const wallet = ethers.Wallet.fromPhrase(mnemonic);//助记词
 console.log("通过助记词创建钱包:")
 console.log(wallet)
 // 加密json用的密码,可以更改成别的
 const pwd = "XXXX";
 const json = await wallet.encrypt(pwd)
 console.log("钱包的加密json:")
 console.log(json)
 require("fs").writeFileSync("keystoreBatch.json", json);//在当前文件夹下生成一个 keystoreBatch.json文件
 }
 saveWalletJson();
  • 通过加密json读取钱包信息
async function ReadWalletJson() {
console.log("开始读取json文件");
const json=require("fs").readFileSync("keystoreBatch.json", "utf8");
const walletJson =await ethers.Wallet.fromEncryptedJson(json, "xxx");//生成json时设置的密码
console.log("Wallet from JSON:",walletJson);
console.log("Address:", walletJson.address);
console.log("Private Key:", walletJson.privateKey);
console.log("Mnemonic:", walletJson.mnemonic.phrase);
}
ReadWalletJson();

staticCall和callStatic:

名称 所属模块 作用 返回值 适用场景
staticCall ethers.Contract 实例方法 只读方式 调用合约函数,不修改状态 函数返回值 任何函数(读/写)
callStatic ethers.Contract 实例方法(v6 新增) 只读方式 调用合约函数,不修改状态 函数返回值 任何函数(读/写)
# 代码实例
# staticCall
const from="0xf39xxx"
const to="0x70xxx"
const result = await TokenContract.transfer.staticCall(to,10,{  
                     // 可选 overrides
                    from: from, // 指定调用者(模拟不同账户)
                });
                console.log('模拟结果:', result);
# callStatic
const result = await TokenContract.transfer.staticCall(to,10,{                 
                    // 可选 overrides
                    from: from, // 指定调用者(模拟不同账户)
                });
                console.log('模拟结果:', result);

callData

  • 接口abi:infce=new ethers.Interface(abi);//两者是一样的功能

  • callData:infce=TokenContract.interface;//两者是一样的功能

const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxx";//合约地址
const TokenABI =[];//abi
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);
const param = TokenContract.interface.encodeFunctionData(
    "balanceOf",
    ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]
  );
  console.log("param:", param);
  const tx = {
    to: TokenAddress,
    data: param
}
// 发起交易,可读操作(view/pure)可以用 provider.call(tx)
const balanceWETH = await provider.call(tx)
console.log(`存款前WETH持仓: ${ethers.formatEther(balanceWETH)}\n`)

encodeFunctionData

const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxxxxx";//合约地址
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//构造合约
# 使用合约的transfer 向0x70997970C51812dc3A010C7d01b50e0d17dc79C8 转10n
const calldata = TokenContract.interface.encodeFunctionData('transfer', [
  '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // 收款地址
  10n                                           // 转账数量 (BigInt)
]);
console.log(calldata)//生成callData
const wallet = new ethers.Wallet("钱包的私钥", provider);
const tx = await wallet.sendTransaction({
  to: "0x5Fxxxxxxx",//合约地址
  data: calldata,
});
await tx.wait();
console.log("交易成功生成的txHash:", tx.hash);
//通过交易hash 
//交易的详细信息
const hash = await provider.getTransaction(tx.hash);
//交易收据
const receipt = await provider.getTransactionReceipt(tx.hash);

识别ERC20、ERC721、ERC115标准合约

识别关键说明:所有现代标准(ERC721、ERC1155)都实现了 ERC165,通过 supportsInterface(bytes4 interfaceId) 函数声明支持的接口,ERC20 不支持 ERC165

  • ERC20

    说明:识别关键ERC20不是基于ERC165,但是ERC20包含totalSupply,识别关键通过totalSupply
    const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
    const signer = await provider.getSigner();//
    const TokenAddress = "0x5Fbxxxxx";//合约地址
    const TokenABI = []//abi
    const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//创建合约
    const totalSupplyValue=await TokenContract.totalSupply(); 
    console.log(totalSupplyValue)//说明是ERC20
    
  • ERC721

    说明:识别关键是ERC721基于ERC165,ERC165标准包含supportsInterface(bytes4 interfaceId)
     创建合约如上
     const isERC721 = await contract.supportsInterface("0x80ac58cd");
     console.log(isERC721); // true 或 false
    
  • ERC1155

    说明:识别关键是ERC721基于ERC165,ERC165标准包含supportsInterface(bytes4 interfaceId)
     创建合约如上
     const isERC721 = await contract.supportsInterface("0xd9b67a26");
     console.log(isERC721); // true 或 false
    
  • 总结

    调用函数/方法 返回值 识别结果 备注
    supportsInterface(0x80ac58cd) true ERC721 NFT 标准接口标识符
    supportsInterface(0xd9b67a26) true ERC1155 多代币标准接口标识符
    totalSupply() 等函数调用成功 成功 ERC20 同质化代币标准(无 ERC165)

总结

以上就是系统介绍了使用 ethers.js 进行区块链开发的关键技术,涵盖事件处理、钱包管理、合约交互及标准识别四大核心模块,并通过代码示例与最佳实践提供完整解决方案;

❌
❌