gmsol_solana_utils/
client.rs1use std::{future::Future, time::Duration};
2
3use solana_client::{
4 client_error::ClientError as SolanaClientError,
5 nonblocking::rpc_client::RpcClient,
6 rpc_client::SerializableTransaction,
7 rpc_config::RpcSendTransactionConfig,
8 rpc_request::{RpcError, RpcRequest},
9 rpc_response::Response,
10};
11use solana_sdk::{commitment_config::CommitmentConfig, signature::Signature};
12use solana_transaction_status::TransactionStatus;
13use tokio::time::sleep;
14
15use crate::utils::WithSlot;
16
17pub trait SendAndConfirm {
19 fn send_and_confirm_transaction_with_config(
21 &self,
22 transaction: &impl SerializableTransaction,
23 config: RpcSendTransactionConfig,
24 ) -> impl Future<Output = std::result::Result<WithSlot<Signature>, SolanaClientError>>;
25}
26
27impl SendAndConfirm for RpcClient {
28 async fn send_and_confirm_transaction_with_config(
29 &self,
30 transaction: &impl SerializableTransaction,
31 config: RpcSendTransactionConfig,
32 ) -> std::result::Result<WithSlot<Signature>, SolanaClientError> {
33 const SEND_RETRIES: usize = 1;
34 const GET_STATUS_RETRIES: usize = usize::MAX;
35
36 'sending: for _ in 0..SEND_RETRIES {
37 let signature = self
38 .send_transaction_with_config(transaction, config)
39 .await?;
40
41 let recent_blockhash = if transaction.uses_durable_nonce() {
42 let (recent_blockhash, ..) = self
43 .get_latest_blockhash_with_commitment(CommitmentConfig::processed())
44 .await?;
45 recent_blockhash
46 } else {
47 *transaction.get_recent_blockhash()
48 };
49
50 for status_retry in 0..GET_STATUS_RETRIES {
51 let result: Response<Vec<Option<TransactionStatus>>> = self
52 .send(
53 RpcRequest::GetSignatureStatuses,
54 serde_json::json!([[signature.to_string()]]),
55 )
56 .await?;
57 let status = result.value[0]
58 .clone()
59 .filter(|result| result.satisfies_commitment(self.commitment()));
60
61 match status {
62 Some(status) => match status.status {
63 Ok(()) => return Ok(WithSlot::new(status.slot, signature)),
64 Err(err) => return Err(err.into()),
65 },
66 None => {
67 if !self
68 .is_blockhash_valid(&recent_blockhash, CommitmentConfig::processed())
69 .await?
70 {
71 break 'sending;
73 } else if cfg!(not(test))
74 && status_retry < GET_STATUS_RETRIES
76 {
77 sleep(Duration::from_millis(500)).await;
79 continue;
80 }
81 }
82 }
83 }
84 }
85
86 Err(RpcError::ForUser(
87 "unable to confirm transaction. \
88 This can happen in situations such as transaction expiration \
89 and insufficient fee-payer funds"
90 .to_string(),
91 )
92 .into())
93 }
94}