Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@flashbots/ethers-provider-bundle

Package Overview
Dependencies
Maintainers
2
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flashbots/ethers-provider-bundle - npm Package Compare versions

Comparing version 0.4.4 to 0.5.0

21

build/index.d.ts

@@ -13,2 +13,6 @@ import { BlockTag, TransactionReceipt, TransactionRequest } from '@ethersproject/abstract-provider';

}
export declare enum FlashbotsTransactionResolution {
TransactionIncluded = 0,
TransactionDropped = 1
}
export declare enum FlashbotsBundleConflictType {

@@ -47,2 +51,8 @@ NoConflict = 0,

}
export interface FlashbotsPrivateTransactionResponse {
transaction: TransactionAccountNonce;
wait: () => Promise<FlashbotsTransactionResolution>;
simulate: () => Promise<SimulationResponse>;
receipts: () => Promise<Array<TransactionReceipt>>;
}
export interface TransactionSimulationBase {

@@ -80,2 +90,3 @@ txHash: string;

export declare type FlashbotsTransaction = FlashbotsTransactionResponse | RelayResponseError;
export declare type FlashbotsPrivateTransaction = FlashbotsPrivateTransactionResponse | RelayResponseError;
export interface GetUserStatsResponseSuccess {

@@ -170,4 +181,10 @@ signing_address: string;

sendBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>, targetBlockNumber: number, opts?: FlashbotsOptions): Promise<FlashbotsTransaction>;
sendPrivateTransaction(transaction: FlashbotsBundleTransaction | FlashbotsBundleRawTransaction, opts?: {
maxBlockNumber?: number;
simulationTimestamp?: number;
}): Promise<FlashbotsPrivateTransaction>;
cancelPrivateTransaction(txHash: string): Promise<boolean | RelayResponseError>;
signBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>): Promise<Array<string>>;
private wait;
private waitForBlock;
private waitForTxInclusion;
getUserStats(): Promise<GetUserStatsResponse>;

@@ -182,4 +199,4 @@ getBundleStats(bundleHash: string, blockNumber: number): Promise<GetBundleStatsResponse>;

private fetchReceipts;
private prepareBundleRequest;
private prepareRelayRequest;
}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FlashbotsBundleProvider = exports.FlashbotsBundleConflictType = exports.FlashbotsBundleResolution = exports.BASE_FEE_MAX_CHANGE_DENOMINATOR = exports.DEFAULT_FLASHBOTS_RELAY = void 0;
exports.FlashbotsBundleProvider = exports.FlashbotsBundleConflictType = exports.FlashbotsTransactionResolution = exports.FlashbotsBundleResolution = exports.BASE_FEE_MAX_CHANGE_DENOMINATOR = exports.DEFAULT_FLASHBOTS_RELAY = void 0;
const web_1 = require("@ethersproject/web");

@@ -10,2 +10,3 @@ const ethers_1 = require("ethers");

exports.BASE_FEE_MAX_CHANGE_DENOMINATOR = 8;
const PRIVATE_TX_WAIT_BLOCKS = 25; // # of blocks
var FlashbotsBundleResolution;

@@ -17,2 +18,7 @@ (function (FlashbotsBundleResolution) {

})(FlashbotsBundleResolution = exports.FlashbotsBundleResolution || (exports.FlashbotsBundleResolution = {}));
var FlashbotsTransactionResolution;
(function (FlashbotsTransactionResolution) {
FlashbotsTransactionResolution[FlashbotsTransactionResolution["TransactionIncluded"] = 0] = "TransactionIncluded";
FlashbotsTransactionResolution[FlashbotsTransactionResolution["TransactionDropped"] = 1] = "TransactionDropped";
})(FlashbotsTransactionResolution = exports.FlashbotsTransactionResolution || (exports.FlashbotsTransactionResolution = {}));
var FlashbotsBundleConflictType;

