Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 34 additions & 34 deletions src/tasks/submit/flashbots/provider.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
//! A generic Flashbots bundle API wrapper.
use std::borrow::Cow;

use crate::config::BuilderConfig;
use alloy::{
primitives::{BlockNumber, keccak256},
rpc::types::mev::{EthBundleHash, MevSendBundle, SimBundleResponse},
rpc::{
json_rpc::{Id, Response, ResponsePayload, RpcRecv, RpcSend},
types::mev::{EthBundleHash, MevSendBundle, SimBundleResponse},
},
signers::Signer,
};
use eyre::Context as _;
use init4_bin_base::utils::signer::LocalOrAws;
use reqwest::header::CONTENT_TYPE;
use serde_json::json;

/// A wrapper over a `Provider` that adds Flashbots MEV bundle helpers.
/// An RPC connection to relevant Flashbots endpoints.
#[derive(Debug)]
pub struct Flashbots {
/// The base URL for the Flashbots API.
Expand All @@ -34,48 +38,43 @@ impl Flashbots {
}

/// Sends a bundle via `mev_sendBundle`.
pub async fn send_bundle(&self, bundle: MevSendBundle) -> eyre::Result<EthBundleHash> {
let params = serde_json::to_value(bundle)?;
let v = self.raw_call("mev_sendBundle", params).await?;
let hash: EthBundleHash =
serde_json::from_value(v.get("result").cloned().unwrap_or(serde_json::Value::Null))?;
Ok(hash)
pub async fn send_bundle(&self, bundle: &MevSendBundle) -> eyre::Result<EthBundleHash> {
self.raw_call("mev_sendBundle", &[bundle]).await
}

/// Simulate a bundle via `mev_simBundle`.
pub async fn simulate_bundle(&self, bundle: MevSendBundle) -> eyre::Result<()> {
let params = serde_json::to_value(bundle)?;
let v = self.raw_call("mev_simBundle", params).await?;
let resp: SimBundleResponse =
serde_json::from_value(v.get("result").cloned().unwrap_or(serde_json::Value::Null))?;
pub async fn simulate_bundle(&self, bundle: &MevSendBundle) -> eyre::Result<()> {
let resp: SimBundleResponse = self.raw_call("mev_simBundle", &[bundle]).await?;
dbg!("successfully simulated bundle", &resp);
Ok(())
}

/// Fetches the bundle status by hash
pub async fn bundle_status(
&self,
_hash: EthBundleHash,
hash: EthBundleHash,
block_number: BlockNumber,
) -> eyre::Result<()> {
let params = json!({ "bundleHash": _hash, "blockNumber": block_number });
let _ = self.raw_call("flashbots_getBundleStatsV2", params).await?;
let params = json!({ "bundleHash": hash, "blockNumber": block_number });
let _resp: serde_json::Value =
self.raw_call("flashbots_getBundleStatsV2", &[params]).await?;

Ok(())
}

/// Makes a raw JSON-RPC call with the Flashbots signature header to the method with the given params.
async fn raw_call(
async fn raw_call<Params: RpcSend, Payload: RpcRecv>(
&self,
method: &str,
params: serde_json::Value,
) -> eyre::Result<serde_json::Value> {
let params = match params {
serde_json::Value::Array(_) => params,
other => serde_json::Value::Array(vec![other]),
};

let body = json!({"jsonrpc":"2.0","id":1,"method":method,"params":params});
let body_bz = serde_json::to_vec(&body)?;
params: &Params,
) -> eyre::Result<Payload> {
let req = alloy::rpc::json_rpc::Request::new(
Cow::Owned(method.to_string()),
Id::Number(1),
params,
);
let body_bz = serde_json::to_vec(&req)?;
drop(req);

let value = self.compute_signature(&body_bz).await?;

Expand All @@ -88,18 +87,19 @@ impl Flashbots {
.send()
.await?;

let text = resp.text().await?;
let v: serde_json::Value =
serde_json::from_str(&text).wrap_err("failed to parse flashbots JSON")?;
if let Some(err) = v.get("error") {
eyre::bail!("flashbots error: {err}");
let resp: Response<Payload> = resp.json().await?;

match resp.payload {
ResponsePayload::Success(payload) => Ok(payload),
ResponsePayload::Failure(err) => {
eyre::bail!("flashbots error: {err}");
}
}
Ok(v)
}

/// Builds an EIP-191 signature for the given body bytes.
async fn compute_signature(&self, body_bz: &[u8]) -> Result<String, eyre::Error> {
let payload = format!("0x{:x}", keccak256(body_bz));
let payload = keccak256(body_bz).to_string();
let signature = self.signer.sign_message(payload.as_ref()).await?;
dbg!(signature.to_string());
let address = self.signer.address();
Expand Down
6 changes: 2 additions & 4 deletions src/tasks/submit/flashbots/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,13 @@ impl FlashbotsTask {
};

// simulate the bundle against Flashbots then send the bundle
if let Err(err) =
flashbots.simulate_bundle(bundle.clone()).instrument(span.clone()).await
{
if let Err(err) = flashbots.simulate_bundle(&bundle).instrument(span.clone()).await {
span_scoped!(span, debug!(%err, "bundle simulation failed"));
continue;
}

let _ = flashbots
.send_bundle(bundle)
.send_bundle(&bundle)
.instrument(span.clone())
.await
.inspect(|bundle_hash| {
Expand Down
2 changes: 1 addition & 1 deletion tests/flashbots_provider_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async fn test_simulate_valid_bundle_sepolia() {
dbg!("submitting bundle with 1 tx", &bundle_body);
let bundle = MevSendBundle::new(latest_block, Some(0), ProtocolVersion::V0_1, bundle_body);

flashbots.simulate_bundle(bundle).await.expect("failed to simulate bundle");
flashbots.simulate_bundle(&bundle).await.expect("failed to simulate bundle");
}

async fn get_test_provider() -> Flashbots {
Expand Down
Loading