solana_trader_client_rust/provider/grpc/
mod.rs

1pub 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}