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