Skip to main content

jito_bundle/client/
status.rs

1use crate::client::jito_bundler::JitoBundler;
2use crate::constants::DEFAULT_INITIAL_CONFIRM_DELAY_SECS;
3use crate::error::JitoError;
4use crate::types::{BundleStatus, JsonRpcRequest, JsonRpcResponse};
5use serde::Deserialize;
6use solana_sdk::signature::Signature;
7use solana_transaction_status_client_types::TransactionConfirmationStatus;
8use std::str::FromStr;
9use std::time::Duration;
10
11impl JitoBundler {
12    pub async fn get_bundle_status(&self, bundle_id: &str) -> BundleStatus {
13        let endpoint = self.config.network.block_engine_url();
14        let request = JsonRpcRequest {
15            jsonrpc: "2.0",
16            id: 1,
17            method: "getBundleStatuses",
18            params: [[bundle_id]],
19        };
20
21        let response = match self.jito_post(endpoint).json(&request).send().await {
22            Ok(r) => r,
23            Err(_) => return BundleStatus::Unknown,
24        };
25
26        let status_code = response.status();
27        if status_code.as_u16() == 429 {
28            return BundleStatus::Pending;
29        }
30        if !status_code.is_success() {
31            return BundleStatus::Unknown;
32        }
33
34        let response_text = match response.text().await {
35            Ok(t) => t,
36            Err(_) => return BundleStatus::Unknown,
37        };
38
39        #[derive(Debug, Deserialize)]
40        struct BundleStatusValue {
41            confirmation_status: Option<String>,
42            slot: Option<u64>,
43            err: Option<serde_json::Value>,
44        }
45
46        #[derive(Debug, Deserialize)]
47        struct StatusResult {
48            value: Vec<BundleStatusValue>,
49        }
50
51        let parsed: JsonRpcResponse<StatusResult> = match serde_json::from_str(&response_text) {
52            Ok(p) => p,
53            Err(_) => return BundleStatus::Unknown,
54        };
55
56        if let Some(result) = parsed.result
57            && let Some(status) = result.value.first()
58        {
59            if let Some(err) = &status.err {
60                return BundleStatus::Failed {
61                    error: Some(err.to_string()),
62                };
63            }
64
65            if let Some(confirmation_status) = &status.confirmation_status
66                && (confirmation_status == "confirmed" || confirmation_status == "finalized")
67            {
68                return BundleStatus::Landed { slot: status.slot };
69            }
70
71            return BundleStatus::Pending;
72        }
73
74        BundleStatus::Unknown
75    }
76
77    pub async fn wait_for_landing_on_chain(
78        &self,
79        signatures: &[String],
80    ) -> Result<BundleStatus, JitoError> {
81        let parsed_signatures: Vec<Signature> = signatures
82            .iter()
83            .map(|s| Signature::from_str(s))
84            .collect::<Result<Vec<_>, _>>()
85            .map_err(|e| JitoError::InvalidSignature {
86                reason: e.to_string(),
87            })?;
88
89        tokio::time::sleep(Duration::from_secs(DEFAULT_INITIAL_CONFIRM_DELAY_SECS)).await;
90
91        let max_attempts = self.config.confirm_policy.max_attempts;
92        let interval_ms = self.config.confirm_policy.interval_ms;
93
94        for _attempt in 0..max_attempts {
95            if let Ok(statuses) = self
96                .rpc_client
97                .get_signature_statuses(&parsed_signatures)
98                .await
99            {
100                for (j, status) in statuses.value.iter().enumerate() {
101                    if let Some(s) = status
102                        && let Some(err) = &s.err
103                    {
104                        return Ok(BundleStatus::Failed {
105                            error: Some(format!("transaction {j} failed: {err:?}")),
106                        });
107                    }
108                }
109                let all_confirmed = statuses.value.iter().all(|status| {
110                    status.as_ref().is_some_and(|s| {
111                        s.confirmation_status.as_ref().is_some_and(|cs| {
112                            cs == &TransactionConfirmationStatus::Confirmed
113                                || cs == &TransactionConfirmationStatus::Finalized
114                        })
115                    })
116                });
117                if all_confirmed {
118                    let slot = statuses
119                        .value
120                        .first()
121                        .and_then(|s| s.as_ref().map(|s| s.slot));
122                    return Ok(BundleStatus::Landed { slot });
123                }
124            }
125            tokio::time::sleep(Duration::from_millis(interval_ms)).await;
126        }
127
128        Err(JitoError::ConfirmationTimeout {
129            attempts: max_attempts,
130        })
131    }
132
133    pub async fn wait_for_landing_via_jito(
134        &self,
135        bundle_id: &str,
136    ) -> Result<BundleStatus, JitoError> {
137        tokio::time::sleep(Duration::from_secs(DEFAULT_INITIAL_CONFIRM_DELAY_SECS)).await;
138
139        let max_attempts = self.config.confirm_policy.max_attempts;
140        let interval_ms = self.config.confirm_policy.interval_ms;
141
142        for _attempt in 0..max_attempts {
143            let status = self.get_bundle_status(bundle_id).await;
144
145            match &status {
146                BundleStatus::Landed { .. } | BundleStatus::Failed { .. } => {
147                    return Ok(status);
148                }
149                _ => {}
150            }
151
152            tokio::time::sleep(Duration::from_millis(interval_ms)).await;
153        }
154
155        Err(JitoError::ConfirmationTimeout {
156            attempts: max_attempts,
157        })
158    }
159}