@flashbots/ethers-provider-bundle
Advanced tools
Comparing version 0.2.1 to 0.3.0
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const ethers_1 = require("ethers"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
const index_1 = require("./index"); | ||
@@ -12,4 +13,5 @@ const ETHEREUM_RPC_URL = process.env.ETHEREUM_RPC_URL || 'http://127.0.0.1:8545'; | ||
const authSigner = FLASHBOTS_AUTH_KEY ? new ethers_1.Wallet(FLASHBOTS_AUTH_KEY) : ethers_1.Wallet.createRandom(); | ||
const flashbotsProvider = await index_1.FlashbotsBundleProvider.create(provider, authSigner); | ||
const flashbotsProvider = await index_1.FlashbotsBundleProvider.create(provider, authSigner, 'https://relay.flashbots.net/'); | ||
const wallet = ethers_1.Wallet.createRandom().connect(provider); | ||
const SIGNED_TRANSACTION = '0xf85f8080825208947a76570ef1d933582c354cbc02c22415e243901880801ca0a0e5096067fa6a62874c9ea2bcc774f2dcf83fd73814db89258af9b4e66092d1a045410ddeda97315d5250d17461930edd6ad46be5351d70c9d02929a1cbd07645'; | ||
const signedTransactions = await flashbotsProvider.signBundle([ | ||
@@ -24,3 +26,3 @@ { | ||
{ | ||
signedTransaction: '0xf85f8080825208947a76570ef1d933582c354cbc02c22415e243901880801ca0a0e5096067fa6a62874c9ea2bcc774f2dcf83fd73814db89258af9b4e66092d1a045410ddeda97315d5250d17461930edd6ad46be5351d70c9d02929a1cbd07645' | ||
signedTransaction: SIGNED_TRANSACTION | ||
} | ||
@@ -37,4 +39,9 @@ ]); | ||
} | ||
const bundleSubmission = await flashbotsProvider.sendRawBundle(signedTransactions, blockNumber + 1); | ||
const bundleSubmission = await flashbotsProvider.sendRawBundle(signedTransactions, blockNumber + 1, { | ||
revertingTxHashes: [utils_1.keccak256(SIGNED_TRANSACTION)] | ||
}); | ||
console.log('bundle submitted, waiting'); | ||
if ('error' in bundleSubmission) { | ||
throw new Error(bundleSubmission.error.message); | ||
} | ||
const waitResponse = await bundleSubmission.wait(); | ||
@@ -41,0 +48,0 @@ const bundleSubmissionSimulation = await bundleSubmission.simulate(); |
@@ -22,2 +22,3 @@ import { BlockTag, TransactionReceipt, TransactionRequest } from '@ethersproject/abstract-provider'; | ||
maxTimestamp?: number; | ||
revertingTxHashes?: Array<string>; | ||
} | ||
@@ -62,2 +63,3 @@ export interface TransactionAccountNonce { | ||
export declare type SimulationResponse = SimulationResponseSuccess | RelayResponseError; | ||
export declare type FlashbotsTransaction = FlashbotsTransactionResponse | RelayResponseError; | ||
export interface GetUserStatsResponseSuccess { | ||
@@ -94,4 +96,4 @@ signing_address: string; | ||
static create(genericProvider: BaseProvider, authSigner: Signer, connectionInfoOrUrl?: ConnectionInfo | string, network?: Networkish): Promise<FlashbotsBundleProvider>; | ||
sendRawBundle(signedBundledTransactions: Array<string>, targetBlockNumber: number, opts?: FlashbotsOptions): Promise<FlashbotsTransactionResponse>; | ||
sendBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>, targetBlockNumber: number, opts?: FlashbotsOptions): Promise<FlashbotsTransactionResponse>; | ||
sendRawBundle(signedBundledTransactions: Array<string>, targetBlockNumber: number, opts?: FlashbotsOptions): Promise<FlashbotsTransaction>; | ||
sendBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>, targetBlockNumber: number, opts?: FlashbotsOptions): Promise<FlashbotsTransaction>; | ||
signBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>): Promise<Array<string>>; | ||
@@ -98,0 +100,0 @@ private wait; |
@@ -57,5 +57,19 @@ "use strict"; | ||
async sendRawBundle(signedBundledTransactions, targetBlockNumber, opts) { | ||
const params = [signedBundledTransactions, `0x${targetBlockNumber.toString(16)}`, (opts === null || opts === void 0 ? void 0 : opts.minTimestamp) || 0, (opts === null || opts === void 0 ? void 0 : opts.maxTimestamp) || 0]; | ||
const request = JSON.stringify(this.prepareBundleRequest('eth_sendBundle', params)); | ||
await this.request(request); | ||
const params = { | ||
txs: signedBundledTransactions, | ||
blockNumber: `0x${targetBlockNumber.toString(16)}`, | ||
minTimestamp: opts === null || opts === void 0 ? void 0 : opts.minTimestamp, | ||
maxTimestamp: opts === null || opts === void 0 ? void 0 : opts.maxTimestamp, | ||
revertingTxHashes: opts === null || opts === void 0 ? void 0 : opts.revertingTxHashes | ||
}; | ||
const request = JSON.stringify(this.prepareBundleRequest('eth_sendBundle', [params])); | ||
const response = await this.request(request); | ||
if (response.error !== undefined) { | ||
return { | ||
error: { | ||
message: response.error.message, | ||
code: response.error.code | ||
} | ||
}; | ||
} | ||
const bundleTransactions = signedBundledTransactions.map((signedTransaction) => { | ||
@@ -203,6 +217,5 @@ const transactionDetails = ethers_1.ethers.utils.parseTransaction(signedTransaction); | ||
} | ||
const params = [signedBundledTransactions, evmBlockNumber, evmBlockStateNumber]; | ||
if (blockTimestamp) { | ||
params.push(blockTimestamp); | ||
} | ||
const params = [ | ||
{ txs: signedBundledTransactions, blockNumber: evmBlockNumber, stateBlockNumber: evmBlockStateNumber, timestamp: blockTimestamp } | ||
]; | ||
const request = JSON.stringify(this.prepareBundleRequest('eth_callBundle', params)); | ||
@@ -209,0 +222,0 @@ const response = await this.request(request); |
{ | ||
"name": "@flashbots/ethers-provider-bundle", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "", | ||
@@ -18,12 +18,12 @@ "main": "build/index.js", | ||
"@types/node": "^14.14.10", | ||
"@typescript-eslint/eslint-plugin": "^4.9.1", | ||
"@typescript-eslint/parser": "^4.9.1", | ||
"eslint": "^7.15.0", | ||
"eslint-config-prettier": "^8.1.0", | ||
"eslint-plugin-prettier": "^3.3.1", | ||
"ethers": "^5.0.23", | ||
"@typescript-eslint/eslint-plugin": "^4.22.1", | ||
"@typescript-eslint/parser": "^4.22.1", | ||
"eslint": "^7.25.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"ethers": "^5.1.4", | ||
"prettier": "^2.2.1" | ||
}, | ||
"peerDependencies": { | ||
"ethers": "^5.0.23" | ||
"ethers": "^5.1.4" | ||
}, | ||
@@ -30,0 +30,0 @@ "dependencies": { |
# ethers-provider-flashbots-bundle | ||
Contains the `FlashbotsBundleProvider` ethers.js provider to provide high-level access to `eth_sendBundle` rpc endpoint on [mev-relay](https://github.com/flashbots/mev-relay-js). | ||
Contains the `FlashbotsBundleProvider` ethers.js provider to provide high-level access to `eth_sendBundle` and `eth_callBundle` rpc endpoint on [mev-relay](https://github.com/flashbots/mev-relay-js). | ||
Flashbots-enabled relays and miners expose two new jsonrpc endpoint: `eth_sendBundle` and `eth_callBundle`. Since these are brand-new, non-standard endpoints, ethers.js and other libraries do not natively support these requests (like `getTransactionCount`). In order to interact with these endpoints, you will also need access to another full-featured (non-Flashbots) endpoint for nonce-calculation, gas estimation, and transaction status. | ||
Flashbots also requires signed payloads. This library takes care of the signing process via the `authSigner` passed into the constructor. [Read more about relay signatures here](https://github.com/flashbots/mev-relay-js#authentication) | ||
This library is not a fully functional ethers.js implementation, just a simple provider class, designed to interact with your existing ethers.js v5 module. | ||
You can pass in a generic ethers.js provider to the Flashbots provider in the constructor: | ||
You must pass in a generic ethers.js provider to the Flashbots provider in the constructor: | ||
@@ -26,3 +29,3 @@ ```ts | ||
flashbotsProvider.sendBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>, targetBlockNumber: number) | ||
=> Promise<FlashbotsTransactionResponse> | ||
=> Promise<FlashbotsTransactionResponse | RelayResponseError> | ||
``` | ||
@@ -62,3 +65,4 @@ | ||
minTimestamp, // optional minimum timestamp at which this bundle is valid (inclusive) | ||
maxTimestamp // optional maximum timestamp at which this bundle is valid (inclusive) | ||
maxTimestamp, // optional maximum timestamp at which this bundle is valid (inclusive) | ||
revertingTxHashes: [tx1, tx2] // optional list of transaction hashes allowed to revert. Without specifying here, any revert invalidates the entire bundle. | ||
} | ||
@@ -79,2 +83,23 @@ ) | ||
## Optional eth_sendBundle arguments | ||
### minTimestamp / maxTimestamp | ||
While each bundle targets only a single block, you can add an additional filter for validity based on the block's timestamp. This does *not* allow for targeting any block number based on a timestamp or instruct miners on what timestamp to use, it merely serves as a secondary filter. | ||
If your bundle is not valid before a certain time or includes an expiring opportunity, setting these values allows the miner to skip bundle processing earlier in the phase. | ||
Additionally, you could target several blocks in the future, but with a strict maxTimestamp, to ensure your bundle is considered for inclusion up to a specific time, regardless of how quickly blocks are mined in that timeframe. | ||
### Reverting Transaction Hashes | ||
Transaction bundles will not be considered for inclusion if they include *any* transactions in them that revert or fail. While this is normally desirable, there are some advanced use-cases where a searcher might WANT to bring a failing transaction to the chain. This is normally desirable for nonce management. Consider: | ||
Transaction Nonce #1 = Failed (unrelated) token transfer | ||
Transaction Nonce #2 = DEX trade | ||
If a searcher wants to bring #2 to the chain, #1 must be included first, and its failure is not related to the desired transaction #2. This is especially common during high gas times. | ||
Optional parameter `revertingTxHashes` allows a searcher to specify an array of transactions that can (but are not required to) revert. | ||
## FlashbotsTransactionResponse | ||
@@ -84,4 +109,5 @@ | ||
- bundleTransactions() - An array of transaction descriptions sent to the relay, including hash, nonce, and the raw transaction. | ||
- receipts() - Returns promise of an array of transaction receipts corresponding to the transaction hashes that were relayed as part of the bundle. Will not wait for block to be mined; could return incomplete information | ||
- wait() - Returns a promise which will wait for target block number to be reched _OR_ one of the transactions to become invalid due to nonce-issues (including, but not limited to, one of the transactions from you bundle being included too early). Returns the wait resolution as a status enum | ||
- wait() - Returns a promise which will wait for target block number to be reached _OR_ one of the transactions to become invalid due to nonce-issues (including, but not limited to, one of the transactions from your bundle being included too early). Returns the wait resolution as a status enum | ||
- simulate() - Returns a promise of the transaction simulation, once the proper block height has been reached. Use this function to troubleshoot failing bundles and verify miner profitability | ||
@@ -91,2 +117,21 @@ | ||
Included is a simple demo of how to construct the FlashbotsProvider with auth signer authentication and submit a [non-functional] bundle. This will not yield any mev, but could serve as a sample initialization to help integrate into your own functional searcher. | ||
Included is a simple demo of how to construct the FlashbotsProvider with auth signer authentication and submit a [non-functional] bundle. This will not yield any mev, but serves as a sample initialization to help integrate into your own functional searcher. | ||
## Flashbots on Goerli | ||
To test Flashbots before going to mainnet, you can use the Goerli Flashbots relay, which works in conjunction with a Flashbots-enabled Goerli validator. Flashbots on Goerli requires two simple changes: | ||
1. Ensure your genericProvider passed in to the FlashbotsBundleProvider constructor is connected to Goerli (gas estimates and nonce requests need to correspond to the correct chain): | ||
```ts | ||
import { providers } from 'ethers' | ||
const provider = providers.getDefaultProvider('goerli') | ||
``` | ||
2. Set the relay endpoint to `https://relay-goerli.flashbots.net/` | ||
```ts | ||
const flashbotsProvider = await FlashbotsBundleProvider.create( | ||
provider, | ||
authSigner, | ||
'https://relay-goerli.flashbots.net/', | ||
'goerli') | ||
``` |
import { providers, Wallet } from 'ethers' | ||
import { ConnectionInfo } from 'ethers/lib/utils' | ||
import { ConnectionInfo, keccak256 } from 'ethers/lib/utils' | ||
import { FlashbotsBundleProvider, FlashbotsBundleResolution } from './index' | ||
@@ -14,6 +14,7 @@ | ||
const authSigner = FLASHBOTS_AUTH_KEY ? new Wallet(FLASHBOTS_AUTH_KEY) : Wallet.createRandom() | ||
const flashbotsProvider = await FlashbotsBundleProvider.create(provider, authSigner) | ||
const flashbotsProvider = await FlashbotsBundleProvider.create(provider, authSigner, 'https://relay.flashbots.net/') | ||
const wallet = Wallet.createRandom().connect(provider) | ||
const SIGNED_TRANSACTION = '0xf85f8080825208947a76570ef1d933582c354cbc02c22415e243901880801ca0a0e5096067fa6a62874c9ea2bcc774f2dcf83fd73814db89258af9b4e66092d1a045410ddeda97315d5250d17461930edd6ad46be5351d70c9d02929a1cbd07645'; | ||
const signedTransactions = await flashbotsProvider.signBundle([ | ||
@@ -29,3 +30,3 @@ { | ||
signedTransaction: | ||
'0xf85f8080825208947a76570ef1d933582c354cbc02c22415e243901880801ca0a0e5096067fa6a62874c9ea2bcc774f2dcf83fd73814db89258af9b4e66092d1a045410ddeda97315d5250d17461930edd6ad46be5351d70c9d02929a1cbd07645' | ||
SIGNED_TRANSACTION | ||
} | ||
@@ -42,4 +43,13 @@ ]) | ||
} | ||
const bundleSubmission = await flashbotsProvider.sendRawBundle(signedTransactions, blockNumber + 1) | ||
const bundleSubmission = await flashbotsProvider.sendRawBundle( | ||
signedTransactions, | ||
blockNumber + 1, | ||
{ | ||
revertingTxHashes: | ||
[keccak256(SIGNED_TRANSACTION)] | ||
}) | ||
console.log('bundle submitted, waiting') | ||
if ('error' in bundleSubmission) { | ||
throw new Error(bundleSubmission.error.message) | ||
} | ||
const waitResponse = await bundleSubmission.wait() | ||
@@ -46,0 +56,0 @@ const bundleSubmissionSimulation = await bundleSubmission.simulate() |
@@ -28,2 +28,3 @@ import { BlockTag, TransactionReceipt, TransactionRequest } from '@ethersproject/abstract-provider' | ||
maxTimestamp?: number | ||
revertingTxHashes?: Array<string> | ||
} | ||
@@ -78,2 +79,4 @@ | ||
export type FlashbotsTransaction = FlashbotsTransactionResponse | RelayResponseError | ||
export interface GetUserStatsResponseSuccess { | ||
@@ -105,3 +108,3 @@ signing_address: string | ||
type RpcParams = Array<string[] | string | number> | ||
type RpcParams = Array<string[] | string | number | Record<string, unknown>> | ||
@@ -167,7 +170,22 @@ const TIMEOUT_MS = 5 * 60 * 1000 | ||
opts?: FlashbotsOptions | ||
): Promise<FlashbotsTransactionResponse> { | ||
const params = [signedBundledTransactions, `0x${targetBlockNumber.toString(16)}`, opts?.minTimestamp || 0, opts?.maxTimestamp || 0] | ||
const request = JSON.stringify(this.prepareBundleRequest('eth_sendBundle', params)) | ||
await this.request(request) | ||
): 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) { | ||
return { | ||
error: { | ||
message: response.error.message, | ||
code: response.error.code | ||
} | ||
} | ||
} | ||
const bundleTransactions = signedBundledTransactions.map((signedTransaction) => { | ||
@@ -201,3 +219,3 @@ const transactionDetails = ethers.utils.parseTransaction(signedTransaction) | ||
opts?: FlashbotsOptions | ||
): Promise<FlashbotsTransactionResponse> { | ||
): Promise<FlashbotsTransaction> { | ||
const signedTransactions = await this.signBundle(bundledTransactions) | ||
@@ -341,6 +359,5 @@ return this.sendRawBundle(signedTransactions, targetBlockNumber, opts) | ||
const params: RpcParams = [signedBundledTransactions, evmBlockNumber, evmBlockStateNumber] | ||
if (blockTimestamp) { | ||
params.push(blockTimestamp) | ||
} | ||
const params: RpcParams = [ | ||
{ txs: signedBundledTransactions, blockNumber: evmBlockNumber, stateBlockNumber: evmBlockStateNumber, timestamp: blockTimestamp } | ||
] | ||
const request = JSON.stringify(this.prepareBundleRequest('eth_callBundle', params)) | ||
@@ -347,0 +364,0 @@ const response = await this.request(request) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
167322
868
133