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(&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        &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 raw request, with lazy body construction.
250    pub async fn raw_send_lazy(
251        &self,
252        body: impl Future<Output = anyhow::Result<serde_json::Value>>,
253    ) -> anyhow::Result<Response> {
254        let (ref url, ref client) = *self.inner.lb.alloc().await;
255        let body = body.await?;
256
257        if self.inner.broadcast {
258            Ok(
259                futures::future::select_ok(url.iter().map(|v| client.post(v).json(&body).send()))
260                    .await?
261                    .0,
262            )
263        } else {
264            Ok(client.post(&url[0]).json(&body).send().await?)
265        }
266    }
267
268    /// Sends a raw request, use base_url + api_url, with lazy body construction.
269    pub async fn raw_send_api_lazy(
270        &self,
271        api_url: impl AsRef<str>,
272        body: impl Future<Output = anyhow::Result<serde_json::Value>>,
273    ) -> anyhow::Result<Response> {
274        let (ref url, ref client) = *self.inner.lb.alloc().await;
275        let body = body.await?;
276
277        if self.inner.broadcast {
278            Ok(futures::future::select_ok(url.iter().map(|v| {
279                client
280                    .post(&format!("{}{}", v, api_url.as_ref()))
281                    .json(&body)
282                    .send()
283            }))
284            .await?
285            .0)
286        } else {
287            Ok(client
288                .post(&format!("{}{}", url[0], api_url.as_ref()))
289                .json(&body)
290                .send()
291                .await?)
292        }
293    }
294
295    /// Sends a raw request, with lazy function to build the body.
296    #[cfg(rustc_version_1_85_0)]
297    pub async fn raw_send_lazy_fn<F>(&self, callback: F) -> anyhow::Result<Response>
298    where
299        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<serde_json::Value>,
300    {
301        let (ref url, ref client) = *self.inner.lb.alloc().await;
302        let body = callback(url, client).await?;
303
304        if self.inner.broadcast {
305            Ok(
306                futures::future::select_ok(url.iter().map(|v| client.post(v).json(&body).send()))
307                    .await?
308                    .0,
309            )
310        } else {
311            Ok(client.post(&url[0]).json(&body).send().await?)
312        }
313    }
314
315    /// Sends a raw request, use base_url + api_url, with lazy function to build the body.
316    #[cfg(rustc_version_1_85_0)]
317    pub async fn raw_send_api_lazy_fn<F>(
318        &self,
319        api_url: impl AsRef<str>,
320        callback: F,
321    ) -> anyhow::Result<Response>
322    where
323        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<serde_json::Value>,
324    {
325        let (ref url, ref client) = *self.inner.lb.alloc().await;
326        let body = callback(url, client).await?;
327
328        if self.inner.broadcast {
329            Ok(futures::future::select_ok(url.iter().map(|v| {
330                client
331                    .post(&format!("{}{}", v, api_url.as_ref()))
332                    .json(&body)
333                    .send()
334            }))
335            .await?
336            .0)
337        } else {
338            Ok(client
339                .post(&format!("{}{}", url[0], api_url.as_ref()))
340                .json(&body)
341                .send()
342                .await?)
343        }
344    }
345
346    /// Sends a single transaction and returns the HTTP response.
347    pub async fn send_transaction(&self, tx: impl Serialize) -> anyhow::Result<Response> {
348        let data = BASE64_STANDARD.encode(bincode::serialize(&tx)?);
349        let body = json!({
350            "id": 1,
351            "jsonrpc": "2.0",
352            "method": "sendTransaction",
353            "params": [
354                data, { "encoding": "base64" }
355            ]
356        });
357
358        let (ref url, ref client) = *self.inner.lb.alloc().await;
359
360        if self.inner.broadcast {
361            Ok(futures::future::select_ok(url.iter().map(|v| {
362                client
363                    .post(&format!("{}/api/v1/transactions", v))
364                    .query(&["bundleOnly", "true"])
365                    .json(&body)
366                    .send()
367            }))
368            .await?
369            .0)
370        } else {
371            Ok(client
372                .post(&format!("{}/api/v1/transactions", url[0]))
373                .query(&["bundleOnly", "true"])
374                .json(&body)
375                .send()
376                .await?)
377        }
378    }
379
380    /// Sends a transaction and returns the bundle ID from the response headers.
381    pub async fn send_transaction_bid(&self, tx: impl Serialize) -> anyhow::Result<String> {
382        Ok(self
383            .send_transaction(tx)
384            .await?
385            .error_for_status()?
386            .headers()
387            .get("x-bundle-id")
388            .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
389            .to_str()
390            .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
391            .to_string())
392    }
393
394    /// Sends a transaction without `bundleOnly` flag.
395    pub async fn send_transaction_no_bundle_only(
396        &self,
397        tx: impl Serialize,
398    ) -> anyhow::Result<Response> {
399        let data = BASE64_STANDARD.encode(bincode::serialize(&tx)?);
400        let body = json!({
401            "id": 1,
402            "jsonrpc": "2.0",
403            "method": "sendTransaction",
404            "params": [
405                data, { "encoding": "base64" }
406            ]
407        });
408
409        let (ref url, ref client) = *self.inner.lb.alloc().await;
410
411        if self.inner.broadcast {
412            Ok(futures::future::select_ok(url.iter().map(|v| {
413                client
414                    .post(&format!("{}/api/v1/transactions", v))
415                    .json(&body)
416                    .send()
417            }))
418            .await?
419            .0)
420        } else {
421            Ok(client
422                .post(&format!("{}/api/v1/transactions", url[0]))
423                .json(&body)
424                .send()
425                .await?)
426        }
427    }
428
429    /// Sends multiple transactions as a bundle.
430    pub async fn send_bundle<T: IntoIterator<Item = impl Serialize>>(
431        &self,
432        tx: T,
433    ) -> anyhow::Result<Response> {
434        let data = tx
435            .into_iter()
436            .map(|tx| {
437                Ok(BASE64_STANDARD.encode(
438                    bincode::serialize(&tx)
439                        .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
440                ))
441            })
442            .collect::<anyhow::Result<Vec<_>>>()?;
443
444        let body = json!({
445            "id": 1,
446            "jsonrpc": "2.0",
447            "method": "sendBundle",
448            "params": [ data, { "encoding": "base64" } ]
449        });
450
451        let (ref url, ref client) = *self.inner.lb.alloc().await;
452
453        if self.inner.broadcast {
454            Ok(futures::future::select_ok(url.iter().map(|v| {
455                client
456                    .post(&format!("{}/api/v1/bundles", v))
457                    .json(&body)
458                    .send()
459            }))
460            .await?
461            .0)
462        } else {
463            Ok(client
464                .post(&format!("{}/api/v1/bundles", url[0]))
465                .json(&body)
466                .send()
467                .await?)
468        }
469    }
470
471    /// Sends a bundle and returns its bundle ID from the JSON response.
472    pub async fn send_bundle_bid<T: IntoIterator<Item = impl Serialize>>(
473        &self,
474        tx: T,
475    ) -> anyhow::Result<String> {
476        self.send_bundle(tx)
477            .await?
478            .error_for_status()?
479            .json::<serde_json::Value>()
480            .await?["result"]
481            .as_str()
482            .map(|v| v.to_string())
483            .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
484    }
485
486    /// Sends a single transaction and returns the HTTP response, with lazy serialization.
487    pub async fn send_transaction_lazy<T>(
488        &self,
489        tx: impl Future<Output = anyhow::Result<T>>,
490    ) -> anyhow::Result<Response>
491    where
492        T: Serialize,
493    {
494        let (ref url, ref client) = *self.inner.lb.alloc().await;
495
496        let data = BASE64_STANDARD.encode(bincode::serialize(&tx.await?)?);
497
498        let body = json!({
499            "id": 1,
500            "jsonrpc": "2.0",
501            "method": "sendTransaction",
502            "params": [
503                data, { "encoding": "base64" }
504            ]
505        });
506
507        if self.inner.broadcast {
508            Ok(futures::future::select_ok(url.iter().map(|v| {
509                client
510                    .post(&format!("{}/api/v1/transactions", v))
511                    .query(&["bundleOnly", "true"])
512                    .json(&body)
513                    .send()
514            }))
515            .await?
516            .0)
517        } else {
518            Ok(client
519                .post(&format!("{}/api/v1/transactions", url[0]))
520                .query(&["bundleOnly", "true"])
521                .json(&body)
522                .send()
523                .await?)
524        }
525    }
526
527    /// Sends a transaction and returns the bundle ID from the response headers, with lazy serialization.
528    pub async fn send_transaction_bid_lazy<T>(
529        &self,
530        tx: impl Future<Output = anyhow::Result<T>>,
531    ) -> anyhow::Result<String>
532    where
533        T: Serialize,
534    {
535        Ok(self
536            .send_transaction_lazy(tx)
537            .await?
538            .error_for_status()?
539            .headers()
540            .get("x-bundle-id")
541            .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
542            .to_str()
543            .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
544            .to_string())
545    }
546
547    /// Sends a transaction without `bundleOnly` flag, with lazy serialization.
548    pub async fn send_transaction_no_bundle_only_lazy<T>(
549        &self,
550        tx: impl Future<Output = anyhow::Result<T>>,
551    ) -> anyhow::Result<Response>
552    where
553        T: Serialize,
554    {
555        let (ref url, ref client) = *self.inner.lb.alloc().await;
556
557        let data = BASE64_STANDARD.encode(bincode::serialize(&tx.await?)?);
558
559        let body = json!({
560            "id": 1,
561            "jsonrpc": "2.0",
562            "method": "sendTransaction",
563            "params": [
564                data, { "encoding": "base64" }
565            ]
566        });
567
568        if self.inner.broadcast {
569            Ok(futures::future::select_ok(url.iter().map(|v| {
570                client
571                    .post(&format!("{}/api/v1/transactions", v))
572                    .json(&body)
573                    .send()
574            }))
575            .await?
576            .0)
577        } else {
578            Ok(client
579                .post(&format!("{}/api/v1/transactions", url[0]))
580                .json(&body)
581                .send()
582                .await?)
583        }
584    }
585
586    /// Sends multiple transactions as a bundle, with lazy serialization.
587    pub async fn send_bundle_lazy<T, S>(
588        &self,
589        tx: impl Future<Output = anyhow::Result<T>>,
590    ) -> anyhow::Result<Response>
591    where
592        T: IntoIterator<Item = S>,
593        S: Serialize,
594    {
595        let (ref url, ref client) = *self.inner.lb.alloc().await;
596
597        let data = tx
598            .await?
599            .into_iter()
600            .map(|tx| {
601                Ok(BASE64_STANDARD.encode(
602                    bincode::serialize(&tx)
603                        .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
604                ))
605            })
606            .collect::<anyhow::Result<Vec<_>>>()?;
607
608        let body = json!({
609            "id": 1,
610            "jsonrpc": "2.0",
611            "method": "sendBundle",
612            "params": [ data, { "encoding": "base64" } ]
613        });
614
615        if self.inner.broadcast {
616            Ok(futures::future::select_ok(url.iter().map(|v| {
617                client
618                    .post(&format!("{}/api/v1/bundles", v))
619                    .json(&body)
620                    .send()
621            }))
622            .await?
623            .0)
624        } else {
625            Ok(client
626                .post(&format!("{}/api/v1/bundles", url[0]))
627                .json(&body)
628                .send()
629                .await?)
630        }
631    }
632
633    /// Sends a bundle and returns its bundle ID from the JSON response, with lazy serialization.
634    pub async fn send_bundle_bid_lazy<T, S>(
635        &self,
636        tx: impl Future<Output = anyhow::Result<T>>,
637    ) -> anyhow::Result<String>
638    where
639        T: IntoIterator<Item = S>,
640        S: Serialize,
641    {
642        self.send_bundle_lazy(tx)
643            .await?
644            .error_for_status()?
645            .json::<serde_json::Value>()
646            .await?["result"]
647            .as_str()
648            .map(|v| v.to_string())
649            .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
650    }
651
652    /// Sends a single transaction and returns the HTTP response, with lazy serialization.
653    #[cfg(rustc_version_1_85_0)]
654    pub async fn send_transaction_lazy_fn<F, T>(&self, callback: F) -> anyhow::Result<Response>
655    where
656        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
657        T: Serialize,
658    {
659        let (ref url, ref client) = *self.inner.lb.alloc().await;
660
661        let data = BASE64_STANDARD.encode(bincode::serialize(&callback(url, client).await?)?);
662
663        let body = json!({
664            "id": 1,
665            "jsonrpc": "2.0",
666            "method": "sendTransaction",
667            "params": [
668                data, { "encoding": "base64" }
669            ]
670        });
671
672        if self.inner.broadcast {
673            Ok(futures::future::select_ok(url.iter().map(|v| {
674                client
675                    .post(&format!("{}/api/v1/transactions", v))
676                    .query(&["bundleOnly", "true"])
677                    .json(&body)
678                    .send()
679            }))
680            .await?
681            .0)
682        } else {
683            Ok(client
684                .post(&format!("{}/api/v1/transactions", url[0]))
685                .query(&["bundleOnly", "true"])
686                .json(&body)
687                .send()
688                .await?)
689        }
690    }
691
692    /// Sends a transaction and returns the bundle ID from the response headers, with lazy serialization.
693    #[cfg(rustc_version_1_85_0)]
694    pub async fn send_transaction_bid_lazy_fn<F, T>(&self, callback: F) -> anyhow::Result<String>
695    where
696        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
697        T: Serialize,
698    {
699        Ok(self
700            .send_transaction_lazy_fn(callback)
701            .await?
702            .error_for_status()?
703            .headers()
704            .get("x-bundle-id")
705            .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
706            .to_str()
707            .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
708            .to_string())
709    }
710
711    /// Sends a transaction without `bundleOnly` flag, with lazy serialization.
712    #[cfg(rustc_version_1_85_0)]
713    pub async fn send_transaction_no_bundle_only_lazy_fn<F, T>(
714        &self,
715        callback: F,
716    ) -> anyhow::Result<Response>
717    where
718        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
719        T: Serialize,
720    {
721        let (ref url, ref client) = *self.inner.lb.alloc().await;
722
723        let data = BASE64_STANDARD.encode(bincode::serialize(&callback(url, client).await?)?);
724
725        let body = json!({
726            "id": 1,
727            "jsonrpc": "2.0",
728            "method": "sendTransaction",
729            "params": [
730                data, { "encoding": "base64" }
731            ]
732        });
733
734        if self.inner.broadcast {
735            Ok(futures::future::select_ok(url.iter().map(|v| {
736                client
737                    .post(&format!("{}/api/v1/transactions", v))
738                    .json(&body)
739                    .send()
740            }))
741            .await?
742            .0)
743        } else {
744            Ok(client
745                .post(&format!("{}/api/v1/transactions", url[0]))
746                .json(&body)
747                .send()
748                .await?)
749        }
750    }
751
752    /// Sends multiple transactions as a bundle, with lazy serialization.
753    #[cfg(rustc_version_1_85_0)]
754    pub async fn send_bundle_lazy_fn<F, T, S>(&self, callback: F) -> anyhow::Result<Response>
755    where
756        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
757        T: IntoIterator<Item = S>,
758        S: Serialize,
759    {
760        let (ref url, ref client) = *self.inner.lb.alloc().await;
761
762        let data = callback(url, client)
763            .await?
764            .into_iter()
765            .map(|tx| {
766                Ok(BASE64_STANDARD.encode(
767                    bincode::serialize(&tx)
768                        .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
769                ))
770            })
771            .collect::<anyhow::Result<Vec<_>>>()?;
772
773        let body = json!({
774            "id": 1,
775            "jsonrpc": "2.0",
776            "method": "sendBundle",
777            "params": [ data, { "encoding": "base64" } ]
778        });
779
780        if self.inner.broadcast {
781            Ok(futures::future::select_ok(url.iter().map(|v| {
782                client
783                    .post(&format!("{}/api/v1/bundles", v))
784                    .json(&body)
785                    .send()
786            }))
787            .await?
788            .0)
789        } else {
790            Ok(client
791                .post(&format!("{}/api/v1/bundles", url[0]))
792                .json(&body)
793                .send()
794                .await?)
795        }
796    }
797
798    /// Sends a bundle and returns its bundle ID from the JSON response, with lazy serialization.
799    #[cfg(rustc_version_1_85_0)]
800    pub async fn send_bundle_bid_lazy_fn<F, T, S>(&self, callback: F) -> anyhow::Result<String>
801    where
802        F: AsyncFnOnce(&Vec<String>, &Client) -> anyhow::Result<T>,
803        T: IntoIterator<Item = S>,
804        S: Serialize,
805    {
806        self.send_bundle_lazy_fn(callback)
807            .await?
808            .error_for_status()?
809            .json::<serde_json::Value>()
810            .await?["result"]
811            .as_str()
812            .map(|v| v.to_string())
813            .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
814    }
815}
816
817/// Represents Jito tip data.
818#[derive(Debug, Clone, Deserialize)]
819pub struct JitoTip {
820    pub landed_tips_25th_percentile: f64,
821    pub landed_tips_50th_percentile: f64,
822    pub landed_tips_75th_percentile: f64,
823    pub landed_tips_95th_percentile: f64,
824    pub landed_tips_99th_percentile: f64,
825    pub ema_landed_tips_50th_percentile: f64,
826}
827
828/// Fetches the current Jito tip from the public API.
829pub async fn get_jito_tip(client: Client) -> anyhow::Result<JitoTip> {
830    Ok(client
831        .get("https://bundles.jito.wtf/api/v1/bundles/tip_floor")
832        .send()
833        .await?
834        .json::<Vec<JitoTip>>()
835        .await?
836        .get(0)
837        .context("get_jito_tip: empty response")?
838        .clone())
839}
840
841/// Represents the result of querying bundle statuses.
842#[derive(Debug, Deserialize)]
843pub struct BundleResult {
844    pub context: serde_json::Value,
845    pub value: Option<Vec<BundleStatus>>,
846}
847
848#[derive(Debug, Deserialize)]
849pub struct BundleStatus {
850    pub bundle_id: String,
851    pub transactions: Option<Vec<String>>,
852    pub slot: Option<u64>,
853    pub confirmation_status: Option<String>,
854    pub err: Option<serde_json::Value>,
855}
856
857/// Fetches statuses of multiple bundles.
858pub async fn get_bundle_statuses<T: IntoIterator<Item = impl AsRef<str>>>(
859    client: Client,
860    bundle: T,
861) -> anyhow::Result<BundleResult> {
862    #[derive(Debug, Deserialize)]
863    struct RpcResponse {
864        result: BundleResult,
865    }
866
867    let payload = json!({
868        "jsonrpc": "2.0",
869        "id": 1,
870        "method": "getBundleStatuses",
871        "params": [bundle.into_iter().map(|v| v.as_ref().to_string()).collect::<Vec<_>>()],
872    });
873
874    Ok(client
875        .post("https://mainnet.block-engine.jito.wtf/api/v1/getBundleStatuses")
876        .json(&payload)
877        .send()
878        .await?
879        .json::<RpcResponse>()
880        .await?
881        .result)
882}
883
884/// Represents in-flight bundle status.
885#[derive(Debug, Deserialize)]
886pub struct InflightBundleStatus {
887    pub bundle_id: String,
888    pub status: String,
889    pub landed_slot: Option<u64>,
890}
891
892#[derive(Debug, Deserialize)]
893pub struct InflightBundleResult {
894    pub context: serde_json::Value,
895    pub value: Option<Vec<InflightBundleStatus>>,
896}
897
898/// Fetches statuses of in-flight bundles.
899pub async fn get_inflight_bundle_statuses<T: IntoIterator<Item = impl AsRef<str>>>(
900    client: Client,
901    bundle: T,
902) -> anyhow::Result<InflightBundleResult> {
903    #[derive(Debug, Deserialize)]
904    struct InflightRpcResponse {
905        result: InflightBundleResult,
906    }
907
908    let payload = json!({
909        "jsonrpc": "2.0",
910        "id": 1,
911        "method": "getInflightBundleStatuses",
912        "params": [bundle.into_iter().map(|v| v.as_ref().to_string()).collect::<Vec<_>>()],
913    });
914
915    Ok(client
916        .post("https://mainnet.block-engine.jito.wtf/api/v1/getInflightBundleStatuses")
917        .json(&payload)
918        .send()
919        .await?
920        .json::<InflightRpcResponse>()
921        .await?
922        .result)
923}
924
925pub async fn test_ip(ip: IpAddr) -> anyhow::Result<IpAddr> {
926    reqwest::ClientBuilder::new()
927        .timeout(Duration::from_secs(3))
928        .local_address(ip)
929        .build()?
930        .get("https://bilibili.com")
931        .send()
932        .await?;
933
934    Ok(ip)
935}
936
937pub async fn test_all_ip() -> Vec<anyhow::Result<IpAddr>> {
938    match get_ip_list() {
939        Ok(v) => futures::future::join_all(v.into_iter().map(|v| test_ip(v))).await,
940        Err(_) => Vec::new(),
941    }
942}
943
944pub async fn test_all_ipv4() -> Vec<anyhow::Result<IpAddr>> {
945    match get_ipv4_list() {
946        Ok(v) => futures::future::join_all(v.into_iter().map(|v| test_ip(v))).await,
947        Err(_) => Vec::new(),
948    }
949}
950
951pub async fn test_all_ipv6() -> Vec<anyhow::Result<IpAddr>> {
952    match get_ipv6_list() {
953        Ok(v) => futures::future::join_all(v.into_iter().map(|v| test_ip(v))).await,
954        Err(_) => Vec::new(),
955    }
956}
957
958pub fn serialize_tx(tx: impl Serialize) -> anyhow::Result<String> {
959    Ok(BASE64_STANDARD.encode(bincode::serialize(&tx)?))
960}
961
962pub fn serialize_tx_vec<T: IntoIterator<Item = impl Serialize>>(
963    tx: T,
964) -> anyhow::Result<Vec<String>> {
965    tx.into_iter()
966        .map(|tx| {
967            Ok(BASE64_STANDARD.encode(
968                bincode::serialize(&tx)
969                    .map_err(|v| anyhow::anyhow!("failed to serialize tx: {}", v))?,
970            ))
971        })
972        .collect::<anyhow::Result<Vec<_>>>()
973}