1use stellar_rpc_client::GetTransactionResponse;
2use stellar_xdr::curr::{ScVal, SorobanTransactionMeta, TransactionMeta, TransactionMetaV3};
3
4use crate::SorobanHelperError;
5
6#[derive(Debug, Clone)]
8pub struct SorobanTransactionResponse {
9 pub response: GetTransactionResponse,
11}
12
13impl From<GetTransactionResponse> for SorobanTransactionResponse {
14 fn from(response: GetTransactionResponse) -> Self {
15 Self { response }
16 }
17}
18
19impl SorobanTransactionResponse {
20 pub fn new(response: GetTransactionResponse) -> Self {
22 Self { response }
23 }
24
25 pub fn get_return_value(&self) -> Result<ScVal, SorobanHelperError> {
35 let result_meta = self.response.result_meta.as_ref().ok_or_else(|| {
37 SorobanHelperError::InvalidArgument("Transaction metadata not available".to_string())
38 })?;
39
40 match result_meta {
42 TransactionMeta::V3(meta_v3) => self.extract_soroban_return_value(meta_v3),
43 _ => Err(SorobanHelperError::InvalidArgument(
44 "Transaction metadata is not in V3 format (not a Soroban transaction)".to_string(),
45 )),
46 }
47 }
48
49 pub fn get_events(&self) -> Result<Vec<stellar_xdr::curr::ContractEvent>, SorobanHelperError> {
59 let result_meta = self.response.result_meta.as_ref().ok_or_else(|| {
61 SorobanHelperError::InvalidArgument("Transaction metadata not available".to_string())
62 })?;
63
64 match result_meta {
66 TransactionMeta::V3(meta_v3) => self.extract_soroban_events(meta_v3),
67 _ => Err(SorobanHelperError::InvalidArgument(
68 "Transaction metadata is not in V3 format (not a Soroban transaction)".to_string(),
69 )),
70 }
71 }
72
73 fn extract_soroban_return_value(
75 &self,
76 meta_v3: &TransactionMetaV3,
77 ) -> Result<ScVal, SorobanHelperError> {
78 let soroban_meta = meta_v3.soroban_meta.as_ref().ok_or_else(|| {
80 SorobanHelperError::InvalidArgument("Soroban metadata not available".to_string())
81 })?;
82
83 Ok(soroban_meta.return_value.clone())
85 }
86
87 fn extract_soroban_events(
89 &self,
90 meta_v3: &TransactionMetaV3,
91 ) -> Result<Vec<stellar_xdr::curr::ContractEvent>, SorobanHelperError> {
92 let soroban_meta = meta_v3.soroban_meta.as_ref().ok_or_else(|| {
94 SorobanHelperError::InvalidArgument("Soroban metadata not available".to_string())
95 })?;
96
97 let events = soroban_meta.events.to_vec();
99 Ok(events)
100 }
101
102 pub fn get_soroban_meta(&self) -> Result<SorobanTransactionMeta, SorobanHelperError> {
112 let result_meta = self.response.result_meta.as_ref().ok_or_else(|| {
114 SorobanHelperError::InvalidArgument("Transaction metadata not available".to_string())
115 })?;
116
117 match result_meta {
119 TransactionMeta::V3(meta_v3) => {
120 let soroban_meta = meta_v3.soroban_meta.as_ref().ok_or_else(|| {
122 SorobanHelperError::InvalidArgument(
123 "Soroban metadata not available".to_string(),
124 )
125 })?;
126
127 Ok(soroban_meta.clone())
129 }
130 _ => Err(SorobanHelperError::InvalidArgument(
131 "Transaction metadata is not in V3 format (not a Soroban transaction)".to_string(),
132 )),
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use crate::mock::create_mock_contract_event;
140
141 use super::*;
142 use stellar_xdr::curr::{
143 ContractEvent, ExtensionPoint, LedgerEntryChanges, SorobanTransactionMetaExt,
144 TransactionResult, TransactionResultExt, TransactionResultResult, VecM,
145 };
146
147 #[test]
148 fn test_get_return_value_success() {
149 let response = create_mock_response(Some(ScVal::U32(42)));
151 let soroban_response = SorobanTransactionResponse::new(response);
152
153 let return_value = soroban_response.get_return_value().unwrap();
155 assert_eq!(return_value, ScVal::U32(42));
156 }
157
158 #[test]
159 fn test_get_return_value_no_meta() {
160 let response = GetTransactionResponse {
162 status: "success".to_string(),
163 envelope: None,
164 result: None,
165 result_meta: None,
166 };
167 let soroban_response = SorobanTransactionResponse::new(response);
168
169 let result = soroban_response.get_return_value();
171 assert!(result.is_err());
172 }
173
174 fn create_mock_response(return_value: Option<ScVal>) -> GetTransactionResponse {
176 let soroban_meta = SorobanTransactionMeta {
178 ext: SorobanTransactionMetaExt::V0,
179 events: VecM::default(),
180 return_value: return_value.unwrap_or(ScVal::Void),
181 diagnostic_events: VecM::default(),
182 };
183
184 let meta_v3 = TransactionMetaV3 {
186 ext: ExtensionPoint::V0,
187 tx_changes_before: LedgerEntryChanges::default(),
188 operations: VecM::default(),
189 tx_changes_after: LedgerEntryChanges::default(),
190 soroban_meta: Some(soroban_meta),
191 };
192
193 let transaction_result = TransactionResult {
194 fee_charged: 0,
195 result: TransactionResultResult::TxSuccess(VecM::default()),
196 ext: TransactionResultExt::V0,
197 };
198
199 GetTransactionResponse {
201 status: "success".to_string(),
202 envelope: None,
203 result: Some(transaction_result),
204 result_meta: Some(TransactionMeta::V3(meta_v3)),
205 }
206 }
207
208 #[test]
209 fn test_get_events_success() {
210 let event1 = create_mock_contract_event();
212 let event2 = create_mock_contract_event();
213
214 let events: VecM<ContractEvent> = vec![event1.clone(), event2.clone()].try_into().unwrap();
216
217 let response = create_mock_response_with_events(Some(ScVal::Void), events);
219 let soroban_response = SorobanTransactionResponse::new(response);
220
221 let extracted_events = soroban_response.get_events().unwrap();
223 assert_eq!(extracted_events.len(), 2);
224 }
225
226 #[test]
227 fn test_get_events_no_meta() {
228 let response = GetTransactionResponse {
230 status: "success".to_string(),
231 envelope: None,
232 result: None,
233 result_meta: None,
234 };
235 let soroban_response = SorobanTransactionResponse::new(response);
236
237 let result = soroban_response.get_events();
239 assert!(result.is_err());
240 }
241
242 #[test]
243 fn test_get_events_empty() {
244 let response = create_mock_response(Some(ScVal::Void));
246 let soroban_response = SorobanTransactionResponse::new(response);
247
248 let events = soroban_response.get_events().unwrap();
250 assert_eq!(events.len(), 0);
251 }
252
253 #[test]
254 fn test_get_soroban_meta_success() {
255 let return_value = ScVal::U32(42);
257 let response = create_mock_response(Some(return_value.clone()));
258 let soroban_response = SorobanTransactionResponse::new(response);
259
260 let soroban_meta = soroban_response.get_soroban_meta().unwrap();
262
263 assert_eq!(soroban_meta.return_value, return_value);
265 assert_eq!(soroban_meta.events.len(), 0);
266 assert_eq!(soroban_meta.diagnostic_events.len(), 0);
267 assert!(matches!(soroban_meta.ext, SorobanTransactionMetaExt::V0));
268 }
269
270 #[test]
271 fn test_get_soroban_meta_no_meta() {
272 let response = GetTransactionResponse {
274 status: "success".to_string(),
275 envelope: None,
276 result: None,
277 result_meta: None,
278 };
279 let soroban_response = SorobanTransactionResponse::new(response);
280
281 let result = soroban_response.get_soroban_meta();
283 assert!(result.is_err());
284 }
285
286 fn create_mock_response_with_events(
288 return_value: Option<ScVal>,
289 events: VecM<stellar_xdr::curr::ContractEvent>,
290 ) -> GetTransactionResponse {
291 let soroban_meta = SorobanTransactionMeta {
293 ext: SorobanTransactionMetaExt::V0,
294 events,
295 return_value: return_value.unwrap_or(ScVal::Void),
296 diagnostic_events: VecM::default(),
297 };
298
299 let meta_v3 = TransactionMetaV3 {
301 ext: ExtensionPoint::V0,
302 tx_changes_before: LedgerEntryChanges::default(),
303 operations: VecM::default(),
304 tx_changes_after: LedgerEntryChanges::default(),
305 soroban_meta: Some(soroban_meta),
306 };
307
308 let transaction_result = TransactionResult {
309 fee_charged: 0,
310 result: TransactionResultResult::TxSuccess(VecM::default()),
311 ext: TransactionResultExt::V0,
312 };
313
314 GetTransactionResponse {
316 status: "success".to_string(),
317 envelope: None,
318 result: Some(transaction_result),
319 result_meta: Some(TransactionMeta::V3(meta_v3)),
320 }
321 }
322}