Skip to content

Instantly share code, notes, and snippets.

@Quanhua-Guan
Last active May 21, 2024 04:11
Show Gist options
  • Save Quanhua-Guan/fe2831acfbd09db94bbe4c021c8d4f5d to your computer and use it in GitHub Desktop.
Save Quanhua-Guan/fe2831acfbd09db94bbe4c021c8d4f5d to your computer and use it in GitHub Desktop.
A Flashbots demo
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