如何从内存池(Mem Pool)筛选ERC721 Mint交易?
从内存池筛选ERC721 Mint交易的解决方案
核心思路
由于Pending交易未上链,无法直接获取其触发的Transfer事件,因此需通过解析交易Calldata结合ERC721合约规则来判断:
- 先验证目标合约是否为ERC721(通过ERC165的
supportsInterface方法) - 模拟交易在
pending区块执行,检查是否会产生从零地址转出的Transfer事件(这是ERC721 Mint操作的核心特征)
关键判断逻辑
ERC721的Mint操作本质是将Token从0x0000000000000000000000000000000000000000(零地址)转移到目标地址,无论公开方法名是什么,最终都会触发Transfer(address(0), recipient, tokenId)事件。通过模拟pending交易执行并捕获日志,即可判断是否为Mint操作。
Go 实现示例
步骤1:订阅Pending交易并获取交易详情
package main import ( "context" "fmt" "math/big" "strings" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient/gethclient" ) func main() { // 连接以太坊WS节点 client, err := ethclient.Dial("wss://your-node-url") if err != nil { panic(err) } gethCli := gethclient.New(client) // 订阅Pending交易 txCh := make(chan common.Hash, 1000) sub, err := client.SubscribePendingTransactions(context.Background(), txCh) if err != nil { panic(err) } defer sub.Unsubscribe() for { select { case txHash := <-txCh: tx, isPending, err := client.TransactionByHash(context.Background(), txHash) if err != nil || !isPending || tx.To() == nil { continue } contractAddr := *tx.To() // 验证是否为ERC721合约 isERC721, err := isERC721Contract(client, contractAddr) if err != nil || !isERC721 { continue } // 判断是否为Mint交易 isMint, err := isERC721MintTx(gethCli, tx) if err != nil || !isMint { continue } fmt.Printf("Found ERC721 Mint Pending Tx: %s\n", txHash.Hex()) case err := <-sub.Err(): panic(err) } } }
步骤2:通过ERC165验证ERC721合约
const erc721InterfaceID = "0x80ac58cd" func isERC721Contract(client *ethclient.Client, addr common.Address) (bool, error) { // 构造ERC165调用ABI erc165ABI, err := abi.JSON(strings.NewReader(`[{"constant":true,"inputs":[{"name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"type":"function"}]`)) if err != nil { return false, err } input, err := erc165ABI.Pack("supportsInterface", common.HexToBytes(erc721InterfaceID[2:])) if err != nil { return false, err } // 调用合约方法 result, err := client.CallContract(context.Background(), ethclient.CallMsg{ To: &addr, Data: input, }, nil) if err != nil { return false, err } var supports bool err = erc165ABI.UnpackIntoInterface(&supports, "supportsInterface", result) return supports, err }
步骤3:模拟交易执行并检查Transfer事件
// ERC721 Transfer事件签名哈希 var transferEventSig = common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") func isERC721MintTx(client *gethclient.Client, tx *types.Transaction) (bool, error) { sender, err := types.Sender(types.NewEIP155Signer(tx.ChainId()), tx) if err != nil { return false, err } // 模拟pending区块执行交易,捕获日志 msg := ethclient.CallMsg{ From: sender, To: tx.To(), Data: tx.Data(), Value: tx.Value(), Gas: tx.Gas(), GasPrice: tx.GasPrice(), } logs, err := client.CallContractWithLogs(context.Background(), msg, big.NewInt(-1)) // -1代表pending区块 if err != nil { return false, err } for _, log := range logs { if log.Topics[0] == transferEventSig { // 解析转出地址,判断是否为零地址 fromAddr := common.BytesToAddress(log.Topics[1].Bytes()[12:]) if fromAddr == common.Address{} { return true, nil } } } return false, nil }
JavaScript 实现示例
步骤1:订阅Pending交易并获取交易详情
const { ethers } = require("ethers"); async function run() { // 连接以太坊WS节点 const provider = new ethers.WebSocketProvider("wss://your-node-url"); // 监听Pending交易 provider.on("pending", async (txHash) => { try { const tx = await provider.getTransaction(txHash); if (!tx || !tx.to) return; // 跳过合约部署交易 const contractAddr = tx.to; // 验证是否为ERC721合约 const isERC721 = await checkERC721Contract(provider, contractAddr); if (!isERC721) return; // 判断是否为Mint交易 const isMint = await checkERC721MintTx(provider, tx); if (!isMint) return; console.log(`Found ERC721 Mint Pending Tx: ${txHash}`); } catch (err) { console.error(err); } }); } run();
步骤2:通过ERC165验证ERC721合约
const ERC165_ABI = ["function supportsInterface(bytes4 interfaceID) external view returns (bool)"]; const ERC721_INTERFACE_ID = "0x80ac58cd"; async function checkERC721Contract(provider, contractAddr) { const contract = new ethers.Contract(contractAddr, ERC165_ABI, provider); return await contract.supportsInterface(ERC721_INTERFACE_ID); }
步骤3:模拟交易执行并检查Transfer事件
const TRANSFER_EVENT_ABI = ["event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)"]; async function checkERC721MintTx(provider, tx) { const txData = { from: await tx.getSenderAddress(), to: tx.to, data: tx.data, value: tx.value, gasLimit: tx.gasLimit, gasPrice: tx.gasPrice }; // 模拟pending区块执行,获取日志 const logs = await provider.call(txData, "pending"); const iface = new ethers.Interface(TRANSFER_EVENT_ABI); for (const log of logs) { try { const parsed = iface.parseLog(log); if (parsed.args.from === ethers.ZeroAddress) { return true; } } catch (e) { // 非Transfer事件,跳过 continue; } } return false; }
注意事项
- 需使用支持
pending区块调用的以太坊节点(如自部署Geth/Erigon节点,或付费节点服务) - 模拟交易执行可能存在一定延迟,需根据节点性能调整处理逻辑
- 部分合约可能使用自定义Mint逻辑,但只要最终触发从零地址转出的Transfer事件,该方法即可有效识别
内容的提问来源于stack exchange,提问作者THJA JUNGLE
相关产品推荐
相关产品推荐

