Skip to main content

MEV 实用

在 MEV Theory 中,我们了解了 MEV 是什么,Flashbots 是什么,以及 Flashbots 的一些用例。在这个级别中,我们将学习如何使用 Flashbots 铸造 NFT。这将是一个非常简单的用例,旨在教您如何使用 Flashbots,不一定要盈利。寻找可以使用 MEV 获利的机会是一个难题,而且通常不是公开信息。每个搜索者都在努力做到最好,如果他们确切地告诉你他们正在使用什么策略,他们就是在自取其辱。

本教程的目的只是首先向您展示如何使用 Flashbots 发送交易,其余的取决于您!

构建

让我们建立一个关于如何使用 flashbots 的示例

创建项目

要设置安全帽项目,请打开终端并执行以下命令

npm 初始化——是的 npm install --save-dev 安全帽 如果您在 Windows 机器上,请执行此额外步骤并安装这些库:)

npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers 在安装 Hardhat 的同一目录中运行:

npx 安全帽 选择创建基本示例项目 为已指定的安全帽项目根按 Enter 如果要添加 .gitignore,请按回车键 按 Enter 键是否要使用 npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers) 安装此示例项目的依赖项? 现在你有一个安全帽项目准备好了!

让我们安装更多的依赖项来帮助我们进一步

安装依赖

npm install @flashbots/ethers-provider-bundle @openzeppelin/contracts dotenv

创建合约

让我们从创建 FakeNFT 合约开始。在你的 contracts 文件夹下创建一个名为 FakeNFT.sol 的新文件,并向其中添加以下代码行

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract FakeNFT is ERC721 {

uint256 tokenId = 1;
uint256 constant price = 0.01 ether;
constructor() ERC721("FAKE", "FAKE") {
}

function mint() public payable {
require(msg.value == price, "Ether sent is incorrect");
_mint(msg.sender, tokenId);
tokenId += 1;
}
}

这是一个非常简单的 ERC-721 合约,允许以 0.01 ETH 铸造 NFT。

修改配置

现在让我们用以下代码行替换 hardhat.config.js 中的代码

require("@nomiclabs/hardhat-waffle");
require("dotenv").config({ path: ".env" });

const QUICKNODE_RPC_URL = process.env.QUICKNODE_RPC_URL;

const PRIVATE_KEY = process.env.PRIVATE_KEY;

module.exports = {
solidity: "0.8.4",
networks: {
goerli: {
url: QUICKNODE_RPC_URL,
accounts: [PRIVATE_KEY],
},
},
};

请注意,我们在这里使用的是 goerli,它是一个以太坊测试网,类似于 Rinkeby 和 Ropsten,但 Flashbots 唯一支持。

添加环境变量

现在是时候设置一些环境变量了,在你的根文件夹下创建一个新文件 .env ,并在其中添加以下代码行。

QUICKNODE_RPC_URL="QUICKNODE_RPC_URL"
PRIVATE_KEY="YOUR-PRIVATE-KEY"
QUICKNODE_WS_URL="QUICKNODE_WS_URL"

要获取您的 QUICKNODE_RPC_URL 和 QUICKNODE_WS_URL,请转到 Quicknode,登录并创建一个新端点。选择 Ethereum,然后选择 Goerli,并在 Discover 模式下创建端点以保留在免费层上。

现在复制 HTTP Provider url 并将其粘贴到 QUICKNODE_RPC_URL 的位置,然后复制 WSS Provider 并将其粘贴到 QUICKNODE_WS_URL 的位置。

将 YOUR-PRIVATE-KEY 替换为您拥有 Goerli Ether 的帐户的私钥,以获得一些 Goerli ether 试试这个水龙头

添加脚本

现在是时候编写一些代码来帮助我们与 Flashbot 交互了。

在 scripts 文件夹下创建一个新文件并将其命名为 flashbots.js 并向其中添加以下代码行

const {
FlashbotsBundleProvider,
} = require("@flashbots/ethers-provider-bundle");
const { BigNumber } = require("ethers");
const { ethers } = require("hardhat");
require("dotenv").config({ path: ".env" });

async function main() {
// Deploy FakeNFT Contract
const fakeNFT = await ethers.getContractFactory("FakeNFT");
const FakeNFT = await fakeNFT.deploy();
await FakeNFT.deployed();

console.log("Address of Fake NFT Contract:", FakeNFT.address);

// Create a Alchemy WebSocket Provider
const provider = new ethers.providers.WebSocketProvider(
process.env.QUICKNODE_WS_URL,
"goerli"
);

// Wrap your private key in the ethers Wallet class
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Create a Flashbots Provider which will forward the request to the relayer
// Which will further send it to the flashbot miner
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
signer,
// URL for the flashbots relayer
"https://relay-goerli.flashbots.net",
"goerli"
);

provider.on("block", async (blockNumber) => {
console.log("Block Number: ", blockNumber);
// Send a bundle of transactions to the flashbot relayer
const bundleResponse = await flashbotsProvider.sendBundle(
[
{
transaction: {
// ChainId for the Goerli network
chainId: 5,
// EIP-1559
type: 2,
// Value of 1 FakeNFT
value: ethers.utils.parseEther("0.01"),
// Address of the FakeNFT
to: FakeNFT.address,
// In the data field, we pass the function selctor of the mint function
data: FakeNFT.interface.getSighash("mint()"),
// Max Gas Fes you are willing to pay
maxFeePerGas: BigNumber.from(10).pow(9).mul(3),
// Max Priority gas fees you are willing to pay
maxPriorityFeePerGas: BigNumber.from(10).pow(9).mul(2),
},
signer: signer,
},
],
blockNumber + 1
);

// If an error is present, log it
if ("error" in bundleResponse) {
console.log(bundleResponse.error.message);
}
});
}

main();

现在让我们尝试了解这些代码行中发生了什么。

在最初的代码行中,我们部署了我们编写的 FakeNFT 合约。

之后,我们创建了一个 Quicknode WebSocket 提供者、一个签名者和一个 Flashbots 提供者。注意我们这次创建 WebSocket 提供者的原因是因为我们想创建一个套接字来监听 Goerli 网络中的每个新块。正如我们之前使用的那样,HTTP 提供程序在请求-响应模型上工作,其中客户端向服务器发送请求,服务器做出响应。然而,在 WebSockets 的情况下,客户端打开一次与 WebSocket 服务器的连接,然后只要连接保持打开状态,服务器就会不断地向它们发送更新。因此客户端不需要一次又一次地发送请求。

这样做的原因是,Goerli 网络中的所有矿工都不是 flashbot 矿工。这意味着对于某些区块,您发送的交易包可能不会被包含在内。

作为一个原因,我们监听每个区块并在每个区块中发送一个请求,以便当 coinbase 矿工(当前区块的矿工)是 flashbots 矿工时,我们的交易被包含在内。

// Create a Alchemy WebSocket Provider
const provider = new ethers.providers.WebSocketProvider(
process.env.QUICKNODE_WS_URL,
"goerli"
);

// Wrap your private key in the ethers Wallet class
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Create a Flashbots Provider which will forward the request to the relayer
// Which will further send it to the flashbot miner
const flashbotsProvider = await FlashbotsBundleProvider.create(
provider,
signer,
// URL for the goerli flashbots relayer
"https://relay-goerli.flashbots.net",
"goerli"
);

在初始化提供者和签名者之后,我们使用我们的提供者来监听阻塞事件。每次调用块事件时,我们都会打印块号并发送一束交易来铸造 NFT。请注意,我们发送的捆绑包可能会或可能不会包含在当前区块中,具体取决于 coinbase 矿工是否是 flashbot 矿工。

现在要创建交易对象,我们为 Goerli 指定 chainId 为 5,type 为 2,因为我们将使用 Post-London Upgrade 气体模型,即 EIP-1559。要刷新您对这个气体模型如何工作的记忆,请查看大二学生的气体模块。

我们指定值为 0.01,因为这是铸造 1 NFT 的数量,而 to 地址是 FakeNFT 合约的地址。

现在对于数据,我们需要指定函数选择器,它是名称的 Keccak-256 (SHA-3) 散列的前四个字节和函数的参数这将确定我们试图调用哪个函数,在我们的例子中, 这将是 mint 函数。

然后我们指定 maxFeePerGas 和 maxPriorityFeePerGas 分别为 3 GWEI 和 2 GWEI。请注意,我在这里得到的值来自查看之前在网络中挖掘的交易以及他们使用的 Gas Fees。

另外,1 GWEI = 10WEI = 1010^8 = 10^9

我们希望交易在下一个区块中被挖掘,因此我们将当前区块号加 1 并发送这组交易。

发送包后,我们得到一个 bundleResponse ,我们检查是否有错误,如果是,我们记录它。

现在请注意,获得响应并不能保证我们的包是否会包含在下一个块中。要检查它是否会包含在下一个块中,您可以使用 bundleResponse.wait() 但为了本教程的目的,我们将耐心等待几个块并观察。

provider.on("block", async (blockNumber) => {
console.log("Block Number: ", blockNumber);
// Send a bundle of transactions to the flashbot relayer
const bundleResponse = await flashbotsProvider.sendBundle(
[
{
transaction: {
// ChainId for the Goerli network
chainId: 5,
// EIP-1559
type: 2,
// Value of 1 FakeNFT
value: ethers.utils.parseEther("0.01"),
// Address of the FakeNFT
to: FakeNFT.address,
// In the data field, we pass the function selctor of the mint function
data: FakeNFT.interface.getSighash("mint()"),
// Max Gas Fees you are willing to pay
maxFeePerGas: BigNumber.from(10).pow(9).mul(3),
// Max Priority gas fees you are willing to pay
maxPriorityFeePerGas: BigNumber.from(10).pow(9).mul(2),
},
signer: signer,
},
],
blockNumber + 1
);

// If an error is present, log it
if ("error" in bundleResponse) {
console.log(bundleResponse.error.message);
}
});

现在要运行此代码,在指向根目录的终端中执行以下命令:

npx hardhat run scripts/flashbots.js --network goerli

在您的终端上打印地址后,转到 Goerli Etherscan 并不断刷新页面,直到您看到 Mint 交易出现(注意它需要一些时间才能出现,因为 flashbot 矿工必须是 coinbase 矿工才能包含我们的捆绑包 在块中)

Boom 🤯 我们现在学会了如何使用 flashbots 来铸造 NFT,但你可以做的更多

参考