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