Skip to main content

flashbots_sdk/
lib.rs

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/// Configuration for Flashbots client
22#[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/// Main client for interacting with Flashbots relay
42#[derive(Debug, Clone)]
43pub struct FlashbotsClient {
44    client: Client,
45    config: FlashbotsClientConfig,
46}
47
48impl FlashbotsClient {
49    /// Creates a new Flashbots client with custom configuration
50    pub fn new(config: FlashbotsClientConfig) -> Self {
51        Self {
52            client: Client::new(),
53            config,
54        }
55    }
56
57    /// Creates a new client configured for Mainnet
58    pub fn new_mainnet() -> Self {
59        Self::new(FlashbotsClientConfig {
60            base_url: MAIN_URL.to_string(),
61            ..Default::default()
62        })
63    }
64
65    /// Creates a new client configured for Sepolia testnet
66    pub fn new_sepolia() -> Self {
67        Self::new(FlashbotsClientConfig {
68            base_url: SEPOLIA_TEST_URL.to_string(),
69            ..Default::default()
70        })
71    }
72
73    /// Sets maximum retry attempts for operations
74    pub fn with_max_retries(mut self, max_retries: u32) -> Self {
75        self.config.max_retries = max_retries;
76        self
77    }
78
79    /// Sets request timeout in seconds
80    pub fn with_timeout(mut self, timeout_seconds: u64) -> Self {
81        self.config.timeout_seconds = timeout_seconds;
82        self
83    }
84
85    /// Sends a bundle to Flashbots relay
86    ///
87    /// # Example
88    /// ```
89    /// use flashbots_rs::{FlashbotsClient, Bundle};
90    ///
91    /// let client = FlashbotsClient::new_mainnet();
92    /// let bundle = Bundle::default(); // Your bundle construction
93    /// let response = client.send_bundle(bundle).await?;
94    /// println!("Bundle sent with hash: {:?}", response.bundle_hash);
95    /// ```
96    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    /// Sends a bundle with automatic retry logic
102    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    /// Simulates bundle execution without submitting to the network
111    ///
112    /// # Example
113    /// ```
114    /// use flashbots_rs::{FlashbotsClient, SimulateBundleRequest};
115    ///
116    /// let client = FlashbotsClient::new_mainnet();
117    /// let simulation = SimulateBundleRequest::default(); // Your simulation parameters
118    /// let result = client.simulate_bundle(simulation).await?;
119    /// println!("Simulation success: {}, Gas used: {}", result.success, result.gas_used);
120    /// ```
121    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    /// Simulates bundle with automatic retry logic
130    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    /// Gets bundle statistics for a specific block
139    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    /// Gets bundle receipt by hash
154    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    /// Gets user statistics for a specific address and block
164    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    /// Checks relay health status
177    ///
178    /// # Example
179    /// ```
180    /// use flashbots_rs::FlashbotsClient;
181    ///
182    /// let client = FlashbotsClient::new_mainnet();
183    /// let is_healthy = client.get_health().await?;
184    /// println!("Relay is healthy: {}", is_healthy);
185    /// ```
186    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    /// Gets bundle price for a specific block
197    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    /// Waits for bundle to be included in a block
206    ///
207    /// # Example
208    /// ```
209    /// use flashbots_rs::FlashbotsClient;
210    /// use ethers::types::H256;
211    ///
212    /// let client = FlashbotsClient::new_mainnet();
213    /// let bundle_hash = H256::zero(); // Your actual bundle hash
214    /// let receipt = client.wait_for_bundle_inclusion(bundle_hash, 10).await?;
215    /// match receipt {
216    ///     Some(receipt) => println!("Bundle included: {:?}", receipt),
217    ///     None => println!("Bundle not included within timeout"),
218    /// }
219    /// ```
220    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    /// Sends bundle and waits for inclusion
249    ///
250    /// # Example
251    /// ```
252    /// use flashbots_rs::{FlashbotsClient, Bundle};
253    ///
254    /// let client = FlashbotsClient::new_mainnet();
255    /// let bundle = Bundle::default(); // Your bundle construction
256    /// let receipt = client.send_and_wait_for_bundle(bundle, 10).await?;
257    /// match receipt {
258    ///     Some(receipt) => println!("Bundle successfully included: {:?}", receipt),
259    ///     None => println!("Bundle not included within timeout"),
260    /// }
261    /// ```
262    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    /// Simulates bundle and sends it if simulation is successful
274    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    /// Sends multiple bundles in sequence
294    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    /// Returns current client configuration
390    pub fn config(&self) -> &FlashbotsClientConfig {
391        &self.config
392    }
393
394    /// Creates a new client instance with different configuration
395    pub fn with_config(&self, config: FlashbotsClientConfig) -> Self {
396        Self::new(config)
397    }
398
399    /// Send the raw transaction packet (low-level API)
400    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    /// Cancel submitted transaction package
406    ///
407    /// # Example
408    /// ```
409    /// let client = FlashbotsClient::new_mainnet();
410    /// let bundle_hashes = vec![H256::zero()]; // Transaction package hash
411    /// let result = client.cancel_bundles(bundle_hashes).await?;
412    /// println!("Cancel result: {}", result.success);
413    /// ```
414    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    /// Retrieve transaction package details and receipts using transaction package hash.
424    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    /// Get detailed information of a specified block
437    ///
438    /// # Example
439    /// ```
440    /// let client = FlashbotsClient::new_mainnet();
441    /// let block_number = U64::from(17000000u64);
442    /// let block_info = client.get_block(block_number).await?;
443    /// println!("Block miner: {:?}", block_info.miner);
444    /// ```
445    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    /// Get the latest block information
451    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    /// Obtain user status information (without relying on specific blocks)
457    ///
458    /// # Example
459    /// ```
460    /// let client = FlashbotsClient::new_mainnet();
461    /// let address = Address::zero(); // real address
462    /// let user_status = client.get_user_status(address).await?;
463    /// println!("User reputation: {:?}", user_status.reputation);
464    /// ```
465    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    /// Get recommended gas prices
471    ///
472    /// # Example
473    /// ```
474    /// let client = FlashbotsClient::new_mainnet();
475    /// let gas_prices = client.get_gas_price().await?;
476    /// println!("Fast gas price: {}", gas_prices.fast_gas_price);
477    /// ```
478    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    /// Get the list of API endpoints supported by the repeater.
484    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    /// Get repeater information
499    ///
500    /// # Example
501    /// ```
502    /// use flashbots_rs::FlashbotsClient;
503    ///
504    /// let client = FlashbotsClient::new_mainnet();
505    /// let relay_info = client.get_relay_info().await?;
506    /// println!("Relay: {} v{}", relay_info.name, relay_info.version);
507    /// ```
508    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    /// Batch Transaction Package Receipts
514    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    /// Check if multiple transaction packages have been included.
535    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    /// Send transaction package and get receipt (simplified version)
548    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    /// Verify transaction package format
567    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        // Check block number
574        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        // Check the timestamp range (if any).
585        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    /// Obtain repeater performance statistics
596    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    /// Get Builder Information
602    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    /// Batch simulated trading package
608    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    /// Send the transaction package and cancel it immediately (for testing purposes).
621    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    /// Get network information
632    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    /// Check if the address is blacklisted.
638    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    /// Get repeater version
644    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    /// Creates a production-ready client (Mainnet, high retry count)
652    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    /// Creates a development client (testnet, fast failure)
662    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}