1use crate::global::{MAIN_URL, SEPOLIA_TEST_URL};
2use crate::types::{
3 BlockResponse, Bundle, BundleByHashResponse, BundlePriceResponse, BundleReceipt, BundleStats,
4 CancelBundlesRequest, CancelBundlesResponse, FlashbotsError, GasPriceResponse, RelayInfo,
5 SimulateBundleResponse, UserStats, UserStatus,
6};
7use crate::types::{FlashbotsResult, SendBundleResponse, SimulateBundleRequest};
8use ethers::core::k256::elliptic_curve::rand_core::block;
9use ethers::types::{Address, H256, U64};
10use reqwest::Client;
11use serde_json::Value;
12use std::time::Duration;
13use tokio::time::sleep;
14pub mod bundler;
15pub mod global;
16pub mod tool;
17pub mod transaction;
18pub mod types;
19pub mod validator;
20
21#[derive(Debug, Clone)]
23pub struct FlashbotsClientConfig {
24 pub base_url: String,
25 pub timeout_seconds: u64,
26 pub max_retries: u32,
27 pub retry_delay_seconds: u64,
28}
29
30impl Default for FlashbotsClientConfig {
31 fn default() -> Self {
32 Self {
33 base_url: MAIN_URL.to_string(),
34 timeout_seconds: 30,
35 max_retries: 3,
36 retry_delay_seconds: 2,
37 }
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct FlashbotsClient {
44 client: Client,
45 config: FlashbotsClientConfig,
46}
47
48impl FlashbotsClient {
49 pub fn new(config: FlashbotsClientConfig) -> Self {
51 Self {
52 client: Client::new(),
53 config,
54 }
55 }
56
57 pub fn new_mainnet() -> Self {
59 Self::new(FlashbotsClientConfig {
60 base_url: MAIN_URL.to_string(),
61 ..Default::default()
62 })
63 }
64
65 pub fn new_sepolia() -> Self {
67 Self::new(FlashbotsClientConfig {
68 base_url: SEPOLIA_TEST_URL.to_string(),
69 ..Default::default()
70 })
71 }
72
73 pub fn with_max_retries(mut self, max_retries: u32) -> Self {
75 self.config.max_retries = max_retries;
76 self
77 }
78
79 pub fn with_timeout(mut self, timeout_seconds: u64) -> Self {
81 self.config.timeout_seconds = timeout_seconds;
82 self
83 }
84
85 pub async fn send_bundle(&self, bundle: Bundle) -> FlashbotsResult<SendBundleResponse> {
97 let url = format!("{}/", self.config.base_url);
98 self.post(&url, &bundle).await
99 }
100
101 pub async fn send_bundle_with_retry(
103 &self,
104 bundle: Bundle,
105 ) -> FlashbotsResult<SendBundleResponse> {
106 self.retry_operation(|| self.send_bundle(bundle.clone()))
107 .await
108 }
109
110 pub async fn simulate_bundle(
122 &self,
123 simulation: SimulateBundleRequest,
124 ) -> FlashbotsResult<SimulateBundleResponse> {
125 let url = format!("{}/simulate", self.config.base_url);
126 self.post(&url, &simulation).await
127 }
128
129 pub async fn simulate_bundle_with_retry(
131 &self,
132 simulation: SimulateBundleRequest,
133 ) -> FlashbotsResult<SimulateBundleResponse> {
134 self.retry_operation(|| self.simulate_bundle(simulation.clone()))
135 .await
136 }
137
138 pub async fn get_bundle_stats(
140 &self,
141 bundle_hash: H256,
142 block_number: U64,
143 ) -> FlashbotsResult<BundleStats> {
144 let url = format!(
145 "{}/bundleStats/0x{}/{}",
146 self.config.base_url,
147 hex::encode(bundle_hash.as_bytes()),
148 block_number
149 );
150 self.get(&url).await
151 }
152
153 pub async fn get_bundle_receipt(&self, bundle_hash: H256) -> FlashbotsResult<BundleReceipt> {
155 let url = format!(
156 "{}/bundles/0x{}",
157 self.config.base_url,
158 hex::encode(bundle_hash.as_bytes())
159 );
160 self.get(&url).await
161 }
162
163 pub async fn get_user_stats(
165 &self,
166 address: Address,
167 block_number: U64,
168 ) -> FlashbotsResult<UserStats> {
169 let url = format!(
170 "{}/userStats/{}/{}",
171 self.config.base_url, address, block_number
172 );
173 self.get(&url).await
174 }
175
176 pub async fn get_health(&self) -> FlashbotsResult<bool> {
187 let url = format!("{}/health", self.config.base_url);
188 let response: Value = self.get(&url).await?;
189 Ok(response
190 .get("status")
191 .and_then(|s| s.as_str())
192 .map(|s| s == "healthy")
193 .unwrap_or(false))
194 }
195
196 pub async fn get_bundle_price(
198 &self,
199 block_number: U64,
200 ) -> FlashbotsResult<BundlePriceResponse> {
201 let url = format!("{}/bundlePrice/{}", self.config.base_url, block_number);
202 self.get(&url).await
203 }
204
205 pub async fn wait_for_bundle_inclusion(
221 &self,
222 bundle_hash: H256,
223 timeout_blocks: u64,
224 ) -> FlashbotsResult<Option<BundleReceipt>> {
225 let mut current_wait = 0;
226 while current_wait < timeout_blocks {
227 match self.get_bundle_receipt(bundle_hash).await {
228 Ok(receipt) => return Ok(Some(receipt)),
229 Err(FlashbotsError::ApiError(e))
230 if e.contains("not found") || e.contains("Bundle not found") =>
231 {
232 current_wait += 1;
233 log::info!(
234 "Bundle not yet included, waiting... (attempt {}/{})",
235 current_wait,
236 timeout_blocks
237 );
238 sleep(Duration::from_secs(12)).await;
239 }
240 Err(e) => return Err(e),
241 }
242 }
243
244 log::warn!("Bundle not included within {} blocks", timeout_blocks);
245 Ok(None)
246 }
247
248 pub async fn send_and_wait_for_bundle(
263 &self,
264 bundle: Bundle,
265 timeout_blocks: u64,
266 ) -> FlashbotsResult<Option<BundleReceipt>> {
267 let response = self.send_bundle_with_retry(bundle).await?;
268 log::info!("Bundle sent successfully, hash: {:?}", response.bundle_hash);
269 self.wait_for_bundle_inclusion(response.bundle_hash, timeout_blocks)
270 .await
271 }
272
273 pub async fn simulate_and_send_bundle(
275 &self,
276 bundle: Bundle,
277 simulation_params: SimulateBundleRequest,
278 ) -> FlashbotsResult<Option<SendBundleResponse>> {
279 let simulation_result = self.simulate_bundle_with_retry(simulation_params).await?;
280 if !simulation_result.success {
281 log::warn!("Bundle simulation failed: {:?}", simulation_result.error);
282 return Ok(None);
283 }
284 log::info!(
285 "Bundle simulation successful: gas_used={}, mev_reward={:?}",
286 simulation_result.gas_used,
287 simulation_result.mev_reward
288 );
289 let response = self.send_bundle_with_retry(bundle).await?;
290 Ok(Some(response))
291 }
292
293 pub async fn send_bundles(
295 &self,
296 bundles: Vec<Bundle>,
297 ) -> FlashbotsResult<Vec<FlashbotsResult<SendBundleResponse>>> {
298 let mut results = Vec::new();
299 for bundle in bundles {
300 let result = self.send_bundle_with_retry(bundle).await;
301 results.push(result);
302 }
303 Ok(results)
304 }
305
306 async fn post<T: serde::Serialize, R: serde::de::DeserializeOwned>(
307 &self,
308 url: &str,
309 body: &T,
310 ) -> FlashbotsResult<R> {
311 let response = self
312 .client
313 .post(url)
314 .timeout(Duration::from_secs(self.config.timeout_seconds))
315 .json(body)
316 .send()
317 .await
318 .map_err(|e| FlashbotsError::Error(format!("{:?}", e)))?;
319 self.handle_response(response).await
320 }
321
322 async fn get<R: serde::de::DeserializeOwned>(&self, url: &str) -> FlashbotsResult<R> {
323 let response = self
324 .client
325 .get(url)
326 .timeout(Duration::from_secs(self.config.timeout_seconds))
327 .send()
328 .await
329 .map_err(|e| FlashbotsError::Error(format!("{:?}", e)))?;
330 self.handle_response(response).await
331 }
332
333 async fn handle_response<R: serde::de::DeserializeOwned>(
334 &self,
335 response: reqwest::Response,
336 ) -> FlashbotsResult<R> {
337 let status = response.status();
338 if !status.is_success() {
339 let error_text = response.text().await.unwrap_or_default();
340 let error_msg = format!("HTTP {}: {}", status, error_text);
341
342 log::error!("API error: {}", error_msg);
343 return Err(FlashbotsError::ApiError(error_msg));
344 }
345 let result: R = response
346 .json()
347 .await
348 .map_err(|e| FlashbotsError::Error(format!("{:?}", e)))?;
349 Ok(result)
350 }
351
352 async fn retry_operation<F, T, Fut>(&self, mut operation: F) -> FlashbotsResult<T>
353 where
354 F: FnMut() -> Fut,
355 Fut: std::future::Future<Output = FlashbotsResult<T>>,
356 {
357 let mut last_error = None;
358 for attempt in 0..self.config.max_retries {
359 match operation().await {
360 Ok(result) => {
361 if attempt > 0 {
362 log::info!("Operation succeeded after {} retries", attempt);
363 }
364 return Ok(result);
365 }
366 Err(e) => {
367 last_error = Some(e);
368 log::warn!(
369 "Operation failed (attempt {}): {:?}",
370 attempt + 1,
371 last_error
372 );
373 if attempt < self.config.max_retries - 1 {
374 log::info!("Retrying in {} seconds...", self.config.retry_delay_seconds);
375 sleep(Duration::from_secs(self.config.retry_delay_seconds)).await;
376 }
377 }
378 }
379 }
380 let final_error = last_error.unwrap();
381 log::error!(
382 "Operation failed after {} retries: {:?}",
383 self.config.max_retries,
384 final_error
385 );
386 Err(final_error)
387 }
388
389 pub fn config(&self) -> &FlashbotsClientConfig {
391 &self.config
392 }
393
394 pub fn with_config(&self, config: FlashbotsClientConfig) -> Self {
396 Self::new(config)
397 }
398
399 pub async fn send_raw_bundle(&self, raw_bundle: Value) -> FlashbotsResult<SendBundleResponse> {
401 let url = format!("{}/", self.config.base_url);
402 self.post(&url, &raw_bundle).await
403 }
404
405 pub async fn cancel_bundles(
415 &self,
416 bundle_hashes: Vec<H256>,
417 ) -> FlashbotsResult<CancelBundlesResponse> {
418 let url = format!("{}/cancelBundles", self.config.base_url);
419 let request = CancelBundlesRequest { bundle_hashes };
420 self.post(&url, &request).await
421 }
422
423 pub async fn get_bundle_by_hash(
425 &self,
426 bundle_hash: H256,
427 ) -> FlashbotsResult<BundleByHashResponse> {
428 let url = format!(
429 "{}/bundleByHash/0x{}",
430 self.config.base_url,
431 hex::encode(bundle_hash.as_bytes())
432 );
433 self.get(&url).await
434 }
435
436 pub async fn get_block(&self, block_number: U64) -> FlashbotsResult<BlockResponse> {
446 let url = format!("{}/block/{}", self.config.base_url, block_number);
447 self.get(&url).await
448 }
449
450 pub async fn get_latest_block(&self) -> FlashbotsResult<BlockResponse> {
452 let url = format!("{}/block/latest", self.config.base_url);
453 self.get(&url).await
454 }
455
456 pub async fn get_user_status(&self, address: Address) -> FlashbotsResult<UserStatus> {
466 let url = format!("{}/userStatus/{}", self.config.base_url, address);
467 self.get(&url).await
468 }
469
470 pub async fn get_gas_price(&self) -> FlashbotsResult<GasPriceResponse> {
479 let url = format!("{}/gasPrice", self.config.base_url);
480 self.get(&url).await
481 }
482
483 pub async fn get_supported_endpoints(&self) -> FlashbotsResult<Vec<String>> {
485 let url = format!("{}/", self.config.base_url);
486 let response: Value = self.get(&url).await?;
487 Ok(response
488 .get("supported_apis")
489 .and_then(|apis| apis.as_array())
490 .map(|apis| {
491 apis.iter()
492 .filter_map(|api| api.as_str().map(|s| s.to_string()))
493 .collect()
494 })
495 .unwrap_or_default())
496 }
497
498 pub async fn get_relay_info(&self) -> FlashbotsResult<RelayInfo> {
509 let url = format!("{}/", self.config.base_url);
510 self.get(&url).await
511 }
512
513 pub async fn get_bundle_receipts(
515 &self,
516 bundle_hashes: Vec<H256>,
517 ) -> FlashbotsResult<Vec<BundleReceipt>> {
518 let mut receipts = Vec::new();
519 for bundle_hash in bundle_hashes {
520 match self.get_bundle_receipt(bundle_hash).await {
521 Ok(receipt) => receipts.push(receipt),
522 Err(e) => {
523 log::warn!(
524 "Failed to get receipt for bundle {:?}: {:?}",
525 bundle_hash,
526 e
527 );
528 }
529 }
530 }
531 Ok(receipts)
532 }
533
534 pub async fn check_bundle_inclusions(
536 &self,
537 bundle_hashes: Vec<H256>,
538 ) -> FlashbotsResult<Vec<(H256, bool)>> {
539 let mut results = Vec::new();
540 for bundle_hash in bundle_hashes {
541 let is_included = self.get_bundle_receipt(bundle_hash).await.is_ok();
542 results.push((bundle_hash, is_included));
543 }
544 Ok(results)
545 }
546
547 pub async fn send_bundle_and_get_receipt(
549 &self,
550 bundle: Bundle,
551 timeout_blocks: u64,
552 ) -> FlashbotsResult<BundleReceipt> {
553 let response = self.send_bundle_with_retry(bundle).await?;
554 log::info!("Bundle sent successfully, hash: {:?}", response.bundle_hash);
555 match self
556 .wait_for_bundle_inclusion(response.bundle_hash, timeout_blocks)
557 .await?
558 {
559 Some(receipt) => Ok(receipt),
560 None => Err(FlashbotsError::Error(
561 "Bundle not included within timeout".to_string(),
562 )),
563 }
564 }
565
566 pub async fn validate_bundle(&self, bundle: &Bundle) -> FlashbotsResult<bool> {
568 if bundle.txs.is_empty() {
569 return Err(FlashbotsError::Error(
570 "Bundle must contain at least one transaction".to_string(),
571 ));
572 }
573 if bundle
575 .block_number
576 .ok_or(|| FlashbotsError::Error(format!("block number is empty")))
577 .is_err()
578 || bundle.block_number.unwrap().is_zero()
579 {
580 return Err(FlashbotsError::Error(
581 "Bundle must have a valid block number".to_string(),
582 ));
583 }
584 if let (Some(min_ts), Some(max_ts)) = (bundle.min_timestamp, bundle.max_timestamp) {
586 if min_ts > max_ts {
587 return Err(FlashbotsError::Error(
588 "Invalid timestamp range: min_timestamp > max_timestamp".to_string(),
589 ));
590 }
591 }
592 Ok(true)
593 }
594
595 pub async fn get_relay_stats(&self) -> FlashbotsResult<Value> {
597 let url = format!("{}/relayStats", self.config.base_url);
598 self.get(&url).await
599 }
600
601 pub async fn get_builder_info(&self) -> FlashbotsResult<Value> {
603 let url = format!("{}/builder", self.config.base_url);
604 self.get(&url).await
605 }
606
607 pub async fn simulate_bundles(
609 &self,
610 simulations: Vec<SimulateBundleRequest>,
611 ) -> FlashbotsResult<Vec<FlashbotsResult<SimulateBundleResponse>>> {
612 let mut results = Vec::new();
613 for simulation in simulations {
614 let result = self.simulate_bundle_with_retry(simulation).await;
615 results.push(result);
616 }
617 Ok(results)
618 }
619
620 pub async fn send_and_cancel_bundle(&self, bundle: Bundle) -> FlashbotsResult<bool> {
622 let response = self.send_bundle_with_retry(bundle).await?;
623 log::info!(
624 "Bundle sent, attempting to cancel: {:?}",
625 response.bundle_hash
626 );
627 let cancel_result = self.cancel_bundles(vec![response.bundle_hash]).await?;
628 Ok(cancel_result.success)
629 }
630
631 pub async fn get_network_info(&self) -> FlashbotsResult<Value> {
633 let url = format!("{}/network", self.config.base_url);
634 self.get(&url).await
635 }
636
637 pub async fn is_blacklisted(&self, address: Address) -> FlashbotsResult<bool> {
639 let user_status = self.get_user_status(address).await?;
640 Ok(user_status.blacklisted)
641 }
642
643 pub async fn get_version(&self) -> FlashbotsResult<String> {
645 let relay_info = self.get_relay_info().await?;
646 Ok(relay_info.version)
647 }
648}
649
650impl FlashbotsClient {
651 pub fn new_production() -> Self {
653 Self::new(FlashbotsClientConfig {
654 base_url: MAIN_URL.to_string(),
655 timeout_seconds: 60,
656 max_retries: 5,
657 retry_delay_seconds: 3,
658 })
659 }
660
661 pub fn new_development() -> Self {
663 Self::new(FlashbotsClientConfig {
664 base_url: SEPOLIA_TEST_URL.to_string(),
665 timeout_seconds: 15,
666 max_retries: 1,
667 retry_delay_seconds: 1,
668 })
669 }
670}