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
17pub 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 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 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 pub fn interval(mut self, interval: Duration) -> Self {
51 self.interval = interval;
52 self
53 }
54
55 pub fn ip(mut self, ip: Vec<IpAddr>) -> Self {
57 self.ip = ip;
58 self
59 }
60
61 pub fn broadcast(mut self, broadcast: bool) -> Self {
63 self.broadcast = broadcast;
64 self
65 }
66
67 pub fn timeout(mut self, timeout: Duration) -> Self {
69 self.timeout = Some(timeout);
70 self
71 }
72
73 pub fn proxy(mut self, proxy: Proxy) -> Self {
75 self.proxy = Some(proxy);
76 self
77 }
78
79 pub fn headers(mut self, headers: HeaderMap) -> Self {
81 self.headers = Some(headers);
82 self
83 }
84
85 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#[derive(Clone)]
198pub struct JitoClient {
199 inner: Arc<JitoClientRef>,
200}
201
202impl JitoClient {
203 pub fn new() -> Self {
205 JitoClientBuilder::new().build().unwrap()
206 }
207
208 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 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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 #[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 #[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 #[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#[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
828pub 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#[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
857pub 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#[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
898pub 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}