1use crate::{ext::FLASHBOTS_SIGNATURE_HEADER, ProviderCall};
2use alloy_json_rpc::{RpcRecv, RpcSend};
3use alloy_primitives::{hex, keccak256};
4use alloy_rpc_client::RpcCall;
5use alloy_signer::Signer;
6use alloy_transport::{TransportErrorKind, TransportResult};
7use http::{HeaderMap, HeaderName, HeaderValue};
8use std::future::IntoFuture;
9
10pub struct MevBuilder<Params, Resp, Output = Resp, Map = fn(Resp) -> Output>
12where
13 Params: RpcSend,
14 Resp: RpcRecv,
15 Map: Fn(Resp) -> Output,
16{
17 inner: RpcCall<Params, Resp, Output, Map>,
18 signer: Option<Box<dyn Signer + Send + Sync>>,
19}
20
21impl<Params, Resp, Output, Map> std::fmt::Debug for MevBuilder<Params, Resp, Output, Map>
22where
23 Params: RpcSend + std::fmt::Debug,
24 Resp: RpcRecv + std::fmt::Debug,
25 Output: std::fmt::Debug,
26 Map: Fn(Resp) -> Output + Clone + std::fmt::Debug,
27{
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("MevBuilder").field("inner", &self.inner).finish()
30 }
31}
32
33impl<Params, Resp, Output, Map> MevBuilder<Params, Resp, Output, Map>
34where
35 Params: RpcSend,
36 Resp: RpcRecv,
37 Map: Fn(Resp) -> Output + Clone,
38{
39 pub const fn new_rpc(inner: RpcCall<Params, Resp, Output, Map>) -> Self {
41 Self { inner, signer: None }
42 }
43}
44
45impl<Params, Resp, Output, Map> From<RpcCall<Params, Resp, Output, Map>>
46 for MevBuilder<Params, Resp, Output, Map>
47where
48 Params: RpcSend,
49 Resp: RpcRecv,
50 Map: Fn(Resp) -> Output + Clone,
51{
52 fn from(inner: RpcCall<Params, Resp, Output, Map>) -> Self {
53 Self::new_rpc(inner)
54 }
55}
56
57impl<Params, Resp, Output, Map> MevBuilder<Params, Resp, Output, Map>
58where
59 Params: RpcSend,
60 Resp: RpcRecv,
61 Map: Fn(Resp) -> Output,
62{
63 pub fn with_auth<S: Signer + Send + Sync + 'static>(mut self, signer: S) -> Self {
68 self.signer = Some(Box::new(signer));
69 self
70 }
71}
72
73impl<Params, Resp, Output, Map> IntoFuture for MevBuilder<Params, Resp, Output, Map>
74where
75 Params: RpcSend + 'static,
76 Resp: RpcRecv,
77 Output: 'static,
78 Map: Fn(Resp) -> Output + Send + 'static,
79{
80 type Output = TransportResult<Output>;
81 type IntoFuture = ProviderCall<Params, Resp, Output, Map>;
82
83 fn into_future(self) -> Self::IntoFuture {
84 if let Some(signer) = self.signer {
85 let fut = async move {
86 let body = serde_json::to_string(&self.inner.request())
88 .map_err(TransportErrorKind::custom)?;
89 let signature = sign_flashbots_payload(body, &signer)
90 .await
91 .map_err(TransportErrorKind::custom)?;
92
93 let headers = HeaderMap::from_iter([(
95 HeaderName::from_static(FLASHBOTS_SIGNATURE_HEADER),
96 HeaderValue::from_str(signature.as_str())
97 .map_err(TransportErrorKind::custom)?,
98 )]);
99
100 let rpc_call = self.inner.map_meta(|meta| {
102 let mut meta = meta;
103 meta.extensions_mut().insert(headers);
104 meta
105 });
106
107 rpc_call.await
108 };
109 return ProviderCall::BoxedFuture(Box::pin(fut));
110 }
111 ProviderCall::RpcCall(self.inner)
112 }
113}
114
115pub async fn sign_flashbots_payload<S: Signer + Send + Sync>(
120 body: String,
121 signer: &S,
122) -> Result<String, alloy_signer::Error> {
123 let message_hash = keccak256(body.as_bytes()).to_string();
124 let signature = signer.sign_message(message_hash.as_bytes()).await?;
125
126 let mut sig_bytes = [0u8; 65];
128 sig_bytes[..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
129 sig_bytes[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
130 sig_bytes[64] = signature.v() as u8;
131 Ok(format!("{}:{}", signer.address(), hex::encode_prefixed(sig_bytes)))
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use alloy_primitives::b256;
138 use alloy_signer_local::PrivateKeySigner;
139
140 #[tokio::test]
141 async fn test_sign_flashbots_payload() {
142 let signer = PrivateKeySigner::from_bytes(&b256!(
143 "0x0000000000000000000000000000000000000000000000000000000000123456"
144 ))
145 .unwrap();
146 let body = "sign this message".to_string();
147 let signature = sign_flashbots_payload(body.clone(), &signer).await.unwrap();
148 assert_eq!(signature, "0xd5F5175D014F28c85F7D67A111C2c9335D7CD771:0x983dc7c520db0d287faff3cd0aef81d5a7f4ffd3473440d3f705da16299724271f660b6fe367f455b205bc014eff3e20defd011f92000f94d39365ca0bc7867200");
149 }
150}