jito_sdk_rust/
lib.rs

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