jito_client/
lib.rs

1use anyhow::{Context, anyhow};
2use base64::prelude::*;
3use load_balancer::{LoadBalancer, interval::IntervalLoadBalancer};
4use reqwest::{Client, ClientBuilder, Response};
5use serde::{Deserialize, Serialize};
6use serde_json::json;
7use std::{net::IpAddr, sync::Arc, time::Duration};
8
9pub use load_balancer;
10pub use load_balancer::get_if_addrs;
11pub use load_balancer::ip::{get_ip_list, get_ipv4_list, get_ipv6_list};
12pub use reqwest;
13pub use reqwest::Proxy;
14pub use reqwest::header::HeaderMap;
15pub use serde_json;
16
17/// Builder for configuring and creating a `JitoClient`.
18pub struct JitoClientBuilder {
19    url: Vec<String>,
20    broadcast: bool,
21    interval: Duration,
22    timeout: Option<Duration>,
23    proxy: Option<Proxy>,
24    headers: Option<HeaderMap>,
25    ip: Vec<IpAddr>,
26}
27
28impl JitoClientBuilder {
29    /// Creates a new `JitoClientBuilder` with default settings.
30    pub fn new() -> Self {
31        Self {
32            url: vec!["https://mainnet.block-engine.jito.wtf".to_string()],
33            broadcast: false,
34            interval: Duration::ZERO,
35            timeout: None,
36            proxy: None,
37            headers: None,
38            ip: Vec::new(),
39        }
40    }
41
42    /// Sets the target URLs for the client.
43    pub fn url<T: IntoIterator<Item = impl AsRef<str>>>(mut self, url: T) -> Self {
44        self.url = url.into_iter().map(|v| v.as_ref().to_string()).collect();
45        self
46    }
47
48    /// Sets the interval duration between requests (0 = unlimited)
49    /// For example, 5 requests per second = 200 ms interval.
50    pub fn interval(mut self, interval: Duration) -> Self {
51        self.interval = interval;
52        self
53    }
54
55    /// Sets the local IP addresses to bind outgoing requests to.
56    pub fn ip(mut self, ip: Vec<IpAddr>) -> Self {
57        self.ip = ip;
58        self
59    }
60
61    /// Broadcast each request to all configured URLs.
62    pub fn broadcast(mut self, broadcast: bool) -> Self {
63        self.broadcast = broadcast;
64        self
65    }
66
67    /// Sets a timeout duration for requests.
68    pub fn timeout(mut self, timeout: Duration) -> Self {
69        self.timeout = Some(timeout);
70        self
71    }
72
73    /// Sets a proxy for the client.
74    pub fn proxy(mut self, proxy: Proxy) -> Self {
75        self.proxy = Some(proxy);
76        self
77    }
78
79    /// Sets headers for the client.
80    pub fn headers(mut self, headers: HeaderMap) -> Self {
81        self.headers = Some(headers);
82        self
83    }
84
85    /// Builds the `JitoClient` with the configured options.
86    pub fn build(self) -> anyhow::Result<JitoClient> {
87        let default_ip = self.ip.is_empty();
88
89        let inner = if self.broadcast {
90            let mut entries = Vec::new();
91
92            if default_ip {
93                let mut cb = ClientBuilder::new();
94
95                if let Some(v) = self.timeout {
96                    cb = cb.timeout(v);
97                }
98
99                if let Some(v) = self.proxy {
100                    cb = cb.proxy(v);
101                }
102
103                if let Some(v) = self.headers {
104                    cb = cb.default_headers(v);
105                }
106
107                entries.push((self.interval, Arc::new((self.url.clone(), cb.build()?))));
108            } else {
109                for ip in &self.ip {
110                    let mut cb = ClientBuilder::new();
111
112                    if let Some(v) = self.timeout {
113                        cb = cb.timeout(v);
114                    }
115
116                    if let Some(v) = self.proxy.clone() {
117                        cb = cb.proxy(v);
118                    }
119
120                    if let Some(v) = self.headers.clone() {
121                        cb = cb.default_headers(v);
122                    }
123
124                    cb = cb.local_address(*ip);
125
126                    entries.push((self.interval, Arc::new((self.url.clone(), cb.build()?))));
127                }
128            }
129
130            JitoClientRef {
131                broadcast: true,
132                lb: IntervalLoadBalancer::new(entries),
133            }
134        } else {
135            let mut entries = Vec::new();
136
137            if default_ip {
138                for url in &self.url {
139                    let mut cb = ClientBuilder::new();
140
141                    if let Some(v) = self.timeout {
142                        cb = cb.timeout(v);
143                    }
144
145                    if let Some(v) = self.proxy.clone() {
146                        cb = cb.proxy(v);
147                    }
148
149                    if let Some(v) = self.headers.clone() {
150                        cb = cb.default_headers(v);
151                    }
152
153                    entries.push((self.interval, Arc::new((vec![url.clone()], cb.build()?))));
154                }
155            } else {
156                for url in &self.url {
157                    for ip in &self.ip {
158                        let mut cb = ClientBuilder::new();
159
160                        if let Some(v) = self.timeout {
161                            cb = cb.timeout(v);
162                        }
163
164                        if let Some(v) = self.proxy.clone() {
165                            cb = cb.proxy(v);
166                        }
167
168                        if let Some(v) = self.headers.clone() {
169                            cb = cb.default_headers(v);
170                        }
171
172                        cb = cb.local_address(*ip);
173
174                        entries.push((self.interval, Arc::new((vec![url.clone()], cb.build()?))));
175                    }
176                }
177            }
178
179            JitoClientRef {
180                broadcast: false,
181                lb: IntervalLoadBalancer::new(entries),
182            }
183        };
184
185        Ok(JitoClient {
186            inner: inner.into(),
187        })
188    }
189}
190
191struct JitoClientRef {
192    broadcast: bool,
193    lb: IntervalLoadBalancer<Arc<(Vec<String>, Client)>>,
194}
195
196/// Jito client for sending transactions and bundles.
197#[derive(Clone)]
198pub struct JitoClient {
199    inner: Arc<JitoClientRef>,
200}
201
202impl JitoClient {
203    /// Creates a new client with default settings.
204    pub fn new() -> Self {
205        JitoClientBuilder::new().build().unwrap()
206    }
207
208    /// Sends a raw request.
209    pub async fn raw_send(&mut self, body: &serde_json::Value) -> anyhow::Result<Response> {
210        let (ref url, ref client) = *self.inner.lb.alloc().await;
211
212        if self.inner.broadcast {
213            Ok(
214                futures::future::select_ok(url.iter().map(|v| client.post(v).json(body).send()))
215                    .await?
216                    .0,
217            )
218        } else {
219            Ok(client.post(&url[0]).json(body).send().await?)
220        }
221    }
222
223    /// Sends a raw request, use base_url + api_url.
224    pub async fn raw_send_api(
225        &mut self,
226        api_url: impl AsRef<str>,
227        body: &serde_json::Value,
228    ) -> anyhow::Result<Response> {
229        let (ref url, ref client) = *self.inner.lb.alloc().await;
230
231        if self.inner.broadcast {
232            Ok(futures::future::select_ok(url.iter().map(|v| {
233                client
234                    .post(&format!("{}{}", v, api_url.as_ref()))
235                    .json(body)
236                    .send()
237            }))
238            .await?
239            .0)
240        } else {
241            Ok(client
242                .post(&format!("{}{}", url[0], api_url.as_ref()))
243                .json(body)
244                .send()
245                .await?)
246        }
247    }
248
249    /// Sends a single transaction and returns the HTTP response.
250    pub async fn send_transaction(&self, tx: impl Serialize) -> anyhow::Result<Response> {
251        let data = BASE64_STANDARD.encode(bincode::serialize(&tx)?);
252        let body = json!({
253            "id": 1,
254            "jsonrpc": "2.0",
255            "method": "sendTransaction",
256            "params": [
257                data, { "encoding": "base64" }
258            ]
259        });
260
261        let (ref url, ref client) = *self.inner.lb.alloc().await;
262
263        if self.inner.broadcast {
264            Ok(futures::future::select_ok(url.iter().map(|v| {
265                client
266                    .post(&format!("{}/api/v1/transactions", v))
267                    .query(&["bundleOnly", "true"])
268                    .json(&body)
269                    .send()
270            }))
271            .await?
272            .0)
273        } else {
274            Ok(client
275                .post(&format!("{}/api/v1/transactions", url[0]))
276                .query(&["bundleOnly", "true"])
277                .json(&body)
278                .send()
279                .await?)
280        }
281    }
282
283    /// Sends a transaction and returns the bundle ID from the response headers.
284    pub async fn send_transaction_bid(&self, tx: impl Serialize) -> anyhow::Result<String> {
285        Ok(self
286            .send_transaction(tx)
287            .await?
288            .error_for_status()?
289            .headers()
290            .get("x-bundle-id")
291            .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
292            .to_str()
293            .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
294            .to_string())
295    }
296
297    /// Sends a transaction without `bundleOnly` flag.
298    pub async fn send_transaction_no_bundle_only(
299        &self,
300        tx: impl Serialize,
301    ) -> anyhow::Result<Response> {
302        let data = BASE64_STANDARD.encode(bincode::serialize(&tx)?);
303        let body = json!({
304            "id": 1,
305            "jsonrpc": "2.0",
306            "method": "sendTransaction",
307            "params": [
308                data, { "encoding": "base64" }
309            ]
310        });
311
312        let (ref url, ref client) = *self.inner.lb.alloc().await;
313
314        if self.inner.broadcast {
315            Ok(futures::future::select_ok(url.iter().map(|v| {
316                client
317                    .post(&format!("{}/api/v1/transactions", v))
318                    .json(&body)
319                    .send()
320            }))
321            .await?
322            .0)
323        } else {
324            Ok(client
325                .post(&format!("{}/api/v1/transactions", url[0]))
326                .json(&body)
327                .send()
328                .await?)
329        }
330    }
331
332    /// Sends multiple transactions as a bundle.
333    pub async fn send_bundle<T: IntoIterator<Item = impl Serialize>>(
334        &self,
335        tx: T,
336    ) -> anyhow::Result<Response> {
337        let data = tx
338            .into_iter()
339            .map(|tx| {
340                Ok(BASE64_STANDARD.encode(
341                    bincode::serialize(&tx)
342                        .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
343                ))
344            })
345            .collect::<anyhow::Result<Vec<_>>>()?;
346
347        let body = json!({
348            "id": 1,
349            "jsonrpc": "2.0",
350            "method": "sendBundle",
351            "params": [ data, { "encoding": "base64" } ]
352        });
353
354        let (ref url, ref client) = *self.inner.lb.alloc().await;
355
356        if self.inner.broadcast {
357            Ok(futures::future::select_ok(url.iter().map(|v| {
358                client
359                    .post(&format!("{}/api/v1/bundles", v))
360                    .json(&body)
361                    .send()
362            }))
363            .await?
364            .0)
365        } else {
366            Ok(client
367                .post(&format!("{}/api/v1/bundles", url[0]))
368                .json(&body)
369                .send()
370                .await?)
371        }
372    }
373
374    /// Sends a bundle and returns its bundle ID from the JSON response.
375    pub async fn send_bundle_bid<T: IntoIterator<Item = impl Serialize>>(
376        &self,
377        tx: T,
378    ) -> anyhow::Result<String> {
379        self.send_bundle(tx)
380            .await?
381            .error_for_status()?
382            .json::<serde_json::Value>()
383            .await?["result"]
384            .as_str()
385            .map(|v| v.to_string())
386            .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
387    }
388
389    /// Sends a single transaction and returns the HTTP response, with lazy serialization.
390    pub async fn send_transaction_lazy<T>(
391        &self,
392        tx: impl Future<Output = anyhow::Result<T>>,
393    ) -> anyhow::Result<Response>
394    where
395        T: Serialize,
396    {
397        let (ref url, ref client) = *self.inner.lb.alloc().await;
398
399        let data = BASE64_STANDARD.encode(bincode::serialize(&tx.await?)?);
400
401        let body = json!({
402            "id": 1,
403            "jsonrpc": "2.0",
404            "method": "sendTransaction",
405            "params": [
406                data, { "encoding": "base64" }
407            ]
408        });
409
410        if self.inner.broadcast {
411            Ok(futures::future::select_ok(url.iter().map(|v| {
412                client
413                    .post(&format!("{}/api/v1/transactions", v))
414                    .query(&["bundleOnly", "true"])
415                    .json(&body)
416                    .send()
417            }))
418            .await?
419            .0)
420        } else {
421            Ok(client
422                .post(&format!("{}/api/v1/transactions", url[0]))
423                .query(&["bundleOnly", "true"])
424                .json(&body)
425                .send()
426                .await?)
427        }
428    }
429
430    /// Sends a transaction and returns the bundle ID from the response headers, with lazy serialization.
431    pub async fn send_transaction_bid_lazy<T>(
432        &self,
433        tx: impl Future<Output = anyhow::Result<T>>,
434    ) -> anyhow::Result<String>
435    where
436        T: Serialize,
437    {
438        Ok(self
439            .send_transaction_lazy(tx)
440            .await?
441            .error_for_status()?
442            .headers()
443            .get("x-bundle-id")
444            .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
445            .to_str()
446            .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
447            .to_string())
448    }
449
450    /// Sends a transaction without `bundleOnly` flag, with lazy serialization.
451    pub async fn send_transaction_no_bundle_only_lazy<T>(
452        &self,
453        tx: impl Future<Output = anyhow::Result<T>>,
454    ) -> anyhow::Result<Response>
455    where
456        T: Serialize,
457    {
458        let (ref url, ref client) = *self.inner.lb.alloc().await;
459
460        let data = BASE64_STANDARD.encode(bincode::serialize(&tx.await?)?);
461
462        let body = json!({
463            "id": 1,
464            "jsonrpc": "2.0",
465            "method": "sendTransaction",
466            "params": [
467                data, { "encoding": "base64" }
468            ]
469        });
470
471        if self.inner.broadcast {
472            Ok(futures::future::select_ok(url.iter().map(|v| {
473                client
474                    .post(&format!("{}/api/v1/transactions", v))
475                    .json(&body)
476                    .send()
477            }))
478            .await?
479            .0)
480        } else {
481            Ok(client
482                .post(&format!("{}/api/v1/transactions", url[0]))
483                .json(&body)
484                .send()
485                .await?)
486        }
487    }
488
489    /// Sends multiple transactions as a bundle, with lazy serialization.
490    pub async fn send_bundle_lazy<T, S>(
491        &self,
492        tx: impl Future<Output = anyhow::Result<T>>,
493    ) -> anyhow::Result<Response>
494    where
495        T: IntoIterator<Item = S>,
496        S: Serialize,
497    {
498        let (ref url, ref client) = *self.inner.lb.alloc().await;
499
500        let data = tx
501            .await?
502            .into_iter()
503            .map(|tx| {
504                Ok(BASE64_STANDARD.encode(
505                    bincode::serialize(&tx)
506                        .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
507                ))
508            })
509            .collect::<anyhow::Result<Vec<_>>>()?;
510
511        let body = json!({
512            "id": 1,
513            "jsonrpc": "2.0",
514            "method": "sendBundle",
515            "params": [ data, { "encoding": "base64" } ]
516        });
517
518        if self.inner.broadcast {
519            Ok(futures::future::select_ok(url.iter().map(|v| {
520                client
521                    .post(&format!("{}/api/v1/bundles", v))
522                    .json(&body)
523                    .send()
524            }))
525            .await?
526            .0)
527        } else {
528            Ok(client
529                .post(&format!("{}/api/v1/bundles", url[0]))
530                .json(&body)
531                .send()
532                .await?)
533        }
534    }
535
536    /// Sends a bundle and returns its bundle ID from the JSON response, with lazy serialization.
537    pub async fn send_bundle_bid_lazy<T, S>(
538        &self,
539        tx: impl Future<Output = anyhow::Result<T>>,
540    ) -> anyhow::Result<String>
541    where
542        T: IntoIterator<Item = S>,
543        S: Serialize,
544    {
545        self.send_bundle_lazy(tx)
546            .await?
547            .error_for_status()?
548            .json::<serde_json::Value>()
549            .await?["result"]
550            .as_str()
551            .map(|v| v.to_string())
552            .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
553    }
554
555    /// Sends a single transaction and returns the HTTP response, with lazy serialization.
556    #[cfg(rustc_version_1_85_0)]
557    pub async fn send_transaction_lazy_fn<F, T>(&self, callback: F) -> anyhow::Result<Response>
558    where
559        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
560        T: Serialize,
561    {
562        let (ref url, ref client) = *self.inner.lb.alloc().await;
563
564        let data = BASE64_STANDARD.encode(bincode::serialize(&callback(url, client).await?)?);
565
566        let body = json!({
567            "id": 1,
568            "jsonrpc": "2.0",
569            "method": "sendTransaction",
570            "params": [
571                data, { "encoding": "base64" }
572            ]
573        });
574
575        if self.inner.broadcast {
576            Ok(futures::future::select_ok(url.iter().map(|v| {
577                client
578                    .post(&format!("{}/api/v1/transactions", v))
579                    .query(&["bundleOnly", "true"])
580                    .json(&body)
581                    .send()
582            }))
583            .await?
584            .0)
585        } else {
586            Ok(client
587                .post(&format!("{}/api/v1/transactions", url[0]))
588                .query(&["bundleOnly", "true"])
589                .json(&body)
590                .send()
591                .await?)
592        }
593    }
594
595    /// Sends a transaction and returns the bundle ID from the response headers, with lazy serialization.
596    #[cfg(rustc_version_1_85_0)]
597    pub async fn send_transaction_bid_lazy_fn<F, T>(&self, callback: F) -> anyhow::Result<String>
598    where
599        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
600        T: Serialize,
601    {
602        Ok(self
603            .send_transaction_lazy_fn(callback)
604            .await?
605            .error_for_status()?
606            .headers()
607            .get("x-bundle-id")
608            .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
609            .to_str()
610            .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
611            .to_string())
612    }
613
614    /// Sends a transaction without `bundleOnly` flag, with lazy serialization.
615    #[cfg(rustc_version_1_85_0)]
616    pub async fn send_transaction_no_bundle_only_lazy_fn<F, T>(
617        &self,
618        callback: F,
619    ) -> anyhow::Result<Response>
620    where
621        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
622        T: Serialize,
623    {
624        let (ref url, ref client) = *self.inner.lb.alloc().await;
625
626        let data = BASE64_STANDARD.encode(bincode::serialize(&callback(url, client).await?)?);
627
628        let body = json!({
629            "id": 1,
630            "jsonrpc": "2.0",
631            "method": "sendTransaction",
632            "params": [
633                data, { "encoding": "base64" }
634            ]
635        });
636
637        if self.inner.broadcast {
638            Ok(futures::future::select_ok(url.iter().map(|v| {
639                client
640                    .post(&format!("{}/api/v1/transactions", v))
641                    .json(&body)
642                    .send()
643            }))
644            .await?
645            .0)
646        } else {
647            Ok(client
648                .post(&format!("{}/api/v1/transactions", url[0]))
649                .json(&body)
650                .send()
651                .await?)
652        }
653    }
654
655    /// Sends multiple transactions as a bundle, with lazy serialization.
656    #[cfg(rustc_version_1_85_0)]
657    pub async fn send_bundle_lazy_fn<F, T, S>(&self, callback: F) -> anyhow::Result<Response>
658    where
659        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
660        T: IntoIterator<Item = S>,
661        S: Serialize,
662    {
663        let (ref url, ref client) = *self.inner.lb.alloc().await;
664
665        let data = callback(url, client)
666            .await?
667            .into_iter()
668            .map(|tx| {
669                Ok(BASE64_STANDARD.encode(
670                    bincode::serialize(&tx)
671                        .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
672                ))
673            })
674            .collect::<anyhow::Result<Vec<_>>>()?;
675
676        let body = json!({
677            "id": 1,
678            "jsonrpc": "2.0",
679            "method": "sendBundle",
680            "params": [ data, { "encoding": "base64" } ]
681        });
682
683        if self.inner.broadcast {
684            Ok(futures::future::select_ok(url.iter().map(|v| {
685                client
686                    .post(&format!("{}/api/v1/bundles", v))
687                    .json(&body)
688                    .send()
689            }))
690            .await?
691            .0)
692        } else {
693            Ok(client
694                .post(&format!("{}/api/v1/bundles", url[0]))
695                .json(&body)
696                .send()
697                .await?)
698        }
699    }
700
701    /// Sends a bundle and returns its bundle ID from the JSON response, with lazy serialization.
702    #[cfg(rustc_version_1_85_0)]
703    pub async fn send_bundle_bid_lazy_fn<F, T, S>(&self, callback: F) -> anyhow::Result<String>
704    where
705        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
706        T: IntoIterator<Item = S>,
707        S: Serialize,
708    {
709        self.send_bundle_lazy_fn(callback)
710            .await?
711            .error_for_status()?
712            .json::<serde_json::Value>()
713            .await?["result"]
714            .as_str()
715            .map(|v| v.to_string())
716            .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
717    }
718}
719
720/// Represents Jito tip data.
721#[derive(Debug, Clone, Deserialize)]
722pub struct JitoTip {
723    pub landed_tips_25th_percentile: f64,
724    pub landed_tips_50th_percentile: f64,
725    pub landed_tips_75th_percentile: f64,
726    pub landed_tips_95th_percentile: f64,
727    pub landed_tips_99th_percentile: f64,
728    pub ema_landed_tips_50th_percentile: f64,
729}
730
731/// Fetches the current Jito tip from the public API.
732pub async fn get_jito_tip(client: Client) -> anyhow::Result<JitoTip> {
733    Ok(client
734        .get("https://bundles.jito.wtf/api/v1/bundles/tip_floor")
735        .send()
736        .await?
737        .json::<Vec<JitoTip>>()
738        .await?
739        .get(0)
740        .context("get_jito_tip: empty response")?
741        .clone())
742}
743
744/// Represents the result of querying bundle statuses.
745#[derive(Debug, Deserialize)]
746pub struct BundleResult {
747    pub context: serde_json::Value,
748    pub value: Option<Vec<BundleStatus>>,
749}
750
751#[derive(Debug, Deserialize)]
752pub struct BundleStatus {
753    pub bundle_id: String,
754    pub transactions: Option<Vec<String>>,
755    pub slot: Option<u64>,
756    pub confirmation_status: Option<String>,
757    pub err: Option<serde_json::Value>,
758}
759
760/// Fetches statuses of multiple bundles.
761pub async fn get_bundle_statuses<T: IntoIterator<Item = impl AsRef<str>>>(
762    client: Client,
763    bundle: T,
764) -> anyhow::Result<BundleResult> {
765    #[derive(Debug, Deserialize)]
766    struct RpcResponse {
767        result: BundleResult,
768    }
769
770    let payload = json!({
771        "jsonrpc": "2.0",
772        "id": 1,
773        "method": "getBundleStatuses",
774        "params": [bundle.into_iter().map(|v| v.as_ref().to_string()).collect::<Vec<_>>()],
775    });
776
777    Ok(client
778        .post("https://mainnet.block-engine.jito.wtf/api/v1/getBundleStatuses")
779        .json(&payload)
780        .send()
781        .await?
782        .json::<RpcResponse>()
783        .await?
784        .result)
785}
786
787/// Represents in-flight bundle status.
788#[derive(Debug, Deserialize)]
789pub struct InflightBundleStatus {
790    pub bundle_id: String,
791    pub status: String,
792    pub landed_slot: Option<u64>,
793}
794
795#[derive(Debug, Deserialize)]
796pub struct InflightBundleResult {
797    pub context: serde_json::Value,
798    pub value: Option<Vec<InflightBundleStatus>>,
799}
800
801/// Fetches statuses of in-flight bundles.
802pub async fn get_inflight_bundle_statuses<T: IntoIterator<Item = impl AsRef<str>>>(
803    client: Client,
804    bundle: T,
805) -> anyhow::Result<InflightBundleResult> {
806    #[derive(Debug, Deserialize)]
807    struct InflightRpcResponse {
808        result: InflightBundleResult,
809    }
810
811    let payload = json!({
812        "jsonrpc": "2.0",
813        "id": 1,
814        "method": "getInflightBundleStatuses",
815        "params": [bundle.into_iter().map(|v| v.as_ref().to_string()).collect::<Vec<_>>()],
816    });
817
818    Ok(client
819        .post("https://mainnet.block-engine.jito.wtf/api/v1/getInflightBundleStatuses")
820        .json(&payload)
821        .send()
822        .await?
823        .json::<InflightRpcResponse>()
824        .await?
825        .result)
826}
827
828pub async fn test_ip(ip: IpAddr) -> anyhow::Result<IpAddr> {
829    reqwest::ClientBuilder::new()
830        .timeout(Duration::from_secs(3))
831        .local_address(ip)
832        .build()?
833        .get("https://crates.io")
834        .send()
835        .await?;
836
837    Ok(ip)
838}
839
840pub async fn test_all_ip() -> Vec<anyhow::Result<IpAddr>> {
841    match get_ip_list() {
842        Ok(v) => futures::future::join_all(v.into_iter().map(|v| test_ip(v))).await,
843        Err(_) => Vec::new(),
844    }
845}
846
847pub async fn test_all_ipv4() -> Vec<anyhow::Result<IpAddr>> {
848    match get_ipv4_list() {
849        Ok(v) => futures::future::join_all(v.into_iter().map(|v| test_ip(v))).await,
850        Err(_) => Vec::new(),
851    }
852}
853
854pub async fn test_all_ipv6() -> Vec<anyhow::Result<IpAddr>> {
855    match get_ipv6_list() {
856        Ok(v) => futures::future::join_all(v.into_iter().map(|v| test_ip(v))).await,
857        Err(_) => Vec::new(),
858    }
859}
860
861pub fn serialize_tx(tx: impl Serialize) -> anyhow::Result<String> {
862    Ok(BASE64_STANDARD.encode(bincode::serialize(&tx)?))
863}
864
865pub fn serialize_tx_vec<T: IntoIterator<Item = impl Serialize>>(
866    tx: T,
867) -> anyhow::Result<Vec<String>> {
868    tx.into_iter()
869        .map(|tx| {
870            Ok(BASE64_STANDARD.encode(
871                bincode::serialize(&tx)
872                    .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
873            ))
874        })
875        .collect::<anyhow::Result<Vec<_>>>()
876}