UNPKG

@flashbots-sdk/ethers-provider-bundle

Version:

This repository contains the `FlashbotsBundleProvider` ethers.js provider, an additional `Provider` to `ethers.js` to enable high-level access to `eth_sendBundle` and `eth_callBundle` rpc endpoint on [mev-relay](https://github.com/flashbots-sdk/mev-relay-

130 lines (113 loc) 4.82 kB
import { BigNumber, providers, Wallet } from 'ethers' import { FlashbotsBundleProvider, FlashbotsBundleResolution } from './index' import { TransactionRequest } from '@ethersproject/abstract-provider' import { v4 as uuidv4 } from 'uuid' const FLASHBOTS_AUTH_KEY = process.env.FLASHBOTS_AUTH_KEY const GWEI = BigNumber.from(10).pow(9) const PRIORITY_FEE = GWEI.mul(3) const LEGACY_GAS_PRICE = GWEI.mul(12) const BLOCKS_IN_THE_FUTURE = 2 // ===== Uncomment this for mainnet ======= // const CHAIN_ID = 1 // const provider = new providers.JsonRpcProvider( // { url: process.env.ETHEREUM_RPC_URL || 'http://127.0.0.1:8545' }, // { chainId: CHAIN_ID, ensAddress: '', name: 'mainnet' } // ) // const FLASHBOTS_EP = 'https://relay.flashbots.net/' // ===== Uncomment this for mainnet ======= // ===== Uncomment this for Goerli ======= const CHAIN_ID = 5 const provider = new providers.InfuraProvider(CHAIN_ID, process.env.INFURA_API_KEY) const FLASHBOTS_EP = 'https://relay-goerli.flashbots.net/' // ===== Uncomment this for Goerli ======= for (const e of ['FLASHBOTS_AUTH_KEY', 'INFURA_API_KEY', 'ETHEREUM_RPC_URL', 'PRIVATE_KEY']) { if (!process.env[e]) { // don't warn for skipping ETHEREUM_RPC_URL if using goerli if (FLASHBOTS_EP.includes('goerli') && e === 'ETHEREUM_RPC_URL') { continue } console.warn(`${e} should be defined as an environment variable`) } } async function main() { const authSigner = FLASHBOTS_AUTH_KEY ? new Wallet(FLASHBOTS_AUTH_KEY) : Wallet.createRandom() const wallet = new Wallet(process.env.PRIVATE_KEY || '', provider) const flashbotsProvider = await FlashbotsBundleProvider.create(provider, authSigner, FLASHBOTS_EP) const userStats = flashbotsProvider.getUserStats() if (process.env.TEST_V2) { try { const userStats2 = await flashbotsProvider.getUserStatsV2() console.log('userStatsV2', userStats2) } catch (e) { console.error('[v2 error]', e) } } const legacyTransaction = { to: wallet.address, gasPrice: LEGACY_GAS_PRICE, gasLimit: 21000, data: '0x', nonce: await provider.getTransactionCount(wallet.address), chainId: CHAIN_ID } provider.on('block', async (blockNumber) => { const block = await provider.getBlock(blockNumber) const replacementUuid = uuidv4() let eip1559Transaction: TransactionRequest if (block.baseFeePerGas == null) { console.warn('This chain is not EIP-1559 enabled, defaulting to two legacy transactions for demo') eip1559Transaction = { ...legacyTransaction } // We set a nonce in legacyTransaction above to limit validity to a single landed bundle. Delete that nonce for tx#2, and allow bundle provider to calculate it delete eip1559Transaction.nonce } else { const maxBaseFeeInFutureBlock = FlashbotsBundleProvider.getMaxBaseFeeInFutureBlock(block.baseFeePerGas, BLOCKS_IN_THE_FUTURE) eip1559Transaction = { to: wallet.address, type: 2, maxFeePerGas: PRIORITY_FEE.add(maxBaseFeeInFutureBlock), maxPriorityFeePerGas: PRIORITY_FEE, gasLimit: 21000, data: '0x', chainId: CHAIN_ID } } const signedTransactions = await flashbotsProvider.signBundle([ { signer: wallet, transaction: legacyTransaction }, { signer: wallet, transaction: eip1559Transaction } ]) const targetBlock = blockNumber + BLOCKS_IN_THE_FUTURE const simulation = await flashbotsProvider.simulate(signedTransactions, targetBlock) // Using TypeScript discrimination if ('error' in simulation) { console.warn(`Simulation Error: ${simulation.error.message}`) process.exit(1) } else { console.log(`Simulation Success: ${JSON.stringify(simulation, null, 2)}`) } const bundleSubmission = await flashbotsProvider.sendRawBundle(signedTransactions, targetBlock, { replacementUuid }) console.log('bundle submitted, waiting') if ('error' in bundleSubmission) { throw new Error(bundleSubmission.error.message) } const cancelResult = await flashbotsProvider.cancelBundles(replacementUuid) console.log('cancel response', cancelResult) const waitResponse = await bundleSubmission.wait() console.log(`Wait Response: ${FlashbotsBundleResolution[waitResponse]}`) if (waitResponse === FlashbotsBundleResolution.BundleIncluded || waitResponse === FlashbotsBundleResolution.AccountNonceTooHigh) { process.exit(0) } else { console.log({ bundleStats: await flashbotsProvider.getBundleStats(simulation.bundleHash, targetBlock), bundleStatsV2: process.env.TEST_V2 && (await flashbotsProvider.getBundleStatsV2(simulation.bundleHash, targetBlock)), userStats: await userStats }) } }) } main()