jup_sdk/
monitor.rs

1use crate::types::JupiterError;
2use solana_client::rpc_config::RpcTransactionConfig;
3use solana_commitment_config::CommitmentConfig;
4use solana_network_sdk::Solana;
5use solana_sdk::signature::Signature;
6use std::str::FromStr;
7use std::time::Duration;
8use tokio::time;
9
10/// Configuration for transaction monitoring
11#[derive(Debug, Clone)]
12pub struct TransactionMonitorConfig {
13    pub timeout: Duration,
14    pub poll_interval: Duration,
15    pub commitment: CommitmentConfig,
16    pub confirmations_required: u8,
17}
18
19impl Default for TransactionMonitorConfig {
20    fn default() -> Self {
21        Self {
22            timeout: Duration::from_secs(60),
23            poll_interval: Duration::from_secs(2),
24            commitment: CommitmentConfig::confirmed(),
25            confirmations_required: 1,
26        }
27    }
28}
29
30/// Transaction status
31#[derive(Debug, Clone, PartialEq)]
32pub enum TransactionStatus {
33    Pending,
34    Confirmed,
35    Finalized,
36    Failed,
37    Timeout,
38}
39
40/// Transaction monitoring result
41#[derive(Debug, Clone)]
42pub struct TransactionMonitorResult {
43    pub signature: String,
44    pub status: TransactionStatus,
45    pub slot: u64,
46    pub block_time: Option<i64>,
47    pub confirmations: Option<u8>,
48    pub logs: Vec<String>,
49    pub error: Option<String>,
50}
51
52/// Transaction monitor for tracking Solana transaction status
53pub struct Monitor;
54
55impl Monitor {
56    /// Monitors a transaction until it reaches final state or timeout
57    ///
58    /// # Params
59    /// signature - Transaction signature string
60    /// solana - Solana client instance
61    /// config - Optional monitoring configuration
62    ///
63    /// # Example
64    /// ```rust
65    /// use solana_network_sdk::Solana;
66    /// use monitor::Monitor;
67    /// use std::time::Duration;
68    ///
69    /// async fn example() -> Result<(), Box<dyn std::error::Error>> {
70    /// let solana = Solana::new(Mode::MAIN);
71    /// let monitor = Monitor;
72    /// let signature = "........";
73    ///
74    /// let result = monitor.monitor_transaction_status(signature, &solana, None).await?;
75    /// println!("Transaction status: {:?}", result.status);
76    /// Ok(())
77    /// }
78    /// ```
79    pub async fn monitor_transaction_status(
80        &self,
81        signature: &str,
82        solana: &Solana,
83        config: Option<TransactionMonitorConfig>,
84    ) -> Result<TransactionMonitorResult, JupiterError> {
85        let config = config.unwrap_or_default();
86        let signature = Signature::from_str(signature)
87            .map_err(|e| JupiterError::InvalidInput(e.to_string()))?;
88        let start = std::time::Instant::now();
89        while start.elapsed() < config.timeout {
90            match self
91                .check_transaction_status(&signature, solana, &config)
92                .await
93            {
94                Ok(Some(result)) => {
95                    if result.status == TransactionStatus::Confirmed
96                        || result.status == TransactionStatus::Finalized
97                    {
98                        return Ok(result);
99                    } else if result.status == TransactionStatus::Failed {
100                        return Ok(result);
101                    }
102                    // Continue to wait for confirmation
103                }
104                Ok(None) => {
105                    // The transaction has not yet been seen online; please continue to wait.
106                }
107                Err(e) => {
108                    // Log the error but continue to retry.
109                    eprintln!("Error checking transaction status: {}", e);
110                }
111            }
112            time::sleep(config.poll_interval).await;
113        }
114        // timeout
115        Ok(TransactionMonitorResult {
116            signature: signature.to_string(),
117            status: TransactionStatus::Timeout,
118            slot: 0,
119            block_time: None,
120            confirmations: None,
121            logs: Vec::new(),
122            error: Some("Transaction monitoring timeout".to_string()),
123        })
124    }
125
126    /// Check the status of a single transaction
127    async fn check_transaction_status(
128        &self,
129        signature: &Signature,
130        solana: &Solana,
131        config: &TransactionMonitorConfig,
132    ) -> Result<Option<TransactionMonitorResult>, JupiterError> {
133        let statuses = solana
134            .client
135            .clone()
136            .ok_or(JupiterError::Error("solana client error".to_string()))?
137            .get_signature_statuses(&[*signature])
138            .await
139            .map_err(|e| JupiterError::NetworkError(e.to_string()))?;
140        if let Some(status) = statuses.value.get(0).and_then(|s| s.as_ref()) {
141            let slot = status.slot;
142            // get transcation lgos
143            let logs = self
144                .get_transaction_logs(signature, solana)
145                .await
146                .map_err(|e| JupiterError::Error(format!("get transcation logs error:{:?}", e)))?
147                .unwrap();
148            // Determine transaction status
149            let transaction_status = if status.err.is_some() {
150                TransactionStatus::Failed
151            } else if status.confirmations.is_none() {
152                // No confirmation number indicates final confirmation.
153                TransactionStatus::Finalized
154            } else if status
155                .confirmations
156                .map(|c| c >= config.confirmations_required.into())
157                .unwrap_or(false)
158            {
159                TransactionStatus::Confirmed
160            } else {
161                TransactionStatus::Pending
162            };
163            // get block time
164            let block_time = if slot > 0 {
165                solana
166                    .client
167                    .clone()
168                    .ok_or(JupiterError::Error("get block time error".to_string()))?
169                    .get_block_time(slot)
170                    .await
171                    .map_err(|e| JupiterError::Error(format!("get block time error:{:?}", e)))?
172            } else {
173                0
174            };
175            let result = TransactionMonitorResult {
176                signature: signature.to_string(),
177                status: transaction_status,
178                slot,
179                block_time: Some(block_time),
180                confirmations: status.confirmations.map(|c| c as u8),
181                logs: logs,
182                error: status.err.clone().map(|e| e.to_string()),
183            };
184
185            return Ok(Some(result));
186        }
187        if let Ok(Some(result)) = self.check_via_transaction(signature, solana, config).await {
188            return Ok(Some(result));
189        }
190        Ok(None)
191    }
192
193    /// Check the transaction status using get_transaction
194    async fn check_via_transaction(
195        &self,
196        signature: &Signature,
197        solana: &Solana,
198        config: &TransactionMonitorConfig,
199    ) -> Result<Option<TransactionMonitorResult>, JupiterError> {
200        let transaction_config = RpcTransactionConfig {
201            encoding: None,
202            commitment: Some(config.commitment),
203            max_supported_transaction_version: Some(0),
204        };
205        match solana
206            .client
207            .clone()
208            .ok_or(JupiterError::Error("get block time error".to_string()))?
209            .get_transaction_with_config(signature, transaction_config)
210            .await
211        {
212            Ok(transaction) => {
213                let slot = transaction.slot;
214                let block_time = transaction.block_time;
215                let logs = transaction
216                    .transaction
217                    .meta
218                    .and_then(|meta| match meta.log_messages {
219                        solana_transaction_status::option_serializer::OptionSerializer::Some(
220                            logs,
221                        ) => Some(logs),
222                        _ => None,
223                    })
224                    .unwrap_or_default();
225                let result = TransactionMonitorResult {
226                    signature: signature.to_string(),
227                    status: TransactionStatus::Confirmed, // 如果能获取到交易,认为是已确认
228                    slot,
229                    block_time,
230                    confirmations: Some(config.confirmations_required),
231                    logs,
232                    error: None,
233                };
234                Ok(Some(result))
235            }
236            Err(_) => Ok(None),
237        }
238    }
239
240    /// get transca
241    async fn get_transaction_logs(
242        &self,
243        signature: &Signature,
244        solana: &Solana,
245    ) -> Result<Option<Vec<String>>, JupiterError> {
246        let transaction_config = RpcTransactionConfig {
247            encoding: None,
248            commitment: None,
249            max_supported_transaction_version: Some(0),
250        };
251        match solana
252            .client
253            .clone()
254            .ok_or(JupiterError::Error("solana client error".to_string()))?
255            .get_transaction_with_config(signature, transaction_config)
256            .await
257        {
258            Ok(transaction) => {
259                Ok(transaction
260                    .transaction
261                    .meta
262                    .and_then(|meta| match meta.log_messages {
263                        solana_transaction_status::option_serializer::OptionSerializer::Some(
264                            logs,
265                        ) => Some(logs),
266                        _ => None,
267                    }))
268            }
269            Err(_) => Err(JupiterError::Error(
270                "transaction does not exist".to_string(),
271            )),
272        }
273    }
274
275    /// Monitors multiple transactions concurrently
276    ///
277    /// # Params
278    /// signatures - Slice of transaction signature strings
279    /// solana - Solana client instance
280    /// config - Optional monitoring configuration
281    ///
282    /// # Example
283    /// ```rust
284    /// use solana_network_sdk::Solana;
285    /// use monitor::Monitor;
286    ///
287    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
288    /// let solana = Solana::new(Mode::MAIN);
289    /// let monitor = Monitor;
290    /// let signatures = vec![
291    ///     "...".to_string(),
292    ///     ".....".to_string(),
293    /// ];
294    ///
295    /// let results = monitor.monitor_transactions_batch(&signatures, &solana, None).await?;
296    /// for result in results {
297    ///     println!("Signature: {}, Status: {:?}", result.signature, result.status);
298    /// }
299    /// # Ok(())
300    /// # }
301    /// ```
302    pub async fn monitor_transactions_batch(
303        &self,
304        signatures: &[String],
305        solana: &Solana,
306        config: Option<TransactionMonitorConfig>,
307    ) -> Result<Vec<TransactionMonitorResult>, JupiterError> {
308        let mut results = Vec::new();
309        let config = config.unwrap_or_default();
310        for signature in signatures {
311            match self
312                .monitor_transaction_status(signature, solana, Some(config.clone()))
313                .await
314            {
315                Ok(result) => results.push(result),
316                Err(e) => {
317                    results.push(TransactionMonitorResult {
318                        signature: signature.clone(),
319                        status: TransactionStatus::Failed,
320                        slot: 0,
321                        block_time: None,
322                        confirmations: None,
323                        logs: Vec::new(),
324                        error: Some(e.to_string()),
325                    });
326                }
327            }
328        }
329        Ok(results)
330    }
331}