1use crate::{ext::FLASHBOTS_SIGNATURE_HEADER, ProviderCall};
2use alloy_json_rpc::{RpcRecv, RpcSend};
3use alloy_primitives::{hex, hex::FromHexError, keccak256, Address, Signature, SignatureError};
4use alloy_rpc_client::RpcCall;
5use alloy_signer::Signer;
6use alloy_transport::{TransportErrorKind, TransportResult};
7use http::{HeaderMap, HeaderName, HeaderValue};
8use std::future::IntoFuture;
9
10#[derive(Debug, thiserror::Error)]
12pub enum FlashbotsSignatureError {
13 #[error("invalid signature format, expected `address:signature`")]
15 InvalidFormat,
16 #[error("invalid address")]
18 InvalidAddress(#[from] FromHexError),
19 #[error("invalid signature")]
21 InvalidSignature(#[from] SignatureError),
22 #[error("signature mismatch: expected {expected}, actual {actual}")]
24 SignatureMismatch {
25 expected: Address,
27 actual: Address,
29 },
30}
31
32pub struct MevBuilder<Params, Resp, Output = Resp, Map = fn(Resp) -> Output>
34where
35 Params: RpcSend,
36 Resp: RpcRecv,
37 Map: Fn(Resp) -> Output,
38{
39 inner: RpcCall<Params, Resp, Output, Map>,
40 signer: Option<Box<dyn Signer + Send + Sync>>,
41}
42
43impl<Params, Resp, Output, Map> std::fmt::Debug for MevBuilder<Params, Resp, Output, Map>
44where
45 Params: RpcSend + std::fmt::Debug,
46 Resp: RpcRecv + std::fmt::Debug,
47 Output: std::fmt::Debug,
48 Map: Fn(Resp) -> Output + Clone + std::fmt::Debug,
49{
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("MevBuilder").field("inner", &self.inner).finish()
52 }
53}
54
55impl<Params, Resp, Output, Map> MevBuilder<Params, Resp, Output, Map>
56where
57 Params: RpcSend,
58 Resp: RpcRecv,
59 Map: Fn(Resp) -> Output + Clone,
60{
61 pub const fn new_rpc(inner: RpcCall<Params, Resp, Output, Map>) -> Self {
63 Self { inner, signer: None }
64 }
65}
66
67impl<Params, Resp, Output, Map> From<RpcCall<Params, Resp, Output, Map>>
68 for MevBuilder<Params, Resp, Output, Map>
69where
70 Params: RpcSend,
71 Resp: RpcRecv,
72 Map: Fn(Resp) -> Output + Clone,
73{
74 fn from(inner: RpcCall<Params, Resp, Output, Map>) -> Self {
75 Self::new_rpc(inner)
76 }
77}
78
79impl<Params, Resp, Output, Map> MevBuilder<Params, Resp, Output, Map>
80where
81 Params: RpcSend,
82 Resp: RpcRecv,
83 Map: Fn(Resp) -> Output,
84{
85 pub fn with_auth<S: Signer + Send + Sync + 'static>(mut self, signer: S) -> Self {
90 self.signer = Some(Box::new(signer));
91 self
92 }
93}
94
95impl<Params, Resp, Output, Map> IntoFuture for MevBuilder<Params, Resp, Output, Map>
96where
97 Params: RpcSend + 'static,
98 Resp: RpcRecv,
99 Output: 'static,
100 Map: Fn(Resp) -> Output + Send + 'static,
101{
102 type Output = TransportResult<Output>;
103 type IntoFuture = ProviderCall<Params, Resp, Output, Map>;
104
105 fn into_future(self) -> Self::IntoFuture {
106 if let Some(signer) = self.signer {
107 let fut = async move {
108 let body = serde_json::to_string(&self.inner.request())
110 .map_err(TransportErrorKind::custom)?;
111 let signature = sign_flashbots_payload(body, &signer)
112 .await
113 .map_err(TransportErrorKind::custom)?;
114
115 let headers = HeaderMap::from_iter([(
117 HeaderName::from_static(FLASHBOTS_SIGNATURE_HEADER),
118 HeaderValue::from_str(signature.as_str())
119 .map_err(TransportErrorKind::custom)?,
120 )]);
121
122 let rpc_call = self.inner.map_meta(|mut meta| {
124 meta.extensions_mut().get_or_insert_default::<HeaderMap>().extend(headers);
125 meta
126 });
127
128 rpc_call.await
129 };
130 return ProviderCall::BoxedFuture(Box::pin(fut));
131 }
132 ProviderCall::RpcCall(self.inner)
133 }
134}
135
136pub async fn sign_flashbots_payload<S: Signer + Send + Sync>(
157 body: String,
158 signer: &S,
159) -> Result<String, alloy_signer::Error> {
160 let message_hash = keccak256(body.as_bytes()).to_string();
161 let signature = signer.sign_message(message_hash.as_bytes()).await?;
162
163 let mut sig_bytes = [0u8; 65];
165 sig_bytes[..32].copy_from_slice(&signature.r().to_be_bytes::<32>());
166 sig_bytes[32..64].copy_from_slice(&signature.s().to_be_bytes::<32>());
167 sig_bytes[64] = signature.v() as u8;
168 Ok(format!("{}:{}", signer.address(), hex::encode_prefixed(sig_bytes)))
169}
170
171pub fn verify_flashbots_signature(
190 signature_header: &str,
191 body: &[u8],
192) -> Result<Address, FlashbotsSignatureError> {
193 let (address_str, sig_str) =
194 signature_header.split_once(':').ok_or(FlashbotsSignatureError::InvalidFormat)?;
195
196 let expected = address_str.parse::<Address>()?;
197 let signature = sig_str.parse::<Signature>()?;
198
199 let message_hash = keccak256(body).to_string();
200 let actual = signature.recover_address_from_msg(message_hash.as_bytes())?;
201
202 if actual != expected {
203 return Err(FlashbotsSignatureError::SignatureMismatch { expected, actual });
204 }
205
206 Ok(actual)
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use alloy_primitives::{address, b256};
213 use alloy_signer_local::PrivateKeySigner;
214
215 const TEST_BODY: &str = "sign this message";
216 const TEST_SIGNATURE: &str = "0xd5F5175D014F28c85F7D67A111C2c9335D7CD771:0x983dc7c520db0d287faff3cd0aef81d5a7f4ffd3473440d3f705da16299724271f660b6fe367f455b205bc014eff3e20defd011f92000f94d39365ca0bc7867200";
217
218 #[tokio::test]
219 async fn test_sign_flashbots_payload() {
220 let signer = PrivateKeySigner::from_bytes(&b256!(
221 "0x0000000000000000000000000000000000000000000000000000000000123456"
222 ))
223 .unwrap();
224 let signature = sign_flashbots_payload(TEST_BODY.to_string(), &signer).await.unwrap();
225 assert_eq!(signature, TEST_SIGNATURE);
226 }
227
228 #[tokio::test]
229 async fn test_verify_flashbots_signature_roundtrip() {
230 let signer = PrivateKeySigner::from_bytes(&b256!(
231 "0x0000000000000000000000000000000000000000000000000000000000123456"
232 ))
233 .unwrap();
234
235 let signature = sign_flashbots_payload(TEST_BODY.to_string(), &signer).await.unwrap();
236 let recovered = verify_flashbots_signature(&signature, TEST_BODY.as_bytes()).unwrap();
237 assert_eq!(recovered, signer.address());
238 }
239
240 #[test]
241 fn test_verify_flashbots_signature_v0() {
242 let recovered = verify_flashbots_signature(TEST_SIGNATURE, TEST_BODY.as_bytes()).unwrap();
244 assert_eq!(recovered, address!("0xd5F5175D014F28c85F7D67A111C2c9335D7CD771"));
245 }
246
247 #[test]
248 fn test_verify_flashbots_signature_v27() {
249 let signature_v27 = format!("{}1b", &TEST_SIGNATURE[..TEST_SIGNATURE.len() - 2]);
251 let recovered = verify_flashbots_signature(&signature_v27, TEST_BODY.as_bytes()).unwrap();
252 assert_eq!(recovered, address!("0xd5F5175D014F28c85F7D67A111C2c9335D7CD771"));
253 }
254
255 #[test]
256 fn test_verify_flashbots_signature_invalid_format() {
257 let result = verify_flashbots_signature("invalid", b"body");
258 assert!(matches!(result, Err(FlashbotsSignatureError::InvalidFormat)));
259 }
260
261 #[test]
262 fn test_verify_flashbots_signature_invalid_address() {
263 let result = verify_flashbots_signature("notanaddress:0x1234", b"body");
264 assert!(matches!(result, Err(FlashbotsSignatureError::InvalidAddress(_))));
265 }
266
267 #[test]
268 fn test_verify_flashbots_signature_invalid_signature() {
269 let result = verify_flashbots_signature(
270 "0xd5F5175D014F28c85F7D67A111C2c9335D7CD771:0xinvalid",
271 b"body",
272 );
273 assert!(matches!(result, Err(FlashbotsSignatureError::InvalidSignature(_))));
274 }
275
276 #[test]
277 fn test_verify_flashbots_signature_mismatch_wrong_address() {
278 let wrong_address = Address::repeat_byte(0x01);
279 let sig_part = TEST_SIGNATURE.split_once(':').unwrap().1;
280 let mismatched = format!("{wrong_address}:{sig_part}");
281 let result = verify_flashbots_signature(&mismatched, TEST_BODY.as_bytes());
282 assert!(matches!(result, Err(FlashbotsSignatureError::SignatureMismatch { .. })));
283 }
284
285 #[test]
286 fn test_verify_flashbots_signature_mismatch_wrong_body() {
287 let result = verify_flashbots_signature(TEST_SIGNATURE, b"wrong body");
288 assert!(matches!(result, Err(FlashbotsSignatureError::SignatureMismatch { .. })));
289 }
290}