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::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}