Skip to content
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"ethers": "^5.4.2"
},
"dependencies": {
"eciesjs": "^0.3.11",
"ts-node": "^9.1.0",
"typescript": "^4.1.2"
}
Expand Down
100 changes: 99 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { BlockTag, TransactionReceipt, TransactionRequest } from '@ethersproject/abstract-provider'
import { Networkish } from '@ethersproject/networks'
import { BaseProvider } from '@ethersproject/providers'
import { BaseProvider, TransactionResponse } from '@ethersproject/providers'
import { ConnectionInfo, fetchJson } from '@ethersproject/web'
import { BigNumber, ethers, providers, Signer } from 'ethers'
import { id } from 'ethers/lib/utils'
import { encode } from '@ethersproject/rlp'
import { encrypt } from 'eciesjs'

export const DEFAULT_FLASHBOTS_RELAY = 'https://relay.flashbots.net'

Expand All @@ -28,6 +30,21 @@ export interface FlashbotsOptions {
revertingTxHashes?: Array<string>
}

/**
* This type was added by Lucas, in order to package all the information needed in a submittable bundle, as the
* {@link FlashbotsBundleProvider.publish} method
*
* It's fields are described as:
* signedBundledTransactions: an array of signed transaction that compose the bundle the user intends to submit
* blockTarget: the block number within which the bundle is valid and includable in a block
* options: an instance of FlashbotsOptions, used to specify extra information about the bundle's validity
*/
export interface FlashbotsBundle {
Comment thread
lmanini marked this conversation as resolved.
signedBundledTransactions: Array<string>
blockTarget: number
options?: FlashbotsOptions
}

export interface TransactionAccountNonce {
hash: string
signedTransaction: string
Expand Down Expand Up @@ -415,6 +432,87 @@ export class FlashbotsBundleProvider extends providers.JsonRpcProvider {
}
}

/**
* Method to send a carrier tx into the public mempool
*
* @param bundle FlashbotsBundle with AT LEAST signed bundled transactions in signedBundledTransactions field obtained
* from {@link signBundle} method, and blockTarget.
* @param validatorPublicKey The public key of the validator that will be able to decrypt the bundle and include it
* into the bundle pool.
* @param signer Signer who will sign the carrier transaction.
* @param carrierTx TransactionRequest whose data field will carry the encrypted bundle : MAY be an incomplete
* object which will be populated with default values.
*
* @return Promise<TransactionResponse> Promise containing the response for the carrier tx
* */

public async publish(
Comment thread
lmanini marked this conversation as resolved.
Outdated
bundle: FlashbotsBundle,
validatorPublicKey: string,
signer: Signer,
carrierTx: TransactionRequest
): Promise<TransactionResponse> {
//RLP-serialize the given bundle
const serializedBundle = this.rlpSerializeBundle(bundle)

//Encrypt the encoded bundle with the passed validator pub_key
const encryptedBundle = encrypt(validatorPublicKey, Buffer.from(serializedBundle))
Comment thread
lmanini marked this conversation as resolved.

//Check if carrier_tx has minimum params, populate with defaults if not
/*
The following statement is intended to be used in order to support any type of incomplete TransactionRequest
received, populating it with default values if any one is missing
*/
await this.prepareCarrierTransaction(carrierTx, signer)

//Populate carrier_tx.data as : carrier_tx.data = MEV_Prefix | validator pub_key | Encrypt(validator pub_key, serialized bundle)
const mevPrefix = `0123` //this is a placeholder!
Comment thread
lmanini marked this conversation as resolved.

let payload = `0x`
payload += mevPrefix
payload += validatorPublicKey
payload += encryptedBundle.toString('hex')

carrierTx.data = payload

//Sign the transaction received as param with passed signer
const signedTx = await signer.signTransaction(carrierTx)

//Propagate carrier_tx into the public mempool and return Promise<TransactionResponse> for the carrier_tx
return this.genericProvider.sendTransaction(signedTx)
}

/**
* A private method to encode a FlashbotsBundle following the RLP serialization standard
* @param bundle the FlashbotsBundle instance to be serialized
* @return string the rlp encoded bundle
* @private
*/
private rlpSerializeBundle(bundle: FlashbotsBundle): string {
Comment thread
lmanini marked this conversation as resolved.
const stringifiedBundle = JSON.stringify(bundle)
const bytesBundle = Buffer.from(stringifiedBundle)
return encode(bytesBundle)
}

/**
* A private method to populate {@param carrier}'s missing fields with default values
* @param carrier an instance of TransactionRequest which will be the tx containing the full payload in its data field
* @param signer an instance of Signer, which is used to retrieve the correct nonce for the carrier tx, should it be missing
* @private
*/
private async prepareCarrierTransaction(carrier: TransactionRequest, signer: Signer) {
Comment thread
lmanini marked this conversation as resolved.
Outdated
if (!('to' in carrier)) carrier.to = '0x0000000000000000000000000000000000000000'
Comment thread
lmanini marked this conversation as resolved.
Outdated
if (!('value' in carrier)) carrier.value = 0
if (!('gasPrice' in carrier)) carrier.gasPrice = await this.genericProvider.getGasPrice()
Comment thread
lmanini marked this conversation as resolved.
Outdated
if (!('nonce' in carrier)) carrier.nonce = await this.genericProvider.getTransactionCount(await signer.getAddress())
if (!('gasLimit' in carrier)) carrier.gasLimit = 210000 //Default value of 210k gasLimit, which is 10x of a simple transfers gasLimit

//TODO How can I calculate the exact gasLimit?? --> Found different resource that said that G_txdatanonzero is either 16 or 68
Comment thread
lmanini marked this conversation as resolved.
Outdated
/*if (!('gasLimit' in carrier)) {
carrier.gasLimit = 21000 + 16 * data.lenghtInBytes ?? (16 is G_txdatanonzero in the yellow paper's fee schedule)
}*/
}

private async request(request: string) {
const connectionInfo = { ...this.connectionInfo }
connectionInfo.headers = {
Expand Down