@@ -104,3 +110,3 @@ (function (FlashbotsBundleConflictType) {

};
const request = JSON.stringify(this.prepareBundleRequest('eth_sendBundle', [params]));
const request = JSON.stringify(this.prepareRelayRequest('eth_sendBundle', [params]));
const response = await this.request(request);

@@ -126,3 +132,3 @@ if (response.error !== undefined && response.error !== null) {

bundleTransactions,
wait: () => this.wait(bundleTransactions, targetBlockNumber, TIMEOUT_MS),
wait: () => this.waitForBlock(bundleTransactions, targetBlockNumber, TIMEOUT_MS),
simulate: () => this.simulate(bundleTransactions.map((tx) => tx.signedTransaction), targetBlockNumber, undefined, opts === null || opts === void 0 ? void 0 : opts.minTimestamp),

@@ -137,2 +143,56 @@ receipts: () => this.fetchReceipts(bundleTransactions),

}
async sendPrivateTransaction(transaction, opts) {
let signedTransaction;
if ("signedTransaction" in transaction) {
signedTransaction = transaction.signedTransaction;
}
else {
signedTransaction = await transaction.signer.signTransaction(transaction.transaction);
}
const startBlockNumberPromise = this.genericProvider.getBlockNumber();
const params = {
tx: signedTransaction,
maxBlockNumber: opts === null || opts === void 0 ? void 0 : opts.maxBlockNumber,
};
const request = JSON.stringify(this.prepareRelayRequest('eth_sendPrivateTransaction', [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 transactionDetails = ethers_1.ethers.utils.parseTransaction(signedTransaction);
const privateTransaction = {
signedTransaction: signedTransaction,
hash: ethers_1.ethers.utils.keccak256(signedTransaction),
account: transactionDetails.from || "0x0",
nonce: transactionDetails.nonce,
};
const startBlockNumber = await startBlockNumberPromise;
return {
transaction: privateTransaction,
wait: () => this.waitForTxInclusion(privateTransaction.hash, (opts === null || opts === void 0 ? void 0 : opts.maxBlockNumber) || startBlockNumber + 25, TIMEOUT_MS),
simulate: () => this.simulate([privateTransaction.signedTransaction], startBlockNumber, undefined, opts === null || opts === void 0 ? void 0 : opts.simulationTimestamp),
receipts: () => this.fetchReceipts([privateTransaction]),
};
}
async cancelPrivateTransaction(txHash) {
const params = {
txHash,
};
const request = JSON.stringify(this.prepareRelayRequest('eth_cancelPrivateTransaction', [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 true;
}
async signBundle(bundledTransactions) {

@@ -169,3 +229,3 @@ const nonces = {};

}
wait(transactionAccountNonces, targetBlockNumber, timeout) {
waitForBlock(transactionAccountNonces, targetBlockNumber, timeout) {
return new Promise((resolve, reject) => {

@@ -230,2 +290,50 @@ let timer = null;

}
waitForTxInclusion(transactionHash, maxBlockNumber, timeout) {
return new Promise((resolve, reject) => {
let timer = null;
let done = false;
// runs on new block event
const handler = async (blockNumber) => {
if (blockNumber <= maxBlockNumber) {
// check tx status on mainnet
const sentTxStatus = await this.genericProvider.getTransaction(transactionHash);
if (sentTxStatus && sentTxStatus.confirmations >= 1) {
resolve(FlashbotsTransactionResolution.TransactionIncluded);
}
else {
return;
}
}
else {
// tx not included in specified range, bail
this.genericProvider.removeListener('block', handler);
resolve(FlashbotsTransactionResolution.TransactionDropped);
}
if (timer) {
clearTimeout(timer);
}
if (done) {
return;
}
done = true;
this.genericProvider.removeListener('block', handler);
};
this.genericProvider.on('block', handler);
// time out if we've been trying for too long
if (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();
}
}
});
}
async getUserStats() {

@@ -235,3 +343,3 @@ const blockDetails = await this.genericProvider.getBlock('latest');

const params = [evmBlockNumber];
const request = JSON.stringify(this.prepareBundleRequest('flashbots_getUserStats', params));
const request = JSON.stringify(this.prepareRelayRequest('flashbots_getUserStats', params));
const response = await this.request(request);

@@ -251,3 +359,3 @@ if (response.error !== undefined && response.error !== null) {

const params = [{ bundleHash, blockNumber: evmBlockNumber }];
const request = JSON.stringify(this.prepareBundleRequest('flashbots_getBundleStats', params));
const request = JSON.stringify(this.prepareRelayRequest('flashbots_getBundleStats', params));
const response = await this.request(request);

@@ -292,3 +400,3 @@ if (response.error !== undefined && response.error !== null) {

];
const request = JSON.stringify(this.prepareBundleRequest('eth_callBundle', params));
const request = JSON.stringify(this.prepareRelayRequest('eth_callBundle', params));
const response = await this.request(request);

@@ -459,3 +567,3 @@ if (response.error !== undefined && response.error !== null) {

}
prepareBundleRequest(method, params) {
prepareRelayRequest(method, params) {
return {

@@ -462,0 +570,0 @@ method: method,

2

package.json
{
"name": "@flashbots/ethers-provider-bundle",
"version": "0.4.4",
"version": "0.5.0",
"description": "",

@@ -5,0 +5,0 @@ "main": "build/index.js",

# ethers-provider-flashbots-bundle
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). **`mev-relay` is a hosted service; it is not necessary to run `mev-relay` or `mev-geth` to proceed with this example.**
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). **`mev-relay` is a hosted service; it is not necessary to run `mev-relay` or `mev-geth` to proceed with this example.**

@@ -13,3 +13,4 @@ Flashbots-enabled relays and miners expose two new jsonrpc endpoints: `eth_sendBundle` and `eth_callBundle`. Since these are 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 need access to another full-featured (non-Flashbots) endpoint for nonce-calculation, gas estimation, and transaction status.

Install ethers.js and the Flashbots ethers bundle provider
Install ethers.js and the Flashbots ethers bundle provider.
```bash

@@ -65,2 +66,3 @@ npm install --save ethers

## Block Targeting
The last thing required for `sendBundle()` is block targeting. Every bundle specifically references a single block. If your bundle is valid for multiple blocks (including all blocks until it is mined), `sendBundle()` must be called for every block, ideally on one of the blocks immediately prior. This gives you a chance to re-evaluate the opportunity you are capturing and re-sign your transactions with a higher nonce, if necessary.

@@ -75,5 +77,6 @@

## Gas Prices and EIP-1559
Before EIP-1559 was activated, the most common way for searchers to submit transactions is with `gasPrice=0`, with an on-chain payment to `block.coinbase` conditional on the transaction's success. All transactions must pay `baseFee` now, an attribute of a block. For an example of how to ensure you are using this `baseFee`, see `demo.ts` in this repository.
```
Before EIP-1559 was activated, the most common way for searchers to submit transactions is with `gasPrice=0`, with an on-chain payment to `block.coinbase` conditional on the transaction's success. All transactions must pay `baseFee` now, an attribute of a block. For an example of how to ensure you are using this `baseFee`, see `demo.ts` in this repository.
```js
const block = await provider.getBlock(blockNumber)

@@ -107,2 +110,3 @@ const maxBaseFeeInFutureBlock = FlashbotsBundleProvider.getMaxBaseFeeInFutureBlock(block.baseFeePerGas, BLOCKS_IN_THE_FUTURE)

Simulate:
```ts

@@ -115,2 +119,3 @@ const signedTransactions = await flashbotsProvider.signBundle(transactionBundle)

Send:
```ts

@@ -124,2 +129,3 @@ const flashbotsTransactionResponse = await flashbotsProvider.sendBundle(

## FlashbotsTransactionResponse
After calling `sendBundle`, this provider will return a Promise of an object with helper functions related to the bundle you submitted.

@@ -137,2 +143,3 @@

Beyond target block number, an object can be passed in with optional attributes:
```ts

@@ -148,3 +155,3 @@ {

While each bundle targets only a single block, you can add a 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.
While each bundle targets only a single block, you can add a 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.

@@ -157,3 +164,3 @@ 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.

Transaction bundles will not be considered for inclusion if they include *any* transactions 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 bundles will not be considered for inclusion if they include _any_ transactions 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:

@@ -181,2 +188,3 @@ Transaction Nonce #1 = Failed (unrelated) token transfer

## Bundle and User Statistics
The Flashbots relay can also return statistics about you as a user (identified solely by your signing address) and any bundle previously submitted.

@@ -186,2 +194,3 @@

- `getBundleStats(bundleHash, targetBlockNumber)` returns data specific to a single bundle submission, including detailed timestamps for the various phases a bundle goes before reaching miners. You can get the bundleHash from the simulation:
```js

@@ -202,2 +211,36 @@ const simulation = await flashbotsProvider.simulate(signedTransactions, targetBlockNumber)

## Sending a Private Transaction
To send a _single_ transaction without having to send it as a bundle, use the `sendPrivateTransaction` function. This method allows us to process transactions faster and more efficiently than regular bundles. When you send a transaction using this method, we will try to send it to miners over the next 25 blocks (up to, but not past the target block number).
```js
const tx = {
from: wallet.address,
to: wallet.address,
value: "0x42",
gasPrice: BigNumber.from(99).mul(1e9), // 99 gwei
gasLimit: BigNumber.from(21000),
}
const privateTx = {
transaction: tx,
signer: wallet,
}
const res = await flashbotsProvider.sendPrivateTransaction(privateTx)
```
Optionally, you can set the following parameters to fine-tune your submission:
```js
// highest block number your tx can be included in
const maxBlockNumber = (await provider.getBlockNumber()) + 10;
// timestamp for simulations
const minTimestamp = 1645753192;
const res = await flashbotsProvider.sendPrivateTransaction(
privateTx,
{maxBlockNumber, minTimestamp}
)
```
## Flashbots on Goerli

@@ -208,2 +251,3 @@

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

@@ -215,2 +259,3 @@ import { providers } from 'ethers'

2. Set the relay endpoint to `https://relay-goerli.flashbots.net/`
```ts

@@ -217,0 +262,0 @@ const flashbotsProvider = await FlashbotsBundleProvider.create(

@@ -11,2 +11,3 @@ import { BlockTag, TransactionReceipt, TransactionRequest } from '@ethersproject/abstract-provider'

export const BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
const PRIVATE_TX_WAIT_BLOCKS = 25 // # of blocks

@@ -19,2 +20,7 @@ export enum FlashbotsBundleResolution {

export enum FlashbotsTransactionResolution {
TransactionIncluded,
TransactionDropped,
}
export enum FlashbotsBundleConflictType {

@@ -59,2 +65,9 @@ NoConflict,

export interface FlashbotsPrivateTransactionResponse {
transaction: TransactionAccountNonce
wait: () => Promise<FlashbotsTransactionResolution>
simulate: () => Promise<SimulationResponse>
receipts: () => Promise<Array<TransactionReceipt>>
}
export interface TransactionSimulationBase {

@@ -100,2 +113,4 @@ txHash: string

export type FlashbotsPrivateTransaction = FlashbotsPrivateTransactionResponse | RelayResponseError
export interface GetUserStatsResponseSuccess {

@@ -289,3 +304,3 @@ signing_address: string

const request = JSON.stringify(this.prepareBundleRequest('eth_sendBundle', [params]))
const request = JSON.stringify(this.prepareRelayRequest('eth_sendBundle', [params]))
const response = await this.request(request)

@@ -313,3 +328,3 @@ if (response.error !== undefined && response.error !== null) {

bundleTransactions,
wait: () => this.wait(bundleTransactions, targetBlockNumber, TIMEOUT_MS),
wait: () => this.waitForBlock(bundleTransactions, targetBlockNumber, TIMEOUT_MS),
simulate: () =>

@@ -336,2 +351,72 @@ this.simulate(

public async sendPrivateTransaction(
transaction: FlashbotsBundleTransaction | FlashbotsBundleRawTransaction,
opts?: {
maxBlockNumber?: number,
simulationTimestamp?: number,
},
): Promise<FlashbotsPrivateTransaction> {
let signedTransaction: string;
if ("signedTransaction" in transaction) {
signedTransaction = transaction.signedTransaction
} else {
signedTransaction = await transaction.signer.signTransaction(transaction.transaction)
}
const startBlockNumberPromise = this.genericProvider.getBlockNumber()
const params = {
tx: signedTransaction,
maxBlockNumber: opts?.maxBlockNumber,
}
const request = JSON.stringify(this.prepareRelayRequest('eth_sendPrivateTransaction', [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 transactionDetails = ethers.utils.parseTransaction(signedTransaction)
const privateTransaction = {
signedTransaction: signedTransaction,
hash: ethers.utils.keccak256(signedTransaction),
account: transactionDetails.from || "0x0",
nonce: transactionDetails.nonce,
}
const startBlockNumber = await startBlockNumberPromise
return {
transaction: privateTransaction,
wait: () => this.waitForTxInclusion(privateTransaction.hash, opts?.maxBlockNumber || startBlockNumber + 25, TIMEOUT_MS),
simulate: () =>
this.simulate(
[privateTransaction.signedTransaction],
startBlockNumber,
undefined,
opts?.simulationTimestamp,
),
receipts: () => this.fetchReceipts([privateTransaction]),
}
}
public async cancelPrivateTransaction(txHash: string): Promise<boolean | RelayResponseError> {
const params = {
txHash,
}
const request = JSON.stringify(this.prepareRelayRequest('eth_cancelPrivateTransaction', [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 true;
}
public async signBundle(bundledTransactions: Array<FlashbotsBundleTransaction | FlashbotsBundleRawTransaction>): Promise<Array<string>> {

@@ -366,3 +451,3 @@ const nonces: { [address: string]: BigNumber } = {}

private wait(transactionAccountNonces: Array<TransactionAccountNonce>, targetBlockNumber: number, timeout: number) {
private waitForBlock(transactionAccountNonces: Array<TransactionAccountNonce>, targetBlockNumber: number, timeout: number) {
return new Promise<FlashbotsBundleResolution>((resolve, reject) => {

@@ -411,3 +496,2 @@ let timer: NodeJS.Timer | null = null

done = true
this.genericProvider.removeListener('block', handler)

@@ -435,2 +519,54 @@ }

private waitForTxInclusion(transactionHash: string, maxBlockNumber: number, timeout: number) {
return new Promise<FlashbotsTransactionResolution>((resolve, reject) => {
let timer: NodeJS.Timer | null = null
let done = false
// runs on new block event
const handler = async (blockNumber: number) => {
if (blockNumber <= maxBlockNumber) {
// check tx status on mainnet
const sentTxStatus = await this.genericProvider.getTransaction(transactionHash)
if (sentTxStatus && sentTxStatus.confirmations >= 1) {
resolve(FlashbotsTransactionResolution.TransactionIncluded)
} else {
return
}
} else {
// tx not included in specified range, bail
this.genericProvider.removeListener('block', handler)
resolve(FlashbotsTransactionResolution.TransactionDropped)
}
if (timer) {
clearTimeout(timer)
}
if (done) {
return
}
done = true
this.genericProvider.removeListener('block', handler)
}
this.genericProvider.on('block', handler)
// time out if we've been trying for too long
if (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> {

@@ -441,3 +577,3 @@ const blockDetails = await this.genericProvider.getBlock('latest')

const params = [evmBlockNumber]
const request = JSON.stringify(this.prepareBundleRequest('flashbots_getUserStats', params))
const request = JSON.stringify(this.prepareRelayRequest('flashbots_getUserStats', params))
const response = await this.request(request)

@@ -460,3 +596,3 @@ if (response.error !== undefined && response.error !== null) {

const params = [{ bundleHash, blockNumber: evmBlockNumber }]
const request = JSON.stringify(this.prepareBundleRequest('flashbots_getBundleStats', params))
const request = JSON.stringify(this.prepareRelayRequest('flashbots_getBundleStats', params))
const response = await this.request(request)

@@ -507,3 +643,3 @@ if (response.error !== undefined && response.error !== null) {

]
const request = JSON.stringify(this.prepareBundleRequest('eth_callBundle', params))
const request = JSON.stringify(this.prepareRelayRequest('eth_callBundle', params))
const response = await this.request(request)

@@ -707,4 +843,4 @@ if (response.error !== undefined && response.error !== null) {

private prepareBundleRequest(
method: 'eth_callBundle' | 'eth_sendBundle' | 'flashbots_getUserStats' | 'flashbots_getBundleStats',
private prepareRelayRequest(
method: 'eth_callBundle' | 'eth_sendBundle' | 'eth_sendPrivateTransaction' | 'eth_cancelPrivateTransaction' | 'flashbots_getUserStats' | 'flashbots_getBundleStats',
params: RpcParams

@@ -711,0 +847,0 @@ ) {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc