jito_sdk_rust/
lib.rs

1use anyhow::{anyhow, Result};
2use rand::seq::SliceRandom;
3use reqwest::Client;
4use serde_json::{json, Value};
5use std::fmt;
6use tracing::{debug, trace};
7
8pub struct JitoJsonRpcSDK {
9    base_url: String,
10    uuid: Option<String>,
11    client: Client,
12}
13
14#[derive(Debug)]
15pub struct PrettyJsonValue(pub Value);
16
17impl fmt::Display for PrettyJsonValue {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(f, "{}", serde_json::to_string_pretty(&self.0).unwrap())
20    }
21}
22
23impl From<Value> for PrettyJsonValue {
24    fn from(value: Value) -> Self {
25        PrettyJsonValue(value)
26    }
27}
28
29impl JitoJsonRpcSDK {
30    pub fn new(base_url: &str, uuid: Option<String>) -> Self {
31        Self {
32            base_url: base_url.to_string(),
33            uuid,
34            client: Client::new(),
35        }
36    }
37
38    async fn send_request(
39        &self,
40        endpoint: &str,
41        method: &str,
42        params: Option<Value>,
43    ) -> Result<Value, reqwest::Error> {
44        let url = format!("{}{}", self.base_url, endpoint);
45
46        let data = json!({
47            "jsonrpc": "2.0",
48            "id": 1,
49            "method": method,
50            "params": params.unwrap_or(json!([]))
51        });
52
53        trace!("Sending request to: {}", url);
54        trace!(
55            "Request body: {}",
56            serde_json::to_string_pretty(&data).unwrap()
57        );
58
59        let response = self
60            .client
61            .post(&url)
62            .header("Content-Type", "application/json")
63            .json(&data)
64            .send()
65            .await?;
66
67        let status = response.status();
68        debug!("Response status: {}", status);
69
70        let body = response.json::<Value>().await?;
71        trace!(
72            "Response body: {}",
73            serde_json::to_string_pretty(&body).unwrap()
74        );
75
76        Ok(body)
77    }
78
79    pub async fn get_tip_accounts(&self) -> Result<Value, reqwest::Error> {
80        let endpoint = if let Some(uuid) = &self.uuid {
81            format!("/bundles?uuid={}", uuid)
82        } else {
83            "/bundles".to_string()
84        };
85
86        self.send_request(&endpoint, "getTipAccounts", None).await
87    }
88
89    // Get a random tip account
90    pub async fn get_random_tip_account(&self) -> Result<String> {
91        let tip_accounts_response = self.get_tip_accounts().await?;
92
93        let tip_accounts = tip_accounts_response["result"]
94            .as_array()
95            .ok_or_else(|| anyhow!("Failed to parse tip accounts as array"))?;
96
97        if tip_accounts.is_empty() {
98            return Err(anyhow!("No tip accounts available"));
99        }
100
101        let random_account = tip_accounts
102            .choose(&mut rand::thread_rng())
103            .ok_or_else(|| anyhow!("Failed to choose random tip account"))?;
104
105        random_account
106            .as_str()
107            .ok_or_else(|| anyhow!("Failed to parse tip account as string"))
108            .map(String::from)
109    }
110
111    pub async fn get_bundle_statuses(&self, bundle_uuids: Vec<String>) -> Result<Value> {
112        let endpoint = if let Some(uuid) = &self.uuid {
113            format!("/getBundleStatuses?uuid={}", uuid)
114        } else {
115            "/getBundleStatuses".to_string()
116        };
117
118        // Construct the params as a list within a list
119        let params = json!([bundle_uuids]);
120
121        self.send_request(&endpoint, "getBundleStatuses", Some(params))
122            .await
123            .map_err(|e| anyhow!("Request error: {}", e))
124    }
125
126    pub async fn send_bundle(&self, params: Option<Value>, uuid: Option<&str>) -> Result<Value, anyhow::Error> {
127        let mut endpoint = "/bundles".to_string();
128        
129        if let Some(uuid) = uuid {
130            endpoint = format!("{}?uuid={}", endpoint, uuid);
131        }
132    
133        // Create the parameters for the request
134        let request_params = match params {
135            // If params is already in the correct format [transactions, {encoding: "base64"}]
136            Some(ref value) if value.is_array() && value.as_array().unwrap().len() == 2 => {
137                // Use it as is
138                value.clone()
139            },
140            Some(Value::Array(transactions)) => {
141                // Validate transactions
142                if transactions.is_empty() {
143                    return Err(anyhow!("Bundle must contain at least one transaction"));
144                }
145                if transactions.len() > 5 {
146                    return Err(anyhow!("Bundle can contain at most 5 transactions"));
147                }
148                
149                json!([
150                    transactions,
151                    {
152                        "encoding": "base64"
153                    }
154                ])
155            },
156            _ => return Err(anyhow!("Invalid bundle format: expected an array of transactions")),
157        };
158    
159        self.send_request(&endpoint, "sendBundle", Some(request_params))
160            .await
161            .map_err(|e| anyhow!("Request error: {}", e))
162    }
163
164    pub async fn send_txn(&self, params: Option<Value>, bundle_only: bool) -> Result<Value, reqwest::Error> {
165        let mut query_params = Vec::new();
166
167        if bundle_only {
168            query_params.push("bundleOnly=true".to_string());
169        }
170
171        let endpoint = if query_params.is_empty() {
172            "/transactions".to_string()
173        } else {
174            format!("/transactions?{}", query_params.join("&"))
175        };
176
177        let params = match params {
178            Some(Value::Object(map)) => {
179                let tx = map.get("tx").and_then(Value::as_str).unwrap_or_default();
180                let skip_preflight = map.get("skipPreflight").and_then(Value::as_bool).unwrap_or(false);
181                json!([
182                    tx,
183                    {
184                        "encoding": "base64",
185                        "skipPreflight": skip_preflight
186                    }
187                ])
188            },
189            _ => json!([]),
190        };
191
192        self.send_request(&endpoint, "sendTransaction", Some(params)).await
193    }
194
195    pub async fn get_in_flight_bundle_statuses(&self, bundle_uuids: Vec<String>) -> Result<Value> {
196        let endpoint = if let Some(uuid) = &self.uuid {
197            format!("/getInflightBundleStatuses?uuid={}", uuid)
198        } else {
199            "/getInflightBundleStatuses".to_string()
200        };
201
202        let params = json!([bundle_uuids]);
203
204        self.send_request(&endpoint, "getInflightBundleStatuses", Some(params))
205            .await
206            .map_err(|e| anyhow!("Request error: {}", e))
207    }
208
209    // Helper method 
210    pub fn prettify(value: Value) -> PrettyJsonValue {
211        PrettyJsonValue(value)
212    }
213}