Skip to main content

Sending Bundles

While the new bundle spec may look complex, a bundle can be sent with default parameters using a client library, which ends up looking quite similar to sending bundles on mev-boost.

Sending a Bundle

Placeholders

Variables in ALL_CAPS are placeholders. To use this code, replace them with your own data.

import MevShareClient, { BundleParams } from '@flashbots/mev-share-client'

const mevShareClient = MevShareClient.useEthereumMainnet(authSigner)

// ...

const bundle = [
{hash: PENDING_TX_HASH},
{tx: await wallet.signTransaction(BACKRUN_TX), canRevert: false},
]

const params: BundleParams = {
inclusion: {
block: targetBlock,
// target several blocks with `maxBlock`
maxBlock: targetBlock + 3,
},
body: bundle,
// send the bundles to more builders to speed up inclusion
privacy: {
builders: ["flashbots", "beaverbuild.org", "rsync", "Titan"]
}
}

const bundleResult = await mevShareClient.sendBundle(params)

The first transaction in the bundle contains the hash of a pending transaction, which we presumably got from listening to the event stream. The second transaction is one we sign ourself, which backruns the first transaction, presumably for a profit.

Bundle endpoint

MEV-Share bundles are sent to the same RPC endpoint as MEV-Boost bundles: https://relay.flashbots.net

See mev-share-client-ts for a full working example.


The params not included in the previous example can be used to configure more advanced conditions for your bundle's execution and the MEV refunds you earn.

Share bundle data

To earn more MEV refunds and improve your bundle's chances of inclusion, you can specify data about your bundle's transactions that you wish to share with other searchers. The data that is shared is specified via the privacy parameter.

bundles with signed txs only

Note that bundles with transactions including the hash parameter are considered "unmatched", and will throw an error.

const params: BundleParams = {
inclusion: {
block: 17539448,
maxBlock: 17539450
},
body: [
{tx: "0x02...", canRevert: false},
{tx: "0x02...", canRevert: false},
],
privacy: {
hints: {
calldata: true,
logs: true,
txHash: true,
},
},
}

Even sharing only the tx_hash hint can be enough to earn some extra MEV. The only requirement to earn it is that your bundle is included in another searcher's bundle.

This strategy is particularly relevant to searchers who operate on public mempool transactions, and/or employ strategies that leave leftover MEV on the table (for example, if your strategy changes prices enough for a backrun arbitrage to be profitable).

Nest bundles (be a MEV-Share Node)

MEV-Share Nodes nest bundles to build composite bundles that are more profitable.

const params: BundleParams = {
inclusion: {
block: TARGET_BLOCK,
},
body: {
{hash: PENDING_TX_HASH},
{bundle: {
inclusion: {...},
body: {...},
privacy: {...},
validity: {...},
}}
},
}

Builder Inheritance

By default, when a user specifies which builders their transactions should be sent to (by setting builders), those settings are inherited by bundles which use those transactions.

Searchers can restrict these settings by specifying builders in the bundle's privacy parameters. Specifically, by setting builders, the bundle is sent to the intersection of all builders specified by each of the bundle's transactions, and the builders specified in the bundle's builders parameter.

Simulating Bundles

Bundles matched by the MEV-Share Node can be simulated to check MEV profits. To simulate a bundle, we use the mev_simBundle endpoint.

only matched bundles can be simulated

Bundle simulations can only be executed on matched bundles, which contain only signed transactions, or nested bundles which also contain only signed transactions. Bundles with transactions including the hash parameter are considered "unmatched", and will throw an error.

const params: BundleParams = {
inclusion: {
block: TARGET_BLOCK
},
body: [
{bundle: {
version: "beta-1",
inclusion: {
block: TARGET_BLOCK,
maxBlock: TARGET_BLOCK + 3,
},
body: [{tx: SIGNED_TX_1}],
privacy: {
hints: {calldata: true},
},
validity: {
refundConfig: [{address: REFUND_ADDRESS, percent: 100}]
}
}},
{tx: SIGNED_TX_2},
],
validity: {
refund: [{bodyIdx: 0, percent: 90}]
}
}

const simResult = await mevShareClient.simulateBundle(params)