1use anyhow::{Context, anyhow, bail};
2use base64::prelude::*;
3use futures::future::{join_all, select_ok};
4use load_balancer::{LoadBalancer, time::TimeLoadBalancer};
5use reqwest::header::HeaderName;
6use reqwest::{Client, ClientBuilder, Response};
7use serde::{Deserialize, Serialize};
8use serde_json::{Value, json};
9use std::{net::IpAddr, sync::Arc, time::Duration};
10
11pub use get_if_addrs::get_if_addrs;
12pub use load_balancer;
13pub use reqwest;
14pub use reqwest::Proxy;
15pub use reqwest::StatusCode;
16pub use reqwest::header::HeaderMap;
17pub use serde_json;
18
19pub struct JitoClientBuilder {
21 url: Vec<String>,
22 broadcast: bool,
23 interval: Duration,
24 timeout: Option<Duration>,
25 proxy: Option<Proxy>,
26 headers: Option<HeaderMap>,
27 ip: Vec<IpAddr>,
28 headers_with_separator: Option<String>,
29 error_read_headers: bool,
30 error_read_body: bool,
31}
32
33impl JitoClientBuilder {
34 pub fn new() -> Self {
36 Self {
37 url: vec!["https://mainnet.block-engine.jito.wtf".to_string()],
38 broadcast: false,
39 interval: Duration::ZERO,
40 timeout: None,
41 proxy: None,
42 headers: None,
43 ip: Vec::new(),
44 headers_with_separator: None,
45 error_read_headers: false,
46 error_read_body: false,
47 }
48 }
49
50 pub fn url<T: IntoIterator<Item = impl AsRef<str>>>(mut self, url: T) -> Self {
52 self.url = url.into_iter().map(|v| v.as_ref().to_string()).collect();
53 self
54 }
55
56 pub fn interval(mut self, interval: Duration) -> Self {
59 self.interval = interval;
60 self
61 }
62
63 pub fn ip<T: IntoIterator<Item = IpAddr>>(mut self, ip: T) -> Self {
65 self.ip = ip.into_iter().collect();
66 self
67 }
68
69 pub fn broadcast(mut self, broadcast: bool) -> Self {
71 self.broadcast = broadcast;
72 self
73 }
74
75 pub fn timeout(mut self, timeout: Duration) -> Self {
77 self.timeout = Some(timeout);
78 self
79 }
80
81 pub fn proxy(mut self, proxy: Proxy) -> Self {
83 self.proxy = Some(proxy);
84 self
85 }
86
87 pub fn headers(mut self, headers: HeaderMap) -> Self {
89 self.headers = Some(headers);
90 self
91 }
92
93 pub fn error_read_headers(mut self, error_read_headers: bool) -> Self {
95 self.error_read_headers = error_read_headers;
96 self
97 }
98
99 pub fn error_read_body(mut self, error_read_body: bool) -> Self {
101 self.error_read_body = error_read_body;
102 self
103 }
104
105 pub fn headers_with_separator(mut self, headers_with_separator: impl AsRef<str>) -> Self {
111 self.headers_with_separator = Some(headers_with_separator.as_ref().to_string());
112 self
113 }
114
115 pub fn build(self) -> anyhow::Result<JitoClient> {
117 let default_ip = self.ip.is_empty();
118
119 let inner = if self.broadcast {
120 let mut entries = Vec::new();
121
122 if default_ip {
123 let mut cb = ClientBuilder::new();
124
125 if let Some(v) = self.timeout {
126 cb = cb.timeout(v);
127 }
128
129 if let Some(v) = self.proxy {
130 cb = cb.proxy(v);
131 }
132
133 if let Some(v) = self.headers {
134 cb = cb.default_headers(v);
135 }
136
137 entries.push((
138 self.interval,
139 Arc::new((self.url.clone(), cb.build()?, "127.0.0.1".parse()?)),
140 ));
141 } else {
142 for ip in &self.ip {
143 let mut cb = ClientBuilder::new();
144
145 if let Some(v) = self.timeout {
146 cb = cb.timeout(v);
147 }
148
149 if let Some(v) = self.proxy.clone() {
150 cb = cb.proxy(v);
151 }
152
153 if let Some(v) = self.headers.clone() {
154 cb = cb.default_headers(v);
155 }
156
157 cb = cb.local_address(*ip);
158
159 entries.push((
160 self.interval,
161 Arc::new((self.url.clone(), cb.build()?, *ip)),
162 ));
163 }
164 }
165
166 JitoClientRef {
167 lb: TimeLoadBalancer::new(entries),
168 error_read_body: self.error_read_body,
169 error_read_headers: self.error_read_headers,
170 headers_with_separator: self.headers_with_separator,
171 }
172 } else {
173 let mut entries = Vec::new();
174
175 if default_ip {
176 for url in &self.url {
177 let mut cb = ClientBuilder::new();
178
179 if let Some(v) = self.timeout {
180 cb = cb.timeout(v);
181 }
182
183 if let Some(v) = self.proxy.clone() {
184 cb = cb.proxy(v);
185 }
186
187 if let Some(v) = self.headers.clone() {
188 cb = cb.default_headers(v);
189 }
190
191 entries.push((
192 self.interval,
193 Arc::new((vec![url.clone()], cb.build()?, "127.0.0.1".parse()?)),
194 ));
195 }
196 } else {
197 for url in &self.url {
198 for ip in &self.ip {
199 let mut cb = ClientBuilder::new();
200
201 if let Some(v) = self.timeout {
202 cb = cb.timeout(v);
203 }
204
205 if let Some(v) = self.proxy.clone() {
206 cb = cb.proxy(v);
207 }
208
209 if let Some(v) = self.headers.clone() {
210 cb = cb.default_headers(v);
211 }
212
213 cb = cb.local_address(*ip);
214
215 entries.push((
216 self.interval,
217 Arc::new((vec![url.clone()], cb.build()?, *ip)),
218 ));
219 }
220 }
221 }
222
223 JitoClientRef {
224 lb: TimeLoadBalancer::new(entries),
225 error_read_body: self.error_read_body,
226 error_read_headers: self.error_read_headers,
227 headers_with_separator: self.headers_with_separator,
228 }
229 };
230
231 Ok(JitoClient {
232 inner: inner.into(),
233 })
234 }
235}
236
237pub type Entry = (Vec<String>, Client, IpAddr);
241
242struct JitoClientRef {
243 lb: TimeLoadBalancer<Arc<Entry>>,
244 error_read_body: bool,
245 error_read_headers: bool,
246 headers_with_separator: Option<String>,
247}
248
249#[derive(Clone)]
251pub struct JitoClient {
252 inner: Arc<JitoClientRef>,
253}
254
255#[derive(Clone)]
256pub struct JitoClientOnce {
257 inner: Arc<JitoClientRef>,
258 entry: Arc<Entry>,
259}
260
261impl JitoClientOnce {
262 async fn handle_response(&self, response: Response) -> anyhow::Result<Response> {
263 if response.status().is_success() {
264 return Ok(response);
265 }
266
267 let status = response.status();
268
269 let headers = if self.inner.error_read_headers {
270 format!("{:#?}", response.headers())
271 } else {
272 String::new()
273 };
274
275 let body = if self.inner.error_read_body {
276 response.text().await.unwrap_or_default()
277 } else {
278 String::new()
279 };
280
281 match (self.inner.error_read_headers, self.inner.error_read_body) {
282 (true, true) => bail!("{}\n{}\n{}", status, headers, body),
283 (true, false) => bail!("{}\n{}", status, headers),
284 (false, true) => bail!("{}\n{}", status, body),
285 (false, false) => bail!("{}", status),
286 }
287 }
288
289 fn split_url<'a>(&'a self, url: &'a str) -> anyhow::Result<(&'a str, HeaderMap)> {
290 let mut headers = HeaderMap::new();
291
292 if let Some(v) = &self.inner.headers_with_separator {
293 if let Some((a, b)) = url.split_once(v) {
294 for (k, v) in form_urlencoded::parse(b.as_bytes()) {
295 headers.insert(HeaderName::from_bytes(k.as_bytes())?, v.parse()?);
296 }
297
298 return Ok((a, headers));
299 }
300 }
301
302 Ok((url, headers))
303 }
304
305 pub fn entry(&self) -> Arc<Entry> {
306 self.entry.clone()
307 }
308
309 pub async fn raw_send(&self, body: &Value) -> anyhow::Result<Response> {
311 let (ref url, ref client, ..) = *self.entry;
312
313 if url.len() > 1 {
314 Ok(select_ok(url.iter().map(|v| {
315 Box::pin(async move {
316 let (url, headers) = self.split_url(v)?;
317 let response = client.post(url).headers(headers).json(&body).send().await?;
318 let response = self.handle_response(response).await?;
319
320 anyhow::Ok(response)
321 })
322 }))
323 .await?
324 .0)
325 } else {
326 let (url, headers) = self.split_url(&url[0])?;
327 let response = client.post(url).headers(headers).json(body).send().await?;
328
329 self.handle_response(response).await
330 }
331 }
332
333 pub async fn raw_send_api(
335 &self,
336 api_url: impl AsRef<str>,
337 body: &Value,
338 ) -> anyhow::Result<Response> {
339 let (ref url, ref client, ..) = *self.entry;
340 let api_url = api_url.as_ref();
341
342 if url.len() > 1 {
343 Ok(select_ok(url.iter().map(|v| {
344 Box::pin(async move {
345 let (base, headers) = self.split_url(v)?;
346
347 anyhow::Ok(
348 self.handle_response(
349 client
350 .post(&format!("{}{}", base, api_url))
351 .headers(headers)
352 .json(&body)
353 .send()
354 .await?,
355 )
356 .await?,
357 )
358 })
359 }))
360 .await?
361 .0)
362 } else {
363 let (base, headers) = self.split_url(&url[0])?;
364
365 self.handle_response(
366 client
367 .post(&format!("{}{}", base, api_url))
368 .headers(headers)
369 .json(body)
370 .send()
371 .await?,
372 )
373 .await
374 }
375 }
376
377 pub async fn send_transaction(&self, tx: impl Serialize) -> anyhow::Result<Response> {
379 let data = serialize_tx_checked(tx)?;
380
381 let body = &json!({
382 "id": 1,
383 "jsonrpc": "2.0",
384 "method": "sendTransaction",
385 "params": [
386 data, { "encoding": "base64" }
387 ]
388 });
389
390 let (ref url, ref client, ..) = *self.entry;
391
392 if url.len() > 1 {
393 Ok(select_ok(url.iter().map(|v| {
394 Box::pin(async move {
395 let (base, headers) = self.split_url(v)?;
396
397 anyhow::Ok(
398 self.handle_response(
399 client
400 .post(&format!("{}/api/v1/transactions", base))
401 .headers(headers)
402 .query(&[("bundleOnly", "true")])
403 .json(body)
404 .send()
405 .await?,
406 )
407 .await?,
408 )
409 })
410 }))
411 .await?
412 .0)
413 } else {
414 let (base, headers) = self.split_url(&url[0])?;
415
416 let response = client
417 .post(&format!("{}/api/v1/transactions", base))
418 .headers(headers)
419 .query(&[("bundleOnly", "true")])
420 .json(body)
421 .send()
422 .await?;
423
424 self.handle_response(response).await
425 }
426 }
427
428 pub async fn send_transaction_bid(&self, tx: impl Serialize) -> anyhow::Result<String> {
430 let response = self.send_transaction(tx).await?;
431
432 Ok(response
433 .headers()
434 .get("x-bundle-id")
435 .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
436 .to_str()
437 .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
438 .to_string())
439 }
440
441 pub async fn send_transaction_no_bundle_only(
443 &self,
444 tx: impl Serialize,
445 ) -> anyhow::Result<Response> {
446 let data = serialize_tx_checked(tx)?;
447 let body = &json!({
448 "id": 1,
449 "jsonrpc": "2.0",
450 "method": "sendTransaction",
451 "params": [
452 data, { "encoding": "base64" }
453 ]
454 });
455
456 let (ref url, ref client, ..) = *self.entry;
457
458 if url.len() > 1 {
459 Ok(select_ok(url.iter().map(|v| {
460 Box::pin(async move {
461 let (base, headers) = self.split_url(v)?;
462
463 anyhow::Ok(
464 self.handle_response(
465 client
466 .post(&format!("{}/api/v1/transactions", base))
467 .headers(headers)
468 .json(body)
469 .send()
470 .await?,
471 )
472 .await?,
473 )
474 })
475 }))
476 .await?
477 .0)
478 } else {
479 let (base, headers) = self.split_url(&url[0])?;
480
481 self.handle_response(
482 client
483 .post(&format!("{}/api/v1/transactions", base))
484 .headers(headers)
485 .json(body)
486 .send()
487 .await?,
488 )
489 .await
490 }
491 }
492
493 pub async fn send_bundle<T: IntoIterator<Item = impl Serialize>>(
495 &self,
496 tx: T,
497 ) -> anyhow::Result<Response> {
498 let data = serialize_tx_vec_checked(tx)?;
499
500 let body = &json!({
501 "id": 1,
502 "jsonrpc": "2.0",
503 "method": "sendBundle",
504 "params": [ data, { "encoding": "base64" } ]
505 });
506
507 let (ref url, ref client, ..) = *self.entry;
508
509 if url.len() > 1 {
510 Ok(select_ok(url.iter().map(|v| {
511 Box::pin(async move {
512 let (base, headers) = self.split_url(v)?;
513
514 anyhow::Ok(
515 self.handle_response(
516 client
517 .post(&format!("{}/api/v1/bundles", base))
518 .headers(headers)
519 .json(body)
520 .send()
521 .await?,
522 )
523 .await?,
524 )
525 })
526 }))
527 .await?
528 .0)
529 } else {
530 let (base, headers) = self.split_url(&url[0])?;
531
532 self.handle_response(
533 client
534 .post(&format!("{}/api/v1/bundles", base))
535 .headers(headers)
536 .json(body)
537 .send()
538 .await?,
539 )
540 .await
541 }
542 }
543
544 pub async fn send_bundle_bid<T: IntoIterator<Item = impl Serialize>>(
546 &self,
547 tx: T,
548 ) -> anyhow::Result<String> {
549 self.send_bundle(tx)
550 .await?
551 .error_for_status()?
552 .json::<Value>()
553 .await?["result"]
554 .as_str()
555 .map(|v| v.to_string())
556 .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
557 }
558
559 pub async fn send_transaction_str(&self, tx: impl AsRef<str>) -> anyhow::Result<Response> {
561 let data = tx.as_ref();
562
563 let body = &json!({
564 "id": 1,
565 "jsonrpc": "2.0",
566 "method": "sendTransaction",
567 "params": [
568 data, { "encoding": "base64" }
569 ]
570 });
571
572 let (ref url, ref client, ..) = *self.entry;
573
574 if url.len() > 1 {
575 Ok(select_ok(url.iter().map(|v| {
576 Box::pin(async move {
577 let (base, headers) = self.split_url(v)?;
578
579 anyhow::Ok(
580 self.handle_response(
581 client
582 .post(&format!("{}/api/v1/transactions", base))
583 .headers(headers)
584 .query(&[("bundleOnly", "true")])
585 .json(body)
586 .send()
587 .await?,
588 )
589 .await?,
590 )
591 })
592 }))
593 .await?
594 .0)
595 } else {
596 let (base, headers) = self.split_url(&url[0])?;
597
598 let response = client
599 .post(&format!("{}/api/v1/transactions", base))
600 .headers(headers)
601 .query(&[("bundleOnly", "true")])
602 .json(body)
603 .send()
604 .await?;
605
606 self.handle_response(response).await
607 }
608 }
609
610 pub async fn send_transaction_bid_str(&self, tx: impl AsRef<str>) -> anyhow::Result<String> {
612 let response = self.send_transaction_str(tx).await?;
613
614 Ok(response
615 .headers()
616 .get("x-bundle-id")
617 .ok_or_else(|| anyhow!("missing `x-bundle-id` header"))?
618 .to_str()
619 .map_err(|v| anyhow!("invalid `x-bundle-id` header: {}", v))?
620 .to_string())
621 }
622
623 pub async fn send_transaction_no_bundle_only_str(
625 &self,
626 tx: impl AsRef<str>,
627 ) -> anyhow::Result<Response> {
628 let data = tx.as_ref();
629 let body = &json!({
630 "id": 1,
631 "jsonrpc": "2.0",
632 "method": "sendTransaction",
633 "params": [
634 data, { "encoding": "base64" }
635 ]
636 });
637
638 let (ref url, ref client, ..) = *self.entry;
639
640 if url.len() > 1 {
641 Ok(select_ok(url.iter().map(|v| {
642 Box::pin(async move {
643 let (base, headers) = self.split_url(v)?;
644
645 anyhow::Ok(
646 self.handle_response(
647 client
648 .post(&format!("{}/api/v1/transactions", base))
649 .headers(headers)
650 .json(body)
651 .send()
652 .await?,
653 )
654 .await?,
655 )
656 })
657 }))
658 .await?
659 .0)
660 } else {
661 let (base, headers) = self.split_url(&url[0])?;
662
663 self.handle_response(
664 client
665 .post(&format!("{}/api/v1/transactions", base))
666 .headers(headers)
667 .json(body)
668 .send()
669 .await?,
670 )
671 .await
672 }
673 }
674
675 pub async fn send_bundle_str<T: IntoIterator<Item = impl AsRef<str>>>(
677 &self,
678 tx: T,
679 ) -> anyhow::Result<Response> {
680 let data = tx
681 .into_iter()
682 .map(|x| x.as_ref().to_string())
683 .collect::<Vec<String>>();
684
685 let body = &json!({
686 "id": 1,
687 "jsonrpc": "2.0",
688 "method": "sendBundle",
689 "params": [ data, { "encoding": "base64" } ]
690 });
691
692 let (ref url, ref client, ..) = *self.entry;
693
694 if url.len() > 1 {
695 Ok(select_ok(url.iter().map(|v| {
696 Box::pin(async move {
697 let (base, headers) = self.split_url(v)?;
698
699 anyhow::Ok(
700 self.handle_response(
701 client
702 .post(&format!("{}/api/v1/bundles", base))
703 .headers(headers)
704 .json(body)
705 .send()
706 .await?,
707 )
708 .await?,
709 )
710 })
711 }))
712 .await?
713 .0)
714 } else {
715 let (base, headers) = self.split_url(&url[0])?;
716
717 self.handle_response(
718 client
719 .post(&format!("{}/api/v1/bundles", base))
720 .headers(headers)
721 .json(body)
722 .send()
723 .await?,
724 )
725 .await
726 }
727 }
728
729 pub async fn send_bundle_bid_str<T: IntoIterator<Item = impl AsRef<str>>>(
731 &self,
732 tx: T,
733 ) -> anyhow::Result<String> {
734 self.send_bundle_str(tx)
735 .await?
736 .error_for_status()?
737 .json::<Value>()
738 .await?["result"]
739 .as_str()
740 .map(|v| v.to_string())
741 .ok_or_else(|| anyhow::anyhow!("missing bundle result"))
742 }
743}
744
745impl JitoClient {
746 pub fn new() -> Self {
748 JitoClientBuilder::new().build().unwrap()
749 }
750
751 pub async fn alloc(&self) -> JitoClientOnce {
753 JitoClientOnce {
754 inner: self.inner.clone(),
755 entry: self.inner.lb.alloc().await,
756 }
757 }
758
759 pub fn try_alloc(&self) -> Option<JitoClientOnce> {
761 Some(JitoClientOnce {
762 inner: self.inner.clone(),
763 entry: self.inner.lb.try_alloc()?,
764 })
765 }
766
767 pub async fn raw_send(&self, body: &Value) -> anyhow::Result<Response> {
769 self.alloc().await.raw_send(body).await
770 }
771
772 pub async fn raw_send_api(
774 &self,
775 api_url: impl AsRef<str>,
776 body: &Value,
777 ) -> anyhow::Result<Response> {
778 self.alloc().await.raw_send_api(api_url, body).await
779 }
780
781 pub async fn send_transaction(&self, tx: impl Serialize) -> anyhow::Result<Response> {
783 self.alloc().await.send_transaction(tx).await
784 }
785
786 pub async fn send_transaction_bid(&self, tx: impl Serialize) -> anyhow::Result<String> {
788 self.alloc().await.send_transaction_bid(tx).await
789 }
790
791 pub async fn send_transaction_no_bundle_only(
793 &self,
794 tx: impl Serialize,
795 ) -> anyhow::Result<Response> {
796 self.alloc().await.send_transaction_no_bundle_only(tx).await
797 }
798
799 pub async fn send_bundle<T: IntoIterator<Item = impl Serialize>>(
801 &self,
802 tx: T,
803 ) -> anyhow::Result<Response> {
804 self.alloc().await.send_bundle(tx).await
805 }
806
807 pub async fn send_bundle_bid<T: IntoIterator<Item = impl Serialize>>(
809 &self,
810 tx: T,
811 ) -> anyhow::Result<String> {
812 self.alloc().await.send_bundle_bid(tx).await
813 }
814
815 pub async fn send_transaction_str(&self, tx: impl AsRef<str>) -> anyhow::Result<Response> {
817 self.alloc().await.send_transaction_str(tx).await
818 }
819
820 pub async fn send_transaction_bid_str(&self, tx: impl AsRef<str>) -> anyhow::Result<String> {
822 self.alloc().await.send_transaction_bid_str(tx).await
823 }
824
825 pub async fn send_transaction_no_bundle_only_str(
827 &self,
828 tx: impl AsRef<str>,
829 ) -> anyhow::Result<Response> {
830 self.alloc()
831 .await
832 .send_transaction_no_bundle_only_str(tx)
833 .await
834 }
835
836 pub async fn send_bundle_str<T: IntoIterator<Item = impl AsRef<str>>>(
838 &self,
839 tx: T,
840 ) -> anyhow::Result<Response> {
841 self.alloc().await.send_bundle_str(tx).await
842 }
843
844 pub async fn send_bundle_bid_str<T: IntoIterator<Item = impl AsRef<str>>>(
846 &self,
847 tx: T,
848 ) -> anyhow::Result<String> {
849 self.alloc().await.send_bundle_bid_str(tx).await
850 }
851}
852
853#[derive(Debug, Clone, Deserialize)]
855pub struct JitoTip {
856 pub landed_tips_25th_percentile: f64,
857 pub landed_tips_50th_percentile: f64,
858 pub landed_tips_75th_percentile: f64,
859 pub landed_tips_95th_percentile: f64,
860 pub landed_tips_99th_percentile: f64,
861 pub ema_landed_tips_50th_percentile: f64,
862}
863
864pub async fn get_jito_tip(client: Client) -> anyhow::Result<JitoTip> {
866 Ok(client
867 .get("https://bundles.jito.wtf/api/v1/bundles/tip_floor")
868 .send()
869 .await?
870 .json::<Vec<JitoTip>>()
871 .await?
872 .get(0)
873 .context("get_jito_tip: empty response")?
874 .clone())
875}
876
877#[derive(Debug, Deserialize)]
879pub struct BundleResult {
880 pub context: Value,
881 pub value: Option<Vec<BundleStatus>>,
882}
883
884#[derive(Debug, Deserialize)]
885pub struct BundleStatus {
886 pub bundle_id: String,
887 pub transactions: Option<Vec<String>>,
888 pub slot: Option<u64>,
889 pub confirmation_status: Option<String>,
890 pub err: Option<Value>,
891}
892
893pub async fn get_bundle_statuses<T: IntoIterator<Item = impl AsRef<str>>>(
895 client: Client,
896 bundle: T,
897) -> anyhow::Result<BundleResult> {
898 #[derive(Debug, Deserialize)]
899 struct RpcResponse {
900 result: BundleResult,
901 }
902
903 let payload = json!({
904 "jsonrpc": "2.0",
905 "id": 1,
906 "method": "getBundleStatuses",
907 "params": [bundle.into_iter().map(|v| v.as_ref().to_string()).collect::<Vec<_>>()],
908 });
909
910 Ok(client
911 .post("https://mainnet.block-engine.jito.wtf/api/v1/getBundleStatuses")
912 .json(&payload)
913 .send()
914 .await?
915 .json::<RpcResponse>()
916 .await?
917 .result)
918}
919
920#[derive(Debug, Deserialize)]
922pub struct InflightBundleStatus {
923 pub bundle_id: String,
924 pub status: String,
925 pub landed_slot: Option<u64>,
926}
927
928#[derive(Debug, Deserialize)]
929pub struct InflightBundleResult {
930 pub context: Value,
931 pub value: Option<Vec<InflightBundleStatus>>,
932}
933
934pub async fn get_inflight_bundle_statuses<T: IntoIterator<Item = impl AsRef<str>>>(
936 client: Client,
937 bundle: T,
938) -> anyhow::Result<InflightBundleResult> {
939 #[derive(Debug, Deserialize)]
940 struct InflightRpcResponse {
941 result: InflightBundleResult,
942 }
943
944 let payload = json!({
945 "jsonrpc": "2.0",
946 "id": 1,
947 "method": "getInflightBundleStatuses",
948 "params": [bundle.into_iter().map(|v| v.as_ref().to_string()).collect::<Vec<_>>()],
949 });
950
951 Ok(client
952 .post("https://mainnet.block-engine.jito.wtf/api/v1/getInflightBundleStatuses")
953 .json(&payload)
954 .send()
955 .await?
956 .json::<InflightRpcResponse>()
957 .await?
958 .result)
959}
960
961pub fn get_ip_list() -> anyhow::Result<Vec<IpAddr>> {
963 Ok(get_if_addrs()?
964 .into_iter()
965 .filter(|v| !v.is_loopback())
966 .map(|v| v.ip())
967 .collect::<Vec<_>>())
968}
969
970pub fn get_ipv4_list() -> anyhow::Result<Vec<IpAddr>> {
972 Ok(get_if_addrs()?
973 .into_iter()
974 .filter(|v| !v.is_loopback() && v.ip().is_ipv4())
975 .map(|v| v.ip())
976 .collect::<Vec<_>>())
977}
978
979pub fn get_ipv6_list() -> anyhow::Result<Vec<IpAddr>> {
981 Ok(get_if_addrs()?
982 .into_iter()
983 .filter(|v| !v.is_loopback() && v.ip().is_ipv6())
984 .map(|v| v.ip())
985 .collect::<Vec<_>>())
986}
987
988pub async fn test_ip(ip: IpAddr) -> anyhow::Result<IpAddr> {
989 reqwest::ClientBuilder::new()
990 .timeout(Duration::from_secs(3))
991 .local_address(ip)
992 .build()?
993 .get("https://apple.com")
994 .send()
995 .await?;
996
997 Ok(ip)
998}
999
1000pub async fn test_all_ip() -> Vec<anyhow::Result<IpAddr>> {
1001 match get_ip_list() {
1002 Ok(v) => {
1003 join_all(
1004 v.into_iter()
1005 .map(|v| async move { test_ip(v).await.context(v) }),
1006 )
1007 .await
1008 }
1009 Err(_) => Vec::new(),
1010 }
1011}
1012
1013pub async fn test_all_ipv4() -> Vec<anyhow::Result<IpAddr>> {
1014 match get_ipv4_list() {
1015 Ok(v) => {
1016 join_all(
1017 v.into_iter()
1018 .map(|v| async move { test_ip(v).await.context(v) }),
1019 )
1020 .await
1021 }
1022 Err(_) => Vec::new(),
1023 }
1024}
1025
1026pub async fn test_all_ipv6() -> Vec<anyhow::Result<IpAddr>> {
1027 match get_ipv6_list() {
1028 Ok(v) => {
1029 join_all(
1030 v.into_iter()
1031 .map(|v| async move { test_ip(v).await.context(v) }),
1032 )
1033 .await
1034 }
1035 Err(_) => Vec::new(),
1036 }
1037}
1038
1039pub fn serialize_tx(tx: impl Serialize) -> anyhow::Result<String> {
1040 Ok(BASE64_STANDARD.encode(bincode::serialize(&tx)?))
1041}
1042
1043pub fn serialize_tx_vec<T: IntoIterator<Item = impl Serialize>>(
1044 tx: T,
1045) -> anyhow::Result<Vec<String>> {
1046 tx.into_iter()
1047 .map(|tx| Ok(BASE64_STANDARD.encode(bincode::serialize(&tx)?)))
1048 .collect::<anyhow::Result<Vec<_>>>()
1049}
1050
1051pub fn serialize_tx_checked(tx: impl Serialize) -> anyhow::Result<String> {
1052 let data = bincode::serialize(&tx)?;
1053
1054 anyhow::ensure!(data.len() <= 1232);
1055
1056 Ok(BASE64_STANDARD.encode(data))
1057}
1058
1059pub fn serialize_tx_vec_checked<T: IntoIterator<Item = impl Serialize>>(
1060 tx: T,
1061) -> anyhow::Result<Vec<String>> {
1062 let mut result = Vec::new();
1063
1064 for i in tx.into_iter() {
1065 let data = bincode::serialize(&i)?;
1066
1067 anyhow::ensure!(data.len() <= 1232);
1068
1069 result.push(BASE64_STANDARD.encode(data));
1070 }
1071
1072 Ok(result)
1073}