UNPKG

@floyddd-vm/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/mev-relay-js).

876 lines (789 loc) 32.4 kB
import { BlockTag, TransactionReceipt, TransactionRequest } from '@ethersproject/abstract-provider' import { Networkish } from '@ethersproject/networks' import { BaseProvider } from '@ethersproject/providers' import { ConnectionInfo, fetchJson } from '@ethersproject/web' import { BigNumber, ethers, providers, Signer } from 'ethers' import { id, keccak256 } from 'ethers/lib/utils' import { serialize } from '@ethersproject/transactions' export const DEFAULT_FLASHBOTS_RELAY = 'https://relay.flashbots.net' export const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8 export enum FlashbotsBundleResolution { BundleIncluded, BlockPassedWithoutInclusion, AccountNonceTooHigh } export enum FlashbotsBundleConflictType { NoConflict, NonceCollision, Error, CoinbasePayment, GasUsed, NoBundlesInBlock } export interface FlashbotsBundleRawTransaction { signedTransaction: string } export interface FlashbotsBundleTransaction { transaction: TransactionRequest signer: Signer } export interface FlashbotsOptions { minTimestamp?: number maxTimestamp?: number revertingTxHashes?: Array<string> } export interface TransactionAccountNonce { hash: string signedTransaction: string account: string nonce: number } export interface FlashbotsTransactionResponse { bundleTransactions: Array<TransactionAccountNonce> wait: () => Promise<FlashbotsBundleResolution> simulate: () => Promise<SimulationResponse> receipts: () => Promise<Array<TransactionReceipt>> bundleHash: string } export interface TransactionSimulationBase { txHash: string gasUsed: number gasFees: string gasPrice: string toAddress: string fromAddress: string } export interface TransactionSimulationSuccess extends TransactionSimulationBase { value: string ethSentToCoinbase: string } export interface TransactionSimulationRevert extends TransactionSimulationBase { error: string revert: string } export type TransactionSimulation = TransactionSimulationSuccess | TransactionSimulationRevert export interface RelayResponseError { error: { message: string code: number } } export interface SimulationResponseSuccess { bundleHash: string coinbaseDiff: BigNumber results: Array<TransactionSimulation> totalGasUsed: number firstRevert?: TransactionSimulation } export type SimulationResponse = SimulationResponseSuccess | RelayResponseError export type FlashbotsTransaction = FlashbotsTransactionResponse | RelayResponseError export interface GetUserStatsResponseSuccess { signing_address: string blocks_won_total: number bundles_submitted_total: number bundles_error_total: number avg_gas_price_gwei: number blocks_won_last_7d: number bundles_submitted_last_7d: number bundles_error_7d: number avg_gas_price_gwei_last_7d: number blocks_won_last_numberd: number bundles_submitted_last_numberd: number bundles_error_numberd: number avg_gas_price_gwei_last_numberd: number blocks_won_last_numberh: number bundles_submitted_last_numberh: number bundles_error_numberh: number avg_gas_price_gwei_last_numberh: number blocks_won_last_5m: number bundles_submitted_last_5m: number bundles_error_5m: number avg_gas_price_gwei_last_5m: number } export type GetUserStatsResponse = GetUserStatsResponseSuccess | RelayResponseError export interface GetBundleStatsResponseSuccess { isSimulated: boolean isSentToMiners: boolean isHighPriority: boolean simulatedAt: string submittedAt: string sentToMinersAt: string } export type GetBundleStatsResponse = GetBundleStatsResponseSuccess | RelayResponseError interface BlocksApiResponseTransactionDetails { transaction_hash: string tx_index: number bundle_type: 'rogue' | 'flashbots' bundle_index: number block_number: number eoa_address: string to_address: string gas_used: number gas_price: string coinbase_transfer: string total_miner_reward: string } interface BlocksApiResponseBlockDetails { block_number: number miner_reward: string miner: string coinbase_transfers: string gas_used: number gas_price: string transactions: Array<BlocksApiResponseTransactionDetails> } export interface BlocksApiResponse { latest_block_number: number blocks: Array<BlocksApiResponseBlockDetails> } export interface FlashbotsBundleConflict { conflictingBundle: Array<BlocksApiResponseTransactionDetails> initialSimulation: SimulationResponseSuccess conflictType: FlashbotsBundleConflictType } export interface FlashbotsGasPricing { txCount: number gasUsed: number gasFeesPaidBySearcher: BigNumber priorityFeesReceivedByMiner: BigNumber ethSentToCoinbase: BigNumber effectiveGasPriceToSearcher: BigNumber effectivePriorityFeeToMiner: BigNumber } export interface FlashbotsBundleConflictWithGasPricing extends FlashbotsBundleConflict { targetBundleGasPricing: FlashbotsGasPricing conflictingBundleGasPricing?: FlashbotsGasPricing } type RpcParams = Array<string[] | string | number | Record<string, unknown>> const TIMEOUT_MS = 5 * 60 * 1000 export class FlashbotsBundleProvider extends providers.JsonRpcProvider { private genericProvider: BaseProvider private authSigner: Signer private bloXrouteAuthKey: String private connectionInfo: ConnectionInfo constructor(genericProvider: BaseProvider, authSigner: Signer, connectionInfoOrUrl: ConnectionInfo, network: Networkish) { super(connectionInfoOrUrl, network) this.genericProvider = genericProvider this.authSigner = authSigner this.bloXrouteAuthKey = "" this.connectionInfo = connectionInfoOrUrl } public setBloXrouteKey(bloXrouteAuthKey: String){ this.bloXrouteAuthKey = bloXrouteAuthKey } static async throttleCallback(): Promise<boolean> { console.warn('Rate limited') return false } static async create( genericProvider: BaseProvider, authSigner: Signer, connectionInfoOrUrl?: ConnectionInfo | string, network?: Networkish ): Promise<FlashbotsBundleProvider> { const connectionInfo: ConnectionInfo = typeof connectionInfoOrUrl === 'string' || typeof connectionInfoOrUrl === 'undefined' ? { url: connectionInfoOrUrl || DEFAULT_FLASHBOTS_RELAY } : { ...connectionInfoOrUrl } if (connectionInfo.headers === undefined) connectionInfo.headers = {} connectionInfo.throttleCallback = FlashbotsBundleProvider.throttleCallback const networkish: Networkish = { chainId: 0, name: '' } if (typeof network === 'string') { networkish.name = network } else if (typeof network === 'number') { networkish.chainId = network } else if (typeof network === 'object') { networkish.name = network.name networkish.chainId = network.chainId } if (networkish.chainId === 0) { networkish.chainId = (await genericProvider.getNetwork()).chainId } return new FlashbotsBundleProvider(genericProvider, authSigner, connectionInfo, networkish) } static getMaxBaseFeeInFutureBlock(baseFee: BigNumber, blocksInFuture: number): BigNumber { let maxBaseFee = BigNumber.from(baseFee) for (let i = 0; i < blocksInFuture; i++) { maxBaseFee = maxBaseFee.mul(1125).div(1000).add(1) } return maxBaseFee } static getBaseFeeInNextBlock(currentBaseFeePerGas: BigNumber, currentGasUsed: BigNumber, currentGasLimit: BigNumber): BigNumber { const currentGasTarget = currentGasLimit.div(2) if (currentGasUsed.eq(currentGasTarget)) { return currentBaseFeePerGas } else if (currentGasUsed.gt(currentGasTarget)) { const gasUsedDelta = currentGasUsed.sub(currentGasTarget) const baseFeePerGasDelta = currentBaseFeePerGas.mul(gasUsedDelta).div(currentGasTarget).div(BASE_FEE_MAX_CHANGE_DENOMINATOR) return currentBaseFeePerGas.add(baseFeePerGasDelta) } else { const gasUsedDelta = currentGasTarget.sub(currentGasUsed) const baseFeePerGasDelta = currentBaseFeePerGas.mul(gasUsedDelta).div(currentGasTarget).div(BASE_FEE_MAX_CHANGE_DENOMINATOR) return currentBaseFeePerGas.sub(baseFeePerGasDelta) } } static generateBundleHash(txHashes: Array<string>): string { const concatenatedHashes = txHashes.map((txHash) => txHash.slice(2)).join('') return keccak256(`0x${concatenatedHashes}`) } public async sendRawBundle( signedBundledTransactions: Array<string>, targetBlockNumber: number, opts?: FlashbotsOptions ): Promise<FlashbotsTransaction> { const params = { txs: signedBundledTransactions, blockNumber: `0x${targetBlockNumber.toString(16)}`, minTimestamp: opts?.minTimestamp, maxTimestamp: opts?.maxTimestamp, revertingTxHashes: opts?.revertingTxHashes } const request = JSON.stringify(this.prepareBundleRequest('eth_sendBundle', [params])) /* const response = await this.request(request) if (response.error !== undefined && response.error !== null) { return { error: { message: response.error.message, code: response.error.code } } } */ const bundleTransactions = signedBundledTransactions.map((signedTransaction) => { const transactionDetails = ethers.utils.parseTransaction(signedTransaction) return { signedTransaction, hash: ethers.utils.keccak256(signedTransaction), account: transactionDetails.from || '0x0', nonce: transactionDetails.nonce } }) return { bundleTransactions, wait: () => this.wait(bundleTransactions, targetBlockNumber, TIMEOUT_MS), simulate: () => this.simulate( bundleTransactions.map((tx) => tx.signedTransaction), targetBlockNumber, undefined, opts?.minTimestamp ), receipts: () => this.fetchReceipts(bundleTransactions), bundleHash: "0x"//response.result.bundleHash } } public async sendRawBundle_blxr( signedBundledTransactions: Array<string>, targetBlockNumber: number, opts?: FlashbotsOptions ): Promise<FlashbotsTransaction> { const params = { transaction: signedBundledTransactions, block_number: `0x${targetBlockNumber.toString(16)}`, min_timestamp: opts?.minTimestamp, max_timestamp: opts?.maxTimestamp, reverting_hashes: opts?.revertingTxHashes } const request = JSON.stringify(this.prepareBundleRequest('blxr_submit_bundle', [params])) /*const response = await this.request_blxr(request) if (response.error !== undefined && response.error !== null) { return { error: { message: response.error.message, code: response.error.code } } }*/ const bundleTransactions = signedBundledTransactions.map((signedTransaction) => { const transactionDetails = ethers.utils.parseTransaction(signedTransaction) return { signedTransaction, hash: ethers.utils.keccak256(signedTransaction), account: transactionDetails.from || '0x0', nonce: transactionDetails.nonce } }) return { bundleTransactions, wait: () => this.wait(bundleTransactions, targetBlockNumber, TIMEOUT_MS), simulate: () => this.simulate( bundleTransactions.map((tx) => tx.signedTransaction), targetBlockNumber, undefined, opts?.minTimestamp ), receipts: () => this.fetchReceipts(bundleTransactions), bundleHash: "0x" //response.result.bundleHash } } public async sendBundle( bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>, targetBlockNumber: number, opts?: FlashbotsOptions ): Promise<FlashbotsTransaction> { const signedTransactions = await this.signBundle(bundledTransactions) return this.sendRawBundle(signedTransactions, targetBlockNumber, opts) } public async signBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>): Promise<Array<string>> { const nonces: { [address: string]: BigNumber } = {} const signedTransactions = new Array<string>() for (const tx of bundledTransactions) { if ('signedTransaction' in tx) { // in case someone is mixing pre-signed and signing transactions, decode to add to nonce object const transactionDetails = ethers.utils.parseTransaction(tx.signedTransaction) if (transactionDetails.from === undefined) throw new Error('Could not decode signed transaction') nonces[transactionDetails.from] = BigNumber.from(transactionDetails.nonce + 1) signedTransactions.push(tx.signedTransaction) continue } const transaction = { ...tx.transaction } const address = await tx.signer.getAddress() if (typeof transaction.nonce === 'string') throw new Error('Bad nonce') const nonce = transaction.nonce !== undefined ? BigNumber.from(transaction.nonce) : nonces[address] || BigNumber.from(await this.genericProvider.getTransactionCount(address, 'latest')) nonces[address] = nonce.add(1) if (transaction.nonce === undefined) transaction.nonce = nonce if ((transaction.type == null || transaction.type == 0) && transaction.gasPrice === undefined) transaction.gasPrice = BigNumber.from(0) if (transaction.gasLimit === undefined) transaction.gasLimit = await tx.signer.estimateGas(transaction) // TODO: Add target block number and timestamp when supported by geth signedTransactions.push(await tx.signer.signTransaction(transaction)) } return signedTransactions } private wait(transactionAccountNonces: Array<TransactionAccountNonce>, targetBlockNumber: number, timeout: number) { return new Promise<FlashbotsBundleResolution>((resolve, reject) => { let timer: NodeJS.Timer | null = null let done = false const minimumNonceByAccount = transactionAccountNonces.reduce((acc, accountNonce) => { if (accountNonce.nonce > 0) { if (!acc[accountNonce.account] || accountNonce.nonce < acc[accountNonce.account]) { acc[accountNonce.account] = accountNonce.nonce } } return acc }, {} as { [account: string]: number }) const handler = async (blockNumber: number) => { console.log(`wait on_block ${blockNumber}`) const noncesValid = await Promise.all( Object.entries(minimumNonceByAccount).map(async ([account, nonce]) => { const transactionCount = await this.genericProvider.getTransactionCount(account, blockNumber) return nonce >= transactionCount }) ) const allNoncesValid = noncesValid.every(Boolean) if (allNoncesValid) { if(blockNumber < targetBlockNumber) return const block = await this.genericProvider.getBlock(targetBlockNumber) console.log(`node block number ${block.number}`) for (const bt of transactionAccountNonces) { let res = await this.genericProvider.getTransaction(bt.hash); if (res == null) { console.log(`tx not found in node`) continue } console.log(`tx in block - ${res.blockNumber}`); } // check bundle against block: const blockTransactionsHash: { [key: string]: boolean } = {} for (const bt of block.transactions) { blockTransactionsHash[bt] = true } const bundleIncluded = transactionAccountNonces.every((transaction) => blockTransactionsHash[transaction.hash]) resolve(bundleIncluded ? FlashbotsBundleResolution.BundleIncluded : FlashbotsBundleResolution.BlockPassedWithoutInclusion) } else // target block not yet reached, but nonce has become invalid resolve(FlashbotsBundleResolution.AccountNonceTooHigh) if (timer) { clearTimeout(timer) } if (done) { return } done = true this.genericProvider.removeListener('block', handler) } this.genericProvider.on('block', handler) if (typeof timeout === 'number' && timeout > 0) { timer = setTimeout(() => { if (done) { return } timer = null done = true this.genericProvider.removeListener('block', handler) reject('Timed out') }, timeout) if (timer.unref) { timer.unref() } } }) } public bundleWait(signedBundledTransactions: Array<string>, timeout: number) { const transactionAccountNonces = signedBundledTransactions.map((signedTransaction) => { const transactionDetails = ethers.utils.parseTransaction(signedTransaction) return { signedTransaction, hash: ethers.utils.keccak256(signedTransaction), account: transactionDetails.from || '0x0', nonce: transactionDetails.nonce } }) return new Promise<Array<Number>>((resolve, reject) => { let timer: NodeJS.Timer | null = null let done = false let needRemoveListener = false const minimumNonceByAccount = transactionAccountNonces.reduce((acc, accountNonce) => { if (accountNonce.nonce > 0) { if (!acc[accountNonce.account] || accountNonce.nonce < acc[accountNonce.account]) { acc[accountNonce.account] = accountNonce.nonce } } return acc }, {} as { [account: string]: number }) const handler = async (blockNumber: number) => { console.log(`wait on_block ${blockNumber}`) const noncesValid = await Promise.all( Object.entries(minimumNonceByAccount).map(async ([account, nonce]) => { const transactionCount = await this.genericProvider.getTransactionCount(account)//, blockNumber) return nonce >= transactionCount }) ) const allNoncesValid = noncesValid.every(Boolean) if (allNoncesValid) { const block = await this.genericProvider.getBlock(blockNumber) console.log(`node block number ${block.number} - ${block.hash}`) // check bundle against block: const blockTransactionsHash: { [key: string]: boolean } = {} for (const bt of block.transactions) { blockTransactionsHash[bt] = true } const bundleIncluded = transactionAccountNonces.every((transaction) => blockTransactionsHash[transaction.hash]) if (bundleIncluded) { resolve([FlashbotsBundleResolution.BundleIncluded, blockNumber]) needRemoveListener = true; } else { console.log() } }else { // target block not yet reached, but nonce has become invalid resolve([FlashbotsBundleResolution.AccountNonceTooHigh, blockNumber]) needRemoveListener = true; } if (timer) { clearTimeout(timer) } if (done) { return } if(needRemoveListener){ this.genericProvider.removeListener('block', handler) done = true } } this.genericProvider.on('block', handler) if (typeof timeout === 'number' && timeout > 0) { timer = setTimeout(() => { if (done) { return } timer = null done = true this.genericProvider.removeListener('block', handler) reject('Timed out') }, timeout) if (timer.unref) { timer.unref() } } }) } public async getUserStats(): Promise<GetUserStatsResponse> { const blockDetails = await this.genericProvider.getBlock('latest') const evmBlockNumber = `0x${blockDetails.number.toString(16)}` const params = [evmBlockNumber] const request = JSON.stringify(this.prepareBundleRequest('flashbots_getUserStats', params)) const response = await this.request(request) if (response.error !== undefined && response.error !== null) { return { error: { message: response.error.message, code: response.error.code } } } return response.result } public async getBundleStats(bundleHash: string, blockNumber: number): Promise<GetBundleStatsResponse> { const evmBlockNumber = `0x${blockNumber.toString(16)}` const params = [{ bundleHash, blockNumber: evmBlockNumber }] const request = JSON.stringify(this.prepareBundleRequest('flashbots_getBundleStats', params)) const response = await this.request(request) if (response.error !== undefined && response.error !== null) { return { error: { message: response.error.message, code: response.error.code } } } return response.result } public async simulate( signedBundledTransactions: Array<string>, blockTag: BlockTag, stateBlockTag?: BlockTag, blockTimestamp?: number ): Promise<SimulationResponse> { let evmBlockNumber: string if (typeof blockTag === 'number') { evmBlockNumber = `0x${blockTag.toString(16)}` } else { const blockTagDetails = await this.genericProvider.getBlock(blockTag) const blockDetails = blockTagDetails !== null ? blockTagDetails : await this.genericProvider.getBlock('latest') evmBlockNumber = `0x${blockDetails.number.toString(16)}` } let evmBlockStateNumber: string if (typeof stateBlockTag === 'number') { evmBlockStateNumber = `0x${stateBlockTag.toString(16)}` } else if (!stateBlockTag) { evmBlockStateNumber = 'latest' } else { evmBlockStateNumber = stateBlockTag } const params: RpcParams = [ { txs: signedBundledTransactions, blockNumber: evmBlockNumber, stateBlockNumber: evmBlockStateNumber, timestamp: blockTimestamp } ] const request = JSON.stringify(this.prepareBundleRequest('eth_callBundle', params)) const response = await this.request(request) if (response.error !== undefined && response.error !== null) { return { error: { message: response.error.message, code: response.error.code } } } const callResult = response.result return { bundleHash: callResult.bundleHash, coinbaseDiff: BigNumber.from(callResult.coinbaseDiff), results: callResult.results, totalGasUsed: callResult.results.reduce((a: number, b: TransactionSimulation) => a + b.gasUsed, 0), firstRevert: callResult.results.find((txSim: TransactionSimulation) => 'revert' in txSim || 'error' in txSim) } } private calculateBundlePricing( bundleTransactions: Array<BlocksApiResponseTransactionDetails | TransactionSimulation>, baseFee: BigNumber ) { const bundleGasPricing = bundleTransactions.reduce( (acc, transactionDetail) => { const gasUsed = 'gas_used' in transactionDetail ? transactionDetail.gas_used : transactionDetail.gasUsed const gasPricePaidBySearcher = BigNumber.from( 'gas_price' in transactionDetail ? transactionDetail.gas_price : transactionDetail.gasPrice ) const priorityFeeReceivedByMiner = gasPricePaidBySearcher.sub(baseFee) const ethSentToCoinbase = 'coinbase_transfer' in transactionDetail ? transactionDetail.coinbase_transfer : 'ethSentToCoinbase' in transactionDetail ? transactionDetail.ethSentToCoinbase : BigNumber.from(0) return { gasUsed: acc.gasUsed + gasUsed, gasFeesPaidBySearcher: acc.gasFeesPaidBySearcher.add(gasPricePaidBySearcher.mul(gasUsed)), priorityFeesReceivedByMiner: acc.priorityFeesReceivedByMiner.add(priorityFeeReceivedByMiner.mul(gasUsed)), ethSentToCoinbase: acc.ethSentToCoinbase.add(ethSentToCoinbase) } }, { gasUsed: 0, gasFeesPaidBySearcher: BigNumber.from(0), priorityFeesReceivedByMiner: BigNumber.from(0), ethSentToCoinbase: BigNumber.from(0) } ) const effectiveGasPriceToSearcher = bundleGasPricing.gasUsed > 0 ? bundleGasPricing.ethSentToCoinbase.add(bundleGasPricing.gasFeesPaidBySearcher).div(bundleGasPricing.gasUsed) : BigNumber.from(0) const effectivePriorityFeeToMiner = bundleGasPricing.gasUsed > 0 ? bundleGasPricing.ethSentToCoinbase.add(bundleGasPricing.priorityFeesReceivedByMiner).div(bundleGasPricing.gasUsed) : BigNumber.from(0) return { ...bundleGasPricing, txCount: bundleTransactions.length, effectiveGasPriceToSearcher, effectivePriorityFeeToMiner } } public async getConflictingBundle( targetSignedBundledTransactions: Array<string>, targetBlockNumber: number ): Promise<FlashbotsBundleConflictWithGasPricing> { const baseFee = (await this.genericProvider.getBlock(targetBlockNumber)).baseFeePerGas || BigNumber.from(0) const conflictDetails = await this.getConflictingBundleWithoutGasPricing(targetSignedBundledTransactions, targetBlockNumber) return { ...conflictDetails, targetBundleGasPricing: this.calculateBundlePricing(conflictDetails.initialSimulation.results, baseFee), conflictingBundleGasPricing: conflictDetails.conflictingBundle.length > 0 ? this.calculateBundlePricing(conflictDetails.conflictingBundle, baseFee) : undefined } } public async getConflictingBundleWithoutGasPricing( targetSignedBundledTransactions: Array<string>, targetBlockNumber: number ): Promise<FlashbotsBundleConflict> { const [initialSimulation, competingBundles] = await Promise.all([ this.simulate(targetSignedBundledTransactions, targetBlockNumber, targetBlockNumber - 1), this.fetchBlocksApi(targetBlockNumber) ]) if (competingBundles.latest_block_number <= targetBlockNumber) { throw new Error('Blocks-api has not processed target block') } if ('error' in initialSimulation || initialSimulation.firstRevert !== undefined) { throw new Error('Target bundle errors at top of block') } const blockDetails = competingBundles.blocks[0] if (blockDetails === undefined) { return { initialSimulation, conflictType: FlashbotsBundleConflictType.NoBundlesInBlock, conflictingBundle: [] } } const bundleTransactions = blockDetails.transactions const bundleCount = bundleTransactions[bundleTransactions.length - 1].bundle_index + 1 const signedPriorBundleTransactions = [] for (let currentBundleId = 0; currentBundleId < bundleCount; currentBundleId++) { const currentBundleTransactions = bundleTransactions.filter((bundleTransaction) => bundleTransaction.bundle_index === currentBundleId) const currentBundleSignedTxs = await Promise.all( currentBundleTransactions.map(async (competitorBundleBlocksApiTx) => { const tx = await this.genericProvider.getTransaction(competitorBundleBlocksApiTx.transaction_hash) if (tx.raw !== undefined) { return tx.raw } if (tx.v !== undefined && tx.r !== undefined && tx.s !== undefined) { return serialize(tx, { v: tx.v, r: tx.r, s: tx.s }) } throw new Error('Could not get raw tx') }) ) signedPriorBundleTransactions.push(...currentBundleSignedTxs) const competitorAndTargetBundleSimulation = await this.simulate( [...signedPriorBundleTransactions, ...targetSignedBundledTransactions], targetBlockNumber, targetBlockNumber - 1 ) if ('error' in competitorAndTargetBundleSimulation) { if (competitorAndTargetBundleSimulation.error.message.startsWith('err: nonce too low:')) { return { conflictType: FlashbotsBundleConflictType.NonceCollision, initialSimulation, conflictingBundle: currentBundleTransactions } } throw new Error('Simulation error') } const targetSimulation = competitorAndTargetBundleSimulation.results.slice(-targetSignedBundledTransactions.length) for (let j = 0; j < targetSimulation.length; j++) { const targetSimulationTx = targetSimulation[j] const initialSimulationTx = initialSimulation.results[j] if ('error' in targetSimulationTx || 'error' in initialSimulationTx) { if ('error' in targetSimulationTx != 'error' in initialSimulationTx) { return { conflictType: FlashbotsBundleConflictType.Error, initialSimulation, conflictingBundle: currentBundleTransactions } } continue } if (targetSimulationTx.ethSentToCoinbase != initialSimulationTx.ethSentToCoinbase) { return { conflictType: FlashbotsBundleConflictType.CoinbasePayment, initialSimulation, conflictingBundle: currentBundleTransactions } } if (targetSimulationTx.gasUsed != initialSimulation.results[j].gasUsed) { return { conflictType: FlashbotsBundleConflictType.GasUsed, initialSimulation, conflictingBundle: currentBundleTransactions } } } } return { conflictType: FlashbotsBundleConflictType.NoConflict, initialSimulation, conflictingBundle: [] } } public async fetchBlocksApi(blockNumber: number): Promise<BlocksApiResponse> { return fetchJson(`https://blocks.flashbots.net/v1/blocks?block_number=${blockNumber}`) } private async request(request: string) { const connectionInfo = { ...this.connectionInfo } connectionInfo.headers = { 'X-Flashbots-Signature': `${await this.authSigner.getAddress()}:${await this.authSigner.signMessage(id(request))}`, ...this.connectionInfo.headers } return fetchJson(connectionInfo, request) } private async request_blxr(request: string) { const connectionInfo = { ...this.connectionInfo } connectionInfo.url = "https://54.157.119.190";// "https://mev.api.blxrbdn.com"; connectionInfo.headers = { "Content-Type": "application/json", "Authorization": `${this.bloXrouteAuthKey}`, ...this.connectionInfo.headers } let bundle = JSON.parse(request); //qqq.params[0].transaction[0] = qqq.params[0].transaction[0].substring(2); //qqq.params[0].transaction[1] = qqq.params[0].transaction[1].substring(2); for(let i = 0; i<bundle.params[0].transaction.length; i++){ bundle.params[0].transaction[i] = bundle.params[0].transaction[i].substring(2); } let new_request = JSON.stringify(bundle); return fetchJson(connectionInfo, new_request) } private async fetchReceipts(bundledTransactions: Array<TransactionAccountNonce>): Promise<Array<TransactionReceipt>> { return Promise.all(bundledTransactions.map((bundledTransaction) => this.genericProvider.getTransactionReceipt(bundledTransaction.hash))) } private prepareBundleRequest( method: 'eth_callBundle' | 'eth_sendBundle' | 'flashbots_getUserStats' | 'flashbots_getBundleStats' | 'blxr_submit_bundle', params: RpcParams ) { return { method: method, params: params, id: this._nextId++, jsonrpc: '2.0' } } }