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