solana_trader_client_rust/provider/http/
mod.rs1pub mod quote;
2pub mod swap;
3
4use anyhow::{anyhow, Result};
5use reqwest::{
6 header::{HeaderMap, HeaderValue},
7 Client,
8};
9use serde::de::DeserializeOwned;
10use serde_json::json;
11use solana_sdk::{pubkey::Pubkey, signature::Keypair};
12use solana_trader_proto::api::{self, GetRecentBlockHashResponseV2};
13
14use crate::{
15 common::{
16 get_base_url_from_env, http_endpoint, is_submit_only_endpoint,
17 signing::{sign_transaction, SubmitParams},
18 BaseConfig,
19 },
20 provider::utils::convert_string_enums,
21};
22
23use super::utils::IntoTransactionMessage;
24
25pub struct HTTPClient {
26 client: Client,
27 base_url: String,
28 keypair: Option<Keypair>,
29 pub public_key: Option<Pubkey>,
30}
31
32impl HTTPClient {
33 pub fn get_keypair(&self) -> Result<&Keypair> {
34 Ok(self.keypair.as_ref().unwrap())
35 }
36
37 pub fn new(endpoint: Option<String>) -> Result<Self> {
38 let base = BaseConfig::try_from_env()?;
39 let (default_base_url, secure) = get_base_url_from_env();
40 let final_base_url = endpoint.unwrap_or(default_base_url);
41 let endpoint = http_endpoint(&final_base_url, secure);
42
43 is_submit_only_endpoint(&final_base_url);
44
45 let headers = Self::build_headers(&base.auth_header)?;
46 let client = Client::builder()
47 .default_headers(headers)
48 .build()
49 .map_err(|e| anyhow!("Failed to create HTTP client: {}", e))?;
50
51 Ok(Self {
52 client,
53 base_url: endpoint,
54 keypair: base.keypair,
55 public_key: base.public_key,
56 })
57 }
58
59 fn build_headers(auth_header: &str) -> Result<HeaderMap> {
60 let mut headers = HeaderMap::new();
61 headers.insert(
62 "Authorization",
63 HeaderValue::from_str(auth_header)
64 .map_err(|e| anyhow!("Invalid auth header: {}", e))?,
65 );
66 headers.insert("x-sdk", HeaderValue::from_static("rust-client"));
67 headers.insert(
68 "x-sdk-version",
69 HeaderValue::from_static(env!("CARGO_PKG_VERSION")),
70 );
71 Ok(headers)
72 }
73
74 async fn handle_response<T: DeserializeOwned>(&self, response: reqwest::Response) -> Result<T> {
75 if !response.status().is_success() {
76 let error_text = response
77 .text()
78 .await
79 .unwrap_or_else(|_| "Failed to read error response".into());
80 return Err(anyhow::anyhow!("HTTP request failed: {}", error_text));
81 }
82
83 let res = response.text().await?;
84
85 let mut value = serde_json::from_str(&res)
86 .map_err(|e| anyhow::anyhow!("Failed to parse response as JSON: {}", e))?;
87
88 convert_string_enums(&mut value);
89
90 serde_json::from_value(value)
91 .map_err(|e| anyhow::anyhow!("Failed to parse response into desired type: {}", e))
92 }
93
94 pub async fn sign_and_submit<T: IntoTransactionMessage + Clone>(
95 &self,
96 txs: Vec<T>,
97 submit_opts: SubmitParams,
98 use_bundle: bool,
99 ) -> Result<Vec<String>> {
100 let keypair = self.get_keypair()?;
101
102 let response = self
104 .client
105 .get(format!(
106 "{}/api/v2/system/blockhash?offset={}",
107 self.base_url, 0
108 ))
109 .send()
110 .await?;
111
112 let res: GetRecentBlockHashResponseV2 = self.handle_response(response).await?;
113
114 if txs.len() == 1 {
115 let signed_tx = sign_transaction(&txs[0], keypair, res.block_hash).await?;
116
117 let request_json = json!({
118 "transaction": { "content": signed_tx.content, "isCleanup": signed_tx.is_cleanup },
119 "skipPreFlight": submit_opts.skip_pre_flight,
120 "frontRunningProtection": submit_opts.front_running_protection,
121 "useStakedRPCs": submit_opts.use_staked_rpcs,
122 "fastBestEffort": submit_opts.fast_best_effort
123 });
124
125 let response = self
126 .client
127 .post(format!("{}/api/v2/submit", self.base_url))
128 .json(&request_json)
129 .send()
130 .await?;
131
132 let result: serde_json::Value = self.handle_response(response).await?;
133 return Ok(vec![result
134 .get("signature")
135 .and_then(|s| s.as_str())
136 .map(String::from)
137 .ok_or_else(|| anyhow!("Missing signature in response"))?]);
138 }
139
140 let mut entries = Vec::with_capacity(txs.len());
141 for tx in txs {
142 let signed_tx = sign_transaction(&tx, keypair, res.block_hash.clone()).await?;
143 entries.push(json!({
144 "transaction": {
145 "content": signed_tx.content,
146 "isCleanup": signed_tx.is_cleanup
147 },
148 "skipPreFlight": submit_opts.skip_pre_flight,
149 "frontRunningProtection": submit_opts.front_running_protection,
150 "useStakedRPCs": submit_opts.use_staked_rpcs,
151 "fastBestEffort": submit_opts.fast_best_effort
152 }));
153 }
154
155 let request_json = json!({
156 "entries": entries,
157 "useBundle": use_bundle,
158 "submitStrategy": submit_opts.submit_strategy
159 });
160
161 let response = self
162 .client
163 .post(format!("{}/api/v2/submit/batch", self.base_url))
164 .json(&request_json)
165 .send()
166 .await?;
167
168 let result: serde_json::Value = self.handle_response(response).await?;
169
170 let signatures = result["transactions"]
171 .as_array()
172 .ok_or_else(|| anyhow!("Invalid response format"))?
173 .iter()
174 .filter(|entry| entry["submitted"].as_bool().unwrap_or(false))
175 .filter_map(|entry| entry["signature"].as_str().map(String::from))
176 .collect();
177
178 Ok(signatures)
179 }
180
181 pub async fn sign_and_submit_snipe<T: IntoTransactionMessage + Clone>(
182 &self,
183 txs: Vec<T>,
184 use_staked_rpcs: bool,
185 ) -> Result<Vec<String>> {
186 let keypair = self.get_keypair()?;
187
188 let response = self
190 .client
191 .get(format!(
192 "{}/api/v2/system/blockhash?offset={}",
193 self.base_url, 0
194 ))
195 .send()
196 .await?;
197
198 let res: GetRecentBlockHashResponseV2 = self.handle_response(response).await?;
199
200 let mut entries = Vec::with_capacity(txs.len());
202 for tx in txs {
203 let signed_tx = sign_transaction(&tx, keypair, res.block_hash.clone()).await?;
204 entries.push(json!({
205 "transaction": {
206 "content": signed_tx.content,
207 "isCleanup": signed_tx.is_cleanup
208 },
209 "skipPreFlight": false
210 }));
211 }
212
213 let request_json = json!({
214 "entries": entries,
215 "useStakedRPCs": use_staked_rpcs
216 });
217
218 let response = self
219 .client
220 .post(format!("{}/api/v2/submit-snipe", self.base_url))
221 .json(&request_json)
222 .send()
223 .await?;
224
225 let result: serde_json::Value = self.handle_response(response).await?;
226
227 let signatures = result["transactions"]
228 .as_array()
229 .ok_or_else(|| anyhow!("Invalid response format"))?
230 .iter()
231 .filter(|entry| entry["submitted"].as_bool().unwrap_or(false))
232 .filter_map(|entry| entry["signature"].as_str().map(String::from))
233 .collect();
234
235 Ok(signatures)
236 }
237
238 pub async fn sign_and_submit_paladin<T: IntoTransactionMessage + Clone>(
239 &self,
240 tx: T,
241 ) -> Result<String> {
242 let response = self
243 .client
244 .get(format!(
245 "{}/api/v2/system/blockhash?offset={}",
246 self.base_url, 0
247 ))
248 .send()
249 .await?;
250
251 let res: GetRecentBlockHashResponseV2 = self.handle_response(response).await?;
252 let keypair = self.get_keypair()?;
253 let signed_tx = sign_transaction(&tx, keypair, res.block_hash).await?;
254
255 let request_json = json!({
256 "transaction": {
257 "content": signed_tx.content,
258 "isCleanup": signed_tx.is_cleanup
259 }
260 });
261
262 let response = self
263 .client
264 .post(format!("{}/api/v2/submit-paladin", self.base_url))
265 .json(&request_json)
266 .send()
267 .await?;
268
269 let result: serde_json::Value = self.handle_response(response).await?;
270 let signature = result
271 .get("signature")
272 .and_then(|s| s.as_str())
273 .map(String::from)
274 .ok_or_else(|| anyhow!("Missing signature in response"))?;
275
276 Ok(signature)
277 }
278
279 pub async fn get_transaction(
280 &self,
281 request: &api::GetTransactionRequest,
282 ) -> anyhow::Result<api::GetTransactionResponse> {
283 let url = format!(
284 "{}/api/v2/transaction?signature={}",
285 self.base_url, request.signature
286 );
287
288 println!("{}", url);
289
290 let response = self
291 .client
292 .get(&url)
293 .send()
294 .await
295 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
296
297 let response_text = response.text().await?;
298
299 println!("{}", response_text);
300
301 let response = self
310 .client
311 .get(&url)
312 .send()
313 .await
314 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
315
316 self.handle_response(response).await
317 }
318 pub async fn get_recent_block_hash(&self) -> anyhow::Result<api::GetRecentBlockHashResponse> {
319 let url = format!("{}/api/v1/system/blockhash", self.base_url);
320
321 println!("{}", url);
322
323 let response = self
324 .client
325 .get(&url)
326 .send()
327 .await
328 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
329
330 self.handle_response(response).await
331 }
332
333 pub async fn get_recent_block_hash_v2(
334 &self,
335 request: &api::GetRecentBlockHashRequestV2,
336 ) -> anyhow::Result<api::GetRecentBlockHashResponseV2> {
337 let url = format!(
338 "{}/api/v2/system/blockhash?offset={}",
339 self.base_url, request.offset
340 );
341
342 println!("{}", url);
343
344 let response = self
345 .client
346 .get(&url)
347 .send()
348 .await
349 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
350
351 self.handle_response(response).await
352 }
353
354 pub async fn get_rate_limit(&self) -> anyhow::Result<api::GetRateLimitResponse> {
355 let url = format!("{}/api/v2/rate-limit", self.base_url);
356
357 println!("{}", url);
358
359 let response = self
360 .client
361 .get(&url)
362 .send()
363 .await
364 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
365
366 self.handle_response(response).await
367 }
368
369 pub async fn get_account_balance_v2(
370 &self,
371 request: api::GetAccountBalanceRequest,
372 ) -> anyhow::Result<api::GetAccountBalanceResponse> {
373 println!("here1");
374
375 let url = format!(
376 "{}/api/v2/balance?ownerAddress={}",
377 self.base_url, request.owner_address
378 );
379
380 let response = self
381 .client
382 .get(&url)
383 .send()
384 .await
385 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
386
387 self.handle_response(response).await
388 }
389
390 pub async fn get_priority_fee(
391 &self,
392 project: api::Project,
393 percentile: Option<f64>,
394 ) -> Result<api::GetPriorityFeeResponse> {
395 let mut url = format!(
396 "{}/api/v2/system/priority-fee?project={}",
397 self.base_url, project as i32
398 );
399 if let Some(p) = percentile {
400 url = format!(
401 "{}/api/v2/system/priority-fee?project={}&percentile={}",
402 self.base_url, project as i32, p
403 );
404 }
405
406 let response = self
407 .client
408 .get(&url)
409 .send()
410 .await
411 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
412
413 self.handle_response(response).await
414 }
415
416 pub async fn get_priority_fee_by_program(
417 &self,
418 programs: Vec<String>,
419 ) -> Result<api::GetPriorityFeeByProgramResponse> {
420 let url = format!(
421 "{}/api/v2/system/priority-fee-by-program?programs={}",
422 self.base_url,
423 programs.join("&programs=")
424 );
425
426 let response: reqwest::Response = self
427 .client
428 .get(&url)
429 .send()
430 .await
431 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
432
433 self.handle_response(response).await
434 }
435
436 pub async fn get_token_accounts(
437 &self,
438 owner_address: String,
439 ) -> Result<api::GetTokenAccountsResponse> {
440 let url = format!(
441 "{}/api/v1/account/token-accounts?ownerAddress={}",
442 self.base_url, owner_address
443 );
444
445 let response = self
446 .client
447 .get(&url)
448 .send()
449 .await
450 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
451
452 self.handle_response(response).await
453 }
454
455 pub async fn get_account_balance(
456 &self,
457 owner_address: String,
458 ) -> Result<api::GetAccountBalanceResponse> {
459 let url = format!(
460 "{}/api/v2/balance?ownerAddress={}",
461 self.base_url, owner_address
462 );
463
464 let response = self
465 .client
466 .get(&url)
467 .send()
468 .await
469 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
470
471 self.handle_response(response).await
472 }
473
474 pub async fn get_leader_schedule(
475 &self,
476 max_slots: u64,
477 ) -> Result<api::GetLeaderScheduleResponse> {
478 let url = format!(
479 "{}/api/v2/system/leader-schedule?maxSlots={}",
480 self.base_url, max_slots
481 );
482
483 let response = self
484 .client
485 .get(&url)
486 .send()
487 .await
488 .map_err(|e| anyhow!("HTTP GET request failed: {}", e))?;
489
490 self.handle_response(response).await
491 }
492}