Flashbots MEV-Share Client
Rust client library for Flashbots MEV-Share. Based on the MEV-Share specs and the TypeScript reference implementation.
Usage
In Cargo.toml
:
[dependencies]
mev_share_rs = "0.1.0"
Client initialization
First, client initialization may fail for various reasons and therefore returns a Result
. The quickest way to initialize a client, is to pass it a Signer
to use for Flashbots authentication and a Provider
.
use ethers::prelude::*;
use mev_share_rs::prelude::*;
let auth_wallet = LocalWallet::random();
let provider = Provider::connect("https://rpc.example/api_key");
In order to know which MEV-Share endpoint to query, the client needs to know which chain id you're on. You can either .await
the client to fetch it from the provider
:
let client = MevShareClient::new(auth_wallet, provider).await?;
or you can supply it yourself:
let client = MevShareClient::new_with_chain_id(auth_wallet, provider, 1)?;
Subscribing to MEV-Share events
Once you have a client, you can listen to the bundles submitted to the MEV-Share Flashbots relayer:
use mev_share_rs::prelude::*;
let mut stream = client.subscribe_bundles().await?;
while let Some(event) = stream.next().await {
let bundle = event?; // upstream any error
println!("Bundle received: {:?}", bundle);
}
Sending private transactions
You can also send private transactions and bundles with the MEV-Share API: mev_sendBundle
/mev_simBundle
and the latest version of eth_sendPrivateTransaction
use an upgraded bundle format than the order eth_sendBundle
/eth_callBundle
, in order to allow users to specify privacy and other guarantees. You can send a private transaction:
use mev_share_rs::prelude::*;
let pending_tx = client.send_private_transaction(
SendTransactionParams::builder()
// a signed `TypedTransaction`
.tx(tx)
// drop after 20 blocks
.max_block_number(current_block + 20)
.preferences(
// what to disclose
Some(set![
Hint::Hash,
Hint::Calldata,
Hint::Logs,
Hint::ContractAddress,
Hint::FunctionSelector
]),
// to whom
Some(set![
Builder::Flashbots,
]),
)
.build()
).await?;
wait for its inclusion in a block:
let (receipt, block) = pending_tx.inclusion().await?;
println!("Transaction included in block {}", block);
and/or handle errors:
match pending_tx.inclusion().await {
Err(Error::TransactionTimeout) =>
println!("the transaction was not included after 25 blocks or params.max_block_number"),
Err(Error::TransactionReverted) =>
println!("the transaction was reverted"),
Ok((receipt, block)) =>
println!("Transaction included in block {}", block),
}
If you're upstreaming the errors:
client
.send_private_transaction(tx_request)
.await?
.inclusion()
.await?;
Sending bundles
Similarly, you can send private bundles to the MEV-Share Flashbots relayer:
let pending_bundle = client.send_private_bundle(
SendBundleParams::builder()
.body(vec![
// a signed `TypedTransaction`
Signed { tx: tx1, can_revert: false },
// another signed `TypedTransaction`
Signed { tx: tx2, can_revert: false },
// a transaction we found in the mempool
Tx { hash: tx3 }
])
// drop after 3 blocks
.inclusion(current_block + 1, Some(current_block + 1 + 3))
// what to disclose to whom
.privacy(
// do not share any hints
None,
// send only to Flashbots
Some(set![Builder::Flashbots])
),
)
.build()
).await?;
await its inclusion in a block:
let (receipt, block) = pending_bundle.inclusion().await?;
println!("Bundle {:?} included in block {}", pending_bundle.hash, block);
and/or handle any eventual error:
match pending_bundle.inclusion().await {
Err(Error::BundleTimeout(_hashes , max_block)) =>
println!("bundle {:?} not included after max block {}", pending_bundle.hash, max_block),
Err(Error::BundleRevert(receipts)) =>
println!("bundle {:?} reverted: {:?}", pending_bundle.hash, receipts),
Err(Error::BundleDiscard(landed_receipts)) =>
println!("bundle has not been included, but some of the transactions landed: {:?}", landed_receipts),
Ok((receipts, block)) =>
println!("Bundle {:?} included in block {}", pending_bundle.hash, block),
}
Finally, if you upstream the errors, you can just:
let (bundle_receipts, included_block) = client
.send_budnle(bundle_request)
.await?
.inclusion()
.await?;
Simulating bundles
If you send too many bad bundles to the Flashbots API, you risk losing your searcher reputation. To avoid that, you can simulate bundles before sending them to Flashbots.
let simulation = client.simulate_bundle(
bundle_request.clone(),
SimulateBundleParams::builder()
.block(current_block + 1)
.build()
).await?;
// avoid sending a reverting bundle
if !simulation.success { return Err(Error::SimulationFailed(simulation)) }
// simulation success! send the bundle to Flashbots
client.send_bundle(bundle_request).await?.inclusion().await?;
Others
get_event_history
and get_event_history_info
allow you to query bundle submission history: check examples/historycal_stream_data
for an example. Finally, examples/send_backrun_bundle
gives you an idea on how you can put all of the above to use to listen to transactions hints from the relayer and backrun those you're interested in.
API reference
See [MevShareClient
].
Examples
ℹ️ Examples require a .env file (or that you populate your environment directly with the appropriate variables).
cd examples
cp .env.example .env
vim .env
You can run any example using cargo
, e.g.:
cargo run --example send_private_tx
Here's the current examples:
- send_private_tx.rs: sends a private transaction to the Flashbots MEV-Share relayer
- send_bundle.rs: simulates and sends a bundle with hints
- historical_stream_data.rs: query bundles history
- send_backrun_bundle.rs: subscribe to the Flashbots MEV-Share events stream in order to listen for submitted bundles and transactions and backrun them
Contributing, improvements, and further work
Contributions are welcome! If you'd like to contribute to this project, feel free to open a pull request. Here are a few improvements that are currently being worked on:
- move
examples/
from Goerli to Sepolia as Goerli is being deprecated -
PendingBundle::inclusion().await
could include the check for simulation errors via the flashbots APIs and return (an error) before we have on-chain proof the bundle is not landed (max_block reached or partial on-chain inclusion observed) - move from
ethers-rs
to the neweralloy
- add unit tests
- use a
JsonRpcClient
trait instead of forcingWs
If you'd like to see more, go ahead and open an issue.
Security
The tool requires a private key for signing transactions. Make sure you don't share your private key or .env file with anyone or commit it to a public repository.
License
This project is licensed under the MIT License