solana_trader_client_rust/provider/grpc/
mod.rs1pub mod quote;
2pub mod stream;
3pub mod swap;
4
5use anyhow::Result;
6use rustls::crypto::ring::default_provider;
7use rustls::crypto::CryptoProvider;
8use solana_sdk::pubkey::Pubkey;
9use solana_trader_proto::api::{self, GetServerTimeRequest, PostSubmitBatchRequest, PostSubmitPaladinRequest, TransactionMessageV2};
10use std::collections::HashMap;
11use tonic::service::Interceptor;
12use tonic::transport::ClientTlsConfig;
13use tonic::{
14 metadata::MetadataValue, service::interceptor::InterceptedService, transport::Channel, Request,
15};
16use crate::provider::utils::timestamp;
17
18use crate::common::signing::{sign_transaction, SubmitParams};
19use crate::common::{get_base_url_from_env, grpc_endpoint, is_submit_only_endpoint, BaseConfig};
20use solana_sdk::signature::Keypair;
21use solana_trader_proto::api::{
22 GetRecentBlockHashRequestV2, PostSubmitRequest, TransactionMessage, PostSubmitSnipeRequest
23};
24
25use super::utils::IntoTransactionMessage;
26
27#[derive(Clone)]
28struct AuthInterceptor {
29 headers: HashMap<&'static str, String>,
30 enabled: bool,
31}
32
33impl AuthInterceptor {
34 fn new(auth_header: String, enabled: bool) -> Self {
35 let mut headers = HashMap::new();
36 headers.insert("authorization", auth_header);
37 headers.insert("x-sdk", "rust-client".to_string());
38 headers.insert("x-sdk-version", env!("CARGO_PKG_VERSION").to_string());
39
40 Self { headers, enabled }
41 }
42}
43
44impl Interceptor for AuthInterceptor {
45 fn call(
46 &mut self,
47 mut request: tonic::Request<()>,
48 ) -> std::result::Result<tonic::Request<()>, tonic::Status> {
49 if self.enabled {
50 for (key, value) in &self.headers {
51 request.metadata_mut().insert(
52 *key,
53 MetadataValue::try_from(value)
54 .map_err(|e| tonic::Status::internal(e.to_string()))?,
55 );
56 }
57 }
58 Ok(request)
59 }
60}
61
62#[derive(Debug)]
63pub struct GrpcClient {
64 client: api::api_client::ApiClient<InterceptedService<Channel, AuthInterceptor>>,
65 keypair: Option<Keypair>,
66 pub public_key: Option<Pubkey>,
67}
68
69impl GrpcClient {
70 pub fn get_keypair(&self) -> Result<&Keypair> {
71 Ok(self.keypair.as_ref().unwrap())
72 }
73
74 pub async fn new(endpoint: Option<String>) -> Result<Self> {
75 let base = BaseConfig::try_from_env()?;
76 let (default_base_url, secure) = get_base_url_from_env();
77 let final_base_url = endpoint.unwrap_or(default_base_url);
78 let endpoint = grpc_endpoint(&final_base_url, secure);
79
80 is_submit_only_endpoint(&final_base_url);
81
82 if CryptoProvider::get_default().is_none() {
83 default_provider()
84 .install_default()
85 .map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e))?;
86 }
87
88 let channel = Channel::from_shared(endpoint.clone())
89 .map_err(|e| anyhow::anyhow!("Invalid URI: {}", e))?
90 .tls_config(ClientTlsConfig::new().with_webpki_roots())
91 .map_err(|e| anyhow::anyhow!("TLS config error: {}", e))?
92 .connect()
93 .await
94 .map_err(|e| anyhow::anyhow!("Connection error: {}", e))?;
95
96 let interceptor = AuthInterceptor::new(base.auth_header, true);
97 let client = api::api_client::ApiClient::with_interceptor(channel, interceptor);
98
99 Ok(Self {
100 client,
101 public_key: base.public_key,
102 keypair: base.keypair,
103 })
104 }
105
106 pub async fn sign_and_submit<T: IntoTransactionMessage + Clone>(
107 &mut self,
108 txs: Vec<T>,
109 submit_opts: SubmitParams,
110 use_bundle: bool,
111 ) -> Result<Vec<String>> {
112 let block_hash = self
113 .client
114 .get_recent_block_hash_v2(GetRecentBlockHashRequestV2 { offset: 0 })
115 .await?
116 .into_inner()
117 .block_hash;
118
119 let keypair = self.get_keypair()?;
120
121 if txs.len() == 1 {
122 let signed_tx = sign_transaction(&txs[0], keypair, block_hash).await?;
123
124 let req = PostSubmitRequest {
125 transaction: Some(TransactionMessage {
126 content: signed_tx.content,
127 is_cleanup: signed_tx.is_cleanup,
128 }),
129 skip_pre_flight: submit_opts.skip_pre_flight,
130 front_running_protection: Some(submit_opts.front_running_protection),
131 use_staked_rp_cs: Some(submit_opts.use_staked_rpcs),
132 fast_best_effort: Some(submit_opts.fast_best_effort),
133 tip: None,
134 allow_back_run: submit_opts.allow_back_run,
135 revenue_address: submit_opts.revenue_address,
136 sniping: Some(false),
137 timestamp: timestamp(),
138 submit_protection: None
139 };
140
141 let signature = self
142 .client
143 .post_submit_v2(req)
144 .await?
145 .into_inner()
146 .signature;
147
148 return Ok(vec![signature]);
149 }
150
151 let mut entries = Vec::with_capacity(txs.len());
152 for tx in txs {
153 let signed_tx = sign_transaction(&tx, keypair, block_hash.clone()).await?;
154
155 let entry = api::PostSubmitRequestEntry {
156 transaction: Some(TransactionMessage {
157 content: signed_tx.content,
158 is_cleanup: signed_tx.is_cleanup,
159 }),
160 skip_pre_flight: submit_opts.skip_pre_flight,
161 };
162 entries.push(entry);
163 }
164
165 let batch_request = api::PostSubmitBatchRequest {
166 entries,
167 use_bundle: Some(use_bundle),
168 submit_strategy: submit_opts.submit_strategy.into(),
169 front_running_protection: Some(submit_opts.front_running_protection),
170 timestamp: timestamp(),
171 submit_protection: None
172 };
173
174 let response = self
175 .client
176 .post_submit_batch_v2(batch_request)
177 .await?
178 .into_inner();
179
180 let signatures = response
181 .transactions
182 .into_iter()
183 .filter(|entry| entry.submitted)
184 .map(|entry| entry.signature)
185 .collect();
186
187 Ok(signatures)
188 }
189
190 pub async fn sign_and_submit_snipe<T: IntoTransactionMessage + Clone>(
191 &mut self,
192 txs: Vec<T>,
193 use_staked_rpcs: bool,
194 ) -> Result<Vec<String>> {
195 let block_hash = self
196 .client
197 .get_recent_block_hash_v2(GetRecentBlockHashRequestV2 { offset: 0 })
198 .await?
199 .into_inner()
200 .block_hash;
201
202 let keypair = self.get_keypair()?;
203
204 let mut entries = Vec::with_capacity(txs.len());
205 for tx in txs {
206 let signed_tx = sign_transaction(&tx, keypair, block_hash.clone()).await?;
207
208 let entry = api::PostSubmitRequestEntry {
209 transaction: Some(TransactionMessage {
210 content: signed_tx.content,
211 is_cleanup: signed_tx.is_cleanup,
212 }),
213 skip_pre_flight: false,
214 };
215 entries.push(entry);
216 }
217
218 let snipe_request = api::PostSubmitSnipeRequest {
219 entries,
220 use_staked_rp_cs: Some(use_staked_rpcs),
221 timestamp: timestamp()
222 };
223
224 let response = self
225 .client
226 .post_submit_snipe_v2(snipe_request)
227 .await?
228 .into_inner();
229
230 let signatures = response
231 .transactions
232 .into_iter()
233 .filter(|entry| entry.submitted)
234 .map(|entry| entry.signature)
235 .collect();
236
237 Ok(signatures)
238 }
239
240 pub async fn sign_and_submit_paladin<T: IntoTransactionMessage + Clone>(
241 &mut self,
242 tx: T,
243 revert_protection: bool,
244 ) -> Result<String> {
245 let block_hash = self
246 .client
247 .get_recent_block_hash_v2(GetRecentBlockHashRequestV2 { offset: 0 })
248 .await?
249 .into_inner()
250 .block_hash;
251
252 let keypair = self.get_keypair()?;
253 let signed_tx = sign_transaction(&tx, keypair, block_hash).await?;
254
255 let paladin_request = api::PostSubmitPaladinRequest {
256 transaction: Some(TransactionMessageV2 {
257 content: signed_tx.content,
258 }),
259 revert_protection: Some(revert_protection),
260 timestamp: timestamp()
261 };
262
263 let signature = self
264 .client
265 .post_submit_paladin_v2(paladin_request)
266 .await?
267 .into_inner()
268 .signature;
269
270 Ok(signature)
271 }
272
273 pub async fn get_transaction(
274 &mut self,
275 request: &api::GetTransactionRequest,
276 ) -> Result<api::GetTransactionResponse> {
277 let response = self
278 .client
279 .get_transaction(Request::new(request.clone()))
280 .await
281 .map_err(|e| anyhow::anyhow!("GetTransactionResponse error: {}", e))?;
282
283 Ok(response.into_inner())
284 }
285
286 pub async fn get_recent_block_hash(
287 &mut self,
288 request: &api::GetRecentBlockHashRequest,
289 ) -> Result<api::GetRecentBlockHashResponse> {
290 let response: tonic::Response<api::GetRecentBlockHashResponse> = self
291 .client
292 .get_recent_block_hash(Request::new(*request))
293 .await
294 .map_err(|e| anyhow::anyhow!("GetRecentBlockHash error: {}", e))?;
295
296 Ok(response.into_inner())
297 }
298
299 pub async fn get_server_time(
300 &mut self,
301 request: &GetServerTimeRequest,
302 ) -> Result<api::GetServerTimeResponse> {
303 let response: tonic::Response<api::GetServerTimeResponse> = self
304 .client
305 .get_server_time(Request::new(*request))
306 .await
307 .map_err(|e| anyhow::anyhow!("GetServerTime error: {}", e))?;
308
309 return Ok(response.into_inner())
310 }
311
312 pub async fn post_submit(
313 &mut self,
314 request: &PostSubmitRequest,
315 ) -> Result<api::PostSubmitResponse> {
316 let response: tonic::Response<api::PostSubmitResponse> = self
317 .client
318 .post_submit(Request::new(request.clone()))
319 .await
320 .map_err(|e| anyhow::anyhow!("PostSubmit error: {}", e))?;
321
322 return Ok(response.into_inner())
323 }
324
325 pub async fn post_submit_snipe_v2(
326 &mut self,
327 request: &PostSubmitSnipeRequest,
328 ) -> Result<api::PostSubmitSnipeResponse> {
329 let response: tonic::Response<api::PostSubmitSnipeResponse> = self
330 .client
331 .post_submit_snipe_v2(Request::new(request.clone()))
332 .await
333 .map_err(|e| anyhow::anyhow!("PostSubmitSnipeV2 error: {}", e))?;
334
335 return Ok(response.into_inner())
336 }
337
338 pub async fn post_submit_paladin_v2(
339 &mut self,
340 request: &PostSubmitPaladinRequest,
341 ) -> Result<api::PostSubmitResponse> {
342 let response: tonic::Response<api::PostSubmitResponse> = self
343 .client
344 .post_submit_paladin_v2(Request::new(request.clone()))
345 .await
346 .map_err(|e| anyhow::anyhow!("PostSubmitPaladinV2 error: {}", e))?;
347
348 return Ok(response.into_inner())
349 }
350
351 pub async fn post_submit_v2(
352 &mut self,
353 request: &PostSubmitRequest,
354 ) -> Result<api::PostSubmitResponse> {
355 let response: tonic::Response<api::PostSubmitResponse> = self
356 .client
357 .post_submit_v2(Request::new(request.clone()))
358 .await
359 .map_err(|e| anyhow::anyhow!("PostSubmitV2 error: {}", e))?;
360
361 return Ok(response.into_inner())
362 }
363
364 pub async fn post_submit_batch(
365 &mut self,
366 request: &PostSubmitBatchRequest,
367 ) -> Result<api::PostSubmitBatchResponse> {
368 let response: tonic::Response<api::PostSubmitBatchResponse> = self
369 .client
370 .post_submit_batch(Request::new(request.clone()))
371 .await
372 .map_err(|e| anyhow::anyhow!("PostSubmitBatch error: {}", e))?;
373
374 return Ok(response.into_inner())
375 }
376
377 pub async fn get_recent_block_hash_v2(
378 &mut self,
379 request: GetRecentBlockHashRequestV2,
380 ) -> Result<api::GetRecentBlockHashResponseV2> {
381 let response = self
382 .client
383 .get_recent_block_hash_v2(Request::new(request))
384 .await
385 .map_err(|e| anyhow::anyhow!("GetRecentBlockHashV2 error: {}", e))?;
386
387 Ok(response.into_inner())
388 }
389
390 pub async fn get_rate_limit(
391 &mut self,
392 request: &api::GetRateLimitRequest,
393 ) -> Result<api::GetRateLimitResponse> {
394 let response = self
395 .client
396 .get_rate_limit(Request::new(*request))
397 .await
398 .map_err(|e| anyhow::anyhow!("GetRateLimit error: {}", e))?;
399
400 Ok(response.into_inner())
401 }
402
403 pub async fn get_account_balance_v2(
404 &mut self,
405 request: &api::GetAccountBalanceRequest,
406 ) -> Result<api::GetAccountBalanceResponse> {
407 let response = self
408 .client
409 .get_account_balance_v2(Request::new(request.clone()))
410 .await
411 .map_err(|e| anyhow::anyhow!("GetAccountBalanceV2 error: {}", e))?;
412
413 Ok(response.into_inner())
414 }
415
416 pub async fn get_priority_fee(
417 &mut self,
418 project: api::Project,
419 percentile: Option<f64>,
420 ) -> Result<api::GetPriorityFeeResponse> {
421 let request = Request::new(api::GetPriorityFeeRequest {
422 project: project as i32,
423 percentile,
424 });
425
426 let response = self
427 .client
428 .get_priority_fee(request)
429 .await
430 .map_err(|e| anyhow::anyhow!("GetPriorityFee error: {}", e))?;
431
432 Ok(response.into_inner())
433 }
434
435 pub async fn get_priority_fee_by_program(
436 &mut self,
437 programs: Vec<String>,
438 ) -> Result<api::GetPriorityFeeByProgramResponse> {
439 let request = Request::new(api::GetPriorityFeeByProgramRequest { programs });
440
441 let response = self
442 .client
443 .get_priority_fee_by_program(request)
444 .await
445 .map_err(|e| anyhow::anyhow!("GetPriorityFeeByProgram error: {}", e))?;
446
447 Ok(response.into_inner())
448 }
449
450 pub async fn get_token_accounts(
451 &mut self,
452 owner_address: String,
453 ) -> Result<api::GetTokenAccountsResponse> {
454 let request = Request::new(api::GetTokenAccountsRequest { owner_address });
455
456 let response = self
457 .client
458 .get_token_accounts(request)
459 .await
460 .map_err(|e| anyhow::anyhow!("GetTokenAccounts error: {}", e))?;
461
462 Ok(response.into_inner())
463 }
464
465 pub async fn get_account_balance(
466 &mut self,
467 owner_address: String,
468 ) -> Result<api::GetAccountBalanceResponse> {
469 let request = Request::new(api::GetAccountBalanceRequest { owner_address });
470
471 let response = self
472 .client
473 .get_account_balance(request)
474 .await
475 .map_err(|e| anyhow::anyhow!("GetAccountBalance error: {}", e))?;
476
477 Ok(response.into_inner())
478 }
479}