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