gmsol_solana_utils/
client.rs

1use 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
17/// Add `send_and_confirm_transaction_with_config` method.
18pub trait SendAndConfirm {
19    /// Send and confirm a transaction.
20    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                            // Block hash is not found by some reason
72                            break 'sending;
73                        } else if cfg!(not(test))
74                            // Ignore sleep at last step.
75                            && status_retry < GET_STATUS_RETRIES
76                        {
77                            // Retry twice a second
78                            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}