Last active
May 21, 2024 04:11
-
-
Save Quanhua-Guan/fe2831acfbd09db94bbe4c021c8d4f5d to your computer and use it in GitHub Desktop.
A Flashbots demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ethers } from "ethers"; | |
import { | |
FlashbotsBundleProvider, | |
FlashbotsBundleResolution, | |
} from "@flashbots/ethers-provider-bundle"; | |
//const GWEI = 10n ** 9n; | |
const CHAIN_ID = 1;//11155111; // goerli测试网,如果用主网,chainid 改为 1 | |
// 1. 普通rpc (非flashbots rpc) | |
const ALCHEMY_GOERLI_URL = 'xxx'; | |
const provider = new ethers.JsonRpcProvider(ALCHEMY_GOERLI_URL); | |
// 2. flashbots声誉私钥,用于建立“声誉”,详情见: https://docs.flashbots.net/flashbots-auction/searchers/advanced/reputation | |
// !!!注意: 这个账户,不要储存资金,也不是flashbots主私钥。 | |
const authKey = '0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f88' | |
const authSigner = new ethers.Wallet(authKey, provider) | |
function bigIntReplacer(key, value) { | |
if (typeof value === 'bigint') { | |
return value.toString(); | |
} else { | |
return value; | |
} | |
} | |
const main = async () => { | |
// 3. flashbots rpc(goerli 测试网),用于发送交易 | |
const flashbotsProvider = await FlashbotsBundleProvider.create( | |
provider, | |
authSigner, | |
// 使用主网 Flashbots,需要把下面两行删去 | |
//'https://relay-goerli.flashbots.net/', | |
//'goerli' | |
// 'https://relay-sepolia.flashbots.net', | |
// 'sepolia' | |
'https://relay.flashbots.net', | |
'Mainnet' | |
); | |
const privateKey = '0x3'; // !!! 用于救援的钱包私钥, 主要为了隔离风险. | |
const wallet = new ethers.Wallet(privateKey, provider); | |
const compromizedPhrase = ''; // !!! 助记词 | |
const compromizedWallet = ethers.Wallet.fromPhrase(compromizedPhrase, provider); | |
console.log(`compromized wallet address: ${compromizedWallet.address}`); | |
const saveAddress = '0x'; // !!! 一个安全的地址. | |
const contractAddress = '0x'; // !!! 合约地址. | |
// Get the base fee for the next block | |
const latestBlock = await provider.getBlock('latest'); | |
// const baseFeePerGas = latestBlock.baseFeePerGas; | |
// Set a priority fee ("tip") in GWEI | |
const priorityFeePerGas = ethers.parseUnits('10', 'gwei'); | |
// Calculate the max fee per gas | |
const maxFeePerGas = ethers.parseUnits('100', 'gwei'); | |
const gasLimit = '22000' | |
console.log(`gasLimit: ${gasLimit}`) | |
const nonce0 = await wallet.getNonce() | |
console.log(`nonce0: ${nonce0}`); | |
console.log(`wallet balance: ${ethers.formatEther(await provider.getBalance(wallet))} ETH`); | |
// process.exit(); | |
// 4. 创建一笔交易 | |
// EIP 1559 transaction | |
const transaction0 = { | |
chainId: CHAIN_ID, | |
type: 2, | |
nonce: nonce0, | |
to: compromizedWallet.address, | |
value: ethers.parseEther("0.007"), // 0.01 | |
gasLimit: gasLimit, // Set the minimum gas limit for a simple transfer | |
maxPriorityFeePerGas: priorityFeePerGas, // The priority fee | |
maxFeePerGas: maxFeePerGas, // The maximum fee per gas you are willing to pay | |
} | |
const signedTx0 = await wallet.signTransaction(transaction0); | |
const abi = '';// !!! 合约abi | |
const contract = new ethers.Contract(contractAddress, abi, compromizedWallet) | |
const data = contract.interface.encodeFunctionData("streamWithdraw", [ethers.parseEther('0.5'), "yo~"]) | |
const nonce1 = await compromizedWallet.getNonce(); | |
console.log(`nonce1: ${nonce1}`); | |
const transaction1 = { | |
chainId: CHAIN_ID, | |
type: 2, | |
nonce: nonce1, | |
to: contractAddress, | |
data: data, | |
gasLimit: "50000", // Set the minimum gas limit for a simple transfer | |
maxPriorityFeePerGas: priorityFeePerGas, // The priority fee | |
maxFeePerGas: maxFeePerGas, // The maximum fee per gas you are willing to pay | |
} | |
const signedTx1 = await compromizedWallet.signTransaction(transaction1) | |
const nonce2 = nonce1 + 1 | |
console.log(`nonce2: ${nonce2}`); | |
const transaction2 = { | |
chainId: CHAIN_ID, | |
type: 2, | |
nonce: nonce2, | |
to: saveAddress, | |
value: ethers.parseEther("0.5"), | |
gasLimit: gasLimit, // Set the minimum gas limit for a simple transfer | |
maxPriorityFeePerGas: priorityFeePerGas, // The priority fee | |
maxFeePerGas: maxFeePerGas, // The maximum fee per gas you are willing to pay | |
} | |
const signedTx2 = await compromizedWallet.signTransaction(transaction2) | |
// 5. 创建交易 Bundle | |
const transactionBundle = [ | |
{ | |
signedTransaction: signedTx0 | |
}, | |
{ | |
signedTransaction: signedTx1 | |
}, | |
{ | |
signedTransaction: signedTx2 | |
} | |
] | |
console.log("start!!!"); | |
console.log(`wallet balance: ${ethers.formatEther(await provider.getBalance(wallet))} ETH`); | |
// 6. 模拟交易,交易模拟成功后才能执行 | |
// 签名交易 | |
const signedTransactions = await flashbotsProvider.signBundle(transactionBundle) | |
console.log("---1---"); | |
// 设置交易的目标执行区块(在哪个区块执行) | |
const targetBlockNumber = (await provider.getBlockNumber()) + 1 | |
console.log(`target block number: ${targetBlockNumber}`) | |
// 模拟 | |
const simulation = await flashbotsProvider.simulate(signedTransactions, targetBlockNumber) | |
// 检查模拟是否成功 | |
if ("error" in simulation) { | |
console.log(`模拟交易出错: ${simulation.error.message}`); | |
process.exit(1); | |
} else { | |
console.log(`模拟交易成功`); | |
console.log(JSON.stringify(simulation, bigIntReplacer, 2)) | |
} | |
// 7. 发送交易上链 | |
// 因为测试网Flashbots的节点很少,需要尝试很多次才能成功上链,这里我们循环发送 100 个区块。 | |
for (let i = 1; i <= 100; i++) { | |
let targetBlockNumberNew = targetBlockNumber + i - 1; | |
// 发送交易 | |
const res = await flashbotsProvider.sendRawBundle(signedTransactions, targetBlockNumberNew); | |
if ("error" in res) { | |
throw new Error(res.error.message); | |
} | |
// 检查交易是否上链 | |
const bundleResolution = await res.wait(); | |
// 交易有三个状态: 成功上链/没有上链/Nonce过高。 | |
if (bundleResolution === FlashbotsBundleResolution.BundleIncluded) { | |
console.log(`恭喜, 交易成功上链,区块: ${targetBlockNumberNew}`); | |
console.log(JSON.stringify(res, null, 2)); | |
process.exit(0); | |
} else if ( | |
bundleResolution === FlashbotsBundleResolution.BlockPassedWithoutInclusion | |
) { | |
console.log(`请重试, 交易没有被纳入区块: ${targetBlockNumberNew}`); | |
} else if ( | |
bundleResolution === FlashbotsBundleResolution.AccountNonceTooHigh | |
) { | |
console.log("Nonce 太高,请重新设置"); | |
process.exit(1); | |
} | |
} | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment