1use std::collections::HashMap;
2
3use super::vm::CallType;
4use multiversx_chain_core::std::Bech32Address;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Transaction {
11 pub nonce: u64,
12 pub value: String,
13 pub receiver: Bech32Address,
14 pub sender: Bech32Address,
15 pub gas_price: u64,
16 pub gas_limit: u64,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub data: Option<String>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub signature: Option<String>,
21 #[serde(rename = "chainID")]
22 pub chain_id: String,
23 pub version: u32,
24 #[serde(skip_serializing_if = "is_zero")]
25 pub options: u32,
26}
27
28#[allow(clippy::trivially_copy_pass_by_ref)]
30fn is_zero(num: &u32) -> bool {
31 *num == 0
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct TxCostResponseData {
38 pub tx_gas_units: u64,
39 pub return_message: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub struct ResponseTxCost {
46 pub data: Option<TxCostResponseData>,
47 pub error: String,
48 pub code: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, Default)]
53#[serde(rename_all = "camelCase")]
54pub struct TransactionOnNetwork {
55 #[serde(rename = "type")]
56 pub kind: String,
57 pub hash: Option<String>,
58 pub nonce: u64,
59 pub round: u64,
60 pub epoch: u64,
61 pub value: String,
62 pub receiver: Bech32Address,
63 pub sender: Bech32Address,
64 pub gas_price: u64,
65 pub gas_limit: u64,
66 #[serde(default)]
67 pub gas_used: u64,
68 #[serde(default)]
69 pub signature: String,
70 pub source_shard: u32,
71 pub destination_shard: u32,
72 #[serde(default)]
73 pub block_nonce: u64,
74 #[serde(default)]
75 pub block_hash: String,
76 pub notarized_at_source_in_meta_nonce: Option<u64>,
77 #[serde(rename = "NotarizedAtSourceInMetaHash")]
78 pub notarized_at_source_in_meta_hash: Option<String>,
79 pub notarized_at_destination_in_meta_nonce: Option<u64>,
80 pub notarized_at_destination_in_meta_hash: Option<String>,
81 pub processing_type_on_destination: String,
82 #[serde(default)]
83 pub miniblock_type: String,
84 #[serde(default)]
85 pub miniblock_hash: String,
86 #[serde(default)]
87 pub timestamp: u64,
88 pub data: Option<String>,
89 pub status: String,
90 pub hyperblock_nonce: Option<u64>,
91 pub hyperblock_hash: Option<String>,
92 #[serde(default)]
93 pub smart_contract_results: Vec<ApiSmartContractResult>,
94 pub logs: Option<ApiLogs>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct Events {
101 pub address: Bech32Address,
102 pub identifier: String,
103 #[serde(default)]
104 pub topics: Vec<String>,
105 #[serde(default)]
106 pub data: LogData,
107}
108
109#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
110#[serde(untagged)]
111pub enum LogData {
112 #[default]
113 Empty,
114 String(String),
115 Vec(Vec<String>),
116}
117
118impl LogData {
119 pub fn for_each<F: FnMut(&String)>(&self, mut f: F) {
120 match self {
121 LogData::Empty => {}
122 LogData::String(s) => f(s),
123 LogData::Vec(v) => v.iter().for_each(f),
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(rename_all = "camelCase")]
131pub struct ApiLogs {
132 pub address: Bech32Address,
133 pub events: Vec<Events>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct ApiSmartContractResult {
139 pub hash: String,
140 pub nonce: u64,
141 pub value: u128, pub receiver: Bech32Address,
143 pub sender: Bech32Address,
144 #[serde(default)]
145 pub data: String,
146 pub prev_tx_hash: String,
147 pub original_tx_hash: String,
148 pub gas_limit: u64,
149 pub gas_price: u64,
150 pub call_type: CallType,
151 pub relayer_address: Option<String>,
152 pub relayed_value: Option<String>,
153 pub code: Option<String>,
154 pub code_metadata: Option<String>,
155 pub return_message: Option<String>,
156 pub original_sender: Option<String>,
157 pub logs: Option<ApiLogs>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct TransactionInfoData {
162 pub transaction: TransactionOnNetwork,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct TransactionInfo {
168 #[serde(default)]
169 pub error: String,
170 pub code: String,
171 pub data: Option<TransactionInfoData>,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct TransactionStatusData {
176 pub status: String,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct TransactionStatus {
182 pub error: String,
183 pub code: String,
184 pub data: Option<TransactionStatusData>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct TransactionProcessStatusData {
189 pub reason: String,
190 pub status: String,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct TransactionProcessStatus {
196 pub error: String,
197 pub code: String,
198 pub data: Option<TransactionProcessStatusData>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ArgCreateTransaction {
204 pub nonce: u64,
205 pub value: String,
206 pub rcv_addr: Bech32Address,
207 pub snd_addr: Bech32Address,
208 pub gas_price: u64,
209 pub gas_limit: u64,
210 pub data: Option<String>,
211 pub signature: String,
212 pub chain_id: String,
213 pub version: u32,
214 pub options: u32,
215 pub available_balance: String,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220pub struct SendTransactionData {
221 pub tx_hash: String,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct SendTransactionResponse {
227 pub error: String,
228 pub code: String,
229 pub data: Option<SendTransactionData>,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct SimulateGasTransactionData {
235 pub tx_gas_units: u64,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct SimulateGasTransactionResponse {
241 pub error: String,
242 pub code: String,
243 pub data: Option<SimulateGasTransactionData>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct SendTransactionsResponseData {
249 pub num_of_sent_txs: i32,
250 pub txs_hashes: HashMap<i32, String>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct SendTransactionsResponse {
256 pub error: String,
257 pub code: String,
258 pub data: Option<SendTransactionsResponseData>,
259}
260
261#[cfg(test)]
262mod test {
263 use super::*;
264
265 #[test]
266 fn parse_event_log_null_data() {
267 let data = r#"
268{
269 "address": "erd1qqqqqqqqqqqqqpgq0628nau8zydgwu96fn8ksqklzhrggkcfq33sm4vmwv",
270 "identifier": "completedTxEvent",
271 "topics": [],
272 "data": null,
273 "additionalData": null
274}
275 "#;
276
277 let event_log = serde_json::from_str::<Events>(data).unwrap();
278 assert!(event_log.topics.is_empty());
279 assert_eq!(event_log.data, LogData::Empty);
280 }
281
282 #[test]
283 fn parse_event_log_no_topics() {
284 let data = r#"
285{
286 "address": "erd1qqqqqqqqqqqqqpgq0628nau8zydgwu96fn8ksqklzhrggkcfq33sm4vmwv",
287 "identifier": "completedTxEvent",
288 "data": null,
289 "additionalData": null
290}
291 "#;
292
293 let event_log = serde_json::from_str::<Events>(data).unwrap();
294 assert!(event_log.topics.is_empty());
295 }
296
297 #[test]
298 fn parse_event_log_null_additional_data() {
299 let data = r#"
300{
301 "address": "erd1qqqqqqqqqqqqqpgq0628nau8zydgwu96fn8ksqklzhrggkcfq33sm4vmwv",
302 "identifier": "completedTxEvent",
303 "topics": [],
304 "data": "data-string",
305 "additionalData": null
306}
307 "#;
308
309 let event_log = serde_json::from_str::<Events>(data).unwrap();
310 assert_eq!(event_log.data, LogData::String("data-string".to_owned()));
311 }
312
313 #[test]
314 fn parse_event_log_with_array_data() {
315 let data = r#"
316{
317 "address": "erd1qqqqqqqqqqqqqpgq0628nau8zydgwu96fn8ksqklzhrggkcfq33sm4vmwv",
318 "identifier": "completedTxEvent",
319 "topics": [],
320 "data": [
321 "data1",
322 "data2"
323 ],
324 "additionalData": null
325}
326 "#;
327
328 let event_log = serde_json::from_str::<Events>(data).unwrap();
329 assert_eq!(
330 event_log.data,
331 LogData::Vec(vec!["data1".to_owned(), "data2".to_owned()])
332 );
333 }
334
335 #[test]
336 fn parse_transaction_info_no_signature() {
337 let data = r#"
338{
339 "data": {
340 "transaction": {
341 "type": "unsigned",
342 "processingTypeOnSource": "SCInvoking",
343 "processingTypeOnDestination": "SCInvoking",
344 "hash": "34cd9c6d0f68c0975971352ed4dcaacc1acd9a2dbd8f5840a2866d09b1d72298",
345 "nonce": 0,
346 "round": 5616535,
347 "epoch": 2314,
348 "value": "0",
349 "receiver": "erd1qqqqqqqqqqqqqpgq0mhy244pyr9pzdhahvvyze4rw3xl29q4kklszyzq72",
350 "sender": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u",
351 "gasPrice": 1000000000,
352 "gasLimit": 25411165,
353 "gasUsed": 1197500,
354 "data": "",
355 "previousTransactionHash": "6c105dc2bb6ca8b89cfcac910a46310812b51312a281837096fc94dd771bb652",
356 "originalTransactionHash": "100d1edd0434938ec39e6cb5059601b4618a1ca25b91c38e5be9e75444b3c4f5",
357 "originalSender": "erd1wavgcxq9tfyrw49k3s3h34085mayu82wqvpd4h6akyh8559pkklsknwhwh",
358 "sourceShard": 4294967295,
359 "destinationShard": 1,
360 "blockNonce": 5547876,
361 "blockHash": "0d7caaf8f2bf46e913f91867527d44cd1c77453c9aee50d91a10739bd272d00c",
362 "notarizedAtSourceInMetaNonce": 5551265,
363 "NotarizedAtSourceInMetaHash": "4c87bc5161925a3902e43a7f9f186e63f21f827ef1129ad0e609a0d45dca016a",
364 "notarizedAtDestinationInMetaNonce": 5551269,
365 "notarizedAtDestinationInMetaHash": "83bfa8463558ee6d2c90b34ee03782619b699fea667acfb98924227bacbba93d",
366 "miniblockType": "SmartContractResultBlock",
367 "miniblockHash": "c12693db88e3b69b68d5279fd8939ec75b7f0d8e529e7fd950c83b5716a436bd",
368 "hyperblockNonce": 5551269,
369 "hyperblockHash": "83bfa8463558ee6d2c90b34ee03782619b699fea667acfb98924227bacbba93d",
370 "timestamp": 1727699210,
371 "logs": {
372 "address": "erd1qqqqqqqqqqqqqpgq0mhy244pyr9pzdhahvvyze4rw3xl29q4kklszyzq72",
373 "events": [
374 {
375 "address": "erd1qqqqqqqqqqqqqpgq0mhy244pyr9pzdhahvvyze4rw3xl29q4kklszyzq72",
376 "identifier": "transferValueOnly",
377 "topics": [
378 "I4byb8EAAA==",
379 "AAAAAAAAAAAFAMMiO8pDAH5z5hUCqfc+N03C7UI6tb8="
380 ],
381 "data": "RXhlY3V0ZU9uRGVzdENvbnRleHQ=",
382 "additionalData": [
383 "RXhlY3V0ZU9uRGVzdENvbnRleHQ=",
384 "ZGVwbG95SW50ZXJjaGFpblRva2Vu",
385 "GeLN3wLxaJKDPbaxdmqkIh0pFNi1l8WJeqy9TofeG40=",
386 "YXZhbGFuY2hlLWZ1amk=",
387 "SVRTVGVzdFRva2Vu",
388 "SVRTVFQ=",
389 "Bg==",
390 "d1iMGAVaSDdUtowjeNXnpvpOHU4DAtrfXbEuelChtb8="
391 ]
392 }
393 ]
394 },
395 "status": "success",
396 "operation": "transfer",
397 "fee": "0",
398 "callType": "asynchronousCallBack",
399 "options": 0
400 }
401 },
402 "error": "",
403 "code": "successful"
404}
405 "#;
406
407 let transaction = serde_json::from_str::<TransactionInfo>(data).unwrap();
408 assert_eq!(
409 transaction.data.unwrap().transaction.hash.unwrap(),
410 "34cd9c6d0f68c0975971352ed4dcaacc1acd9a2dbd8f5840a2866d09b1d72298"
411 );
412 }
413}