Skip to content

optimiz-r/mev-share-client-rs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Flashbots MEV-Share Client

License: MIT

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:

  1. send_private_tx.rs: sends a private transaction to the Flashbots MEV-Share relayer
  2. send_bundle.rs: simulates and sends a bundle with hints
  3. historical_stream_data.rs: query bundles history
  4. 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 newer alloy
  • add unit tests
  • use a JsonRpcClient trait instead of forcing Ws

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

About

Rust client library for Flashbots MEV-Share

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages