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#[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#[derive(Debug, Clone, PartialEq)]
32pub enum TransactionStatus {
33 Pending,
34 Confirmed,
35 Finalized,
36 Failed,
37 Timeout,
38}
39
40#[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
52pub struct Monitor;
54
55impl Monitor {
56 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 }
104 Ok(None) => {
105 }
107 Err(e) => {
108 eprintln!("Error checking transaction status: {}", e);
110 }
111 }
112 time::sleep(config.poll_interval).await;
113 }
114 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 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 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 let transaction_status = if status.err.is_some() {
150 TransactionStatus::Failed
151 } else if status.confirmations.is_none() {
152 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 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 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, 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 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 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}