multiversx_sdk/data/
transaction.rs

1use std::collections::HashMap;
2
3use super::vm::CallType;
4use multiversx_chain_core::std::Bech32Address;
5use serde::{Deserialize, Serialize};
6
7// Transaction holds the fields of a transaction to be broadcasted to the network
8#[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/// This is only used for serialize
29#[allow(clippy::trivially_copy_pass_by_ref)]
30fn is_zero(num: &u32) -> bool {
31    *num == 0
32}
33
34// TxCostResponseData follows the format of the data field of a transaction cost request
35#[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// ResponseTxCost defines a response from the node holding the transaction cost
43#[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// TransactionOnNetwork holds a transaction's info entry in a hyper block
52#[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// Events represents the events generated by a transaction with changed fields' types in order to make it friendly for API's json
93#[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// ApiLogs represents logs with changed fields' types in order to make it friendly for API's json
124#[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, // consider switching to BigUint if this proves insufficient
137    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// TransactionInfo holds a transaction info response from the network
161#[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// TransactionStatus holds a transaction's status response from the network
175#[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// TransactionProcessStatus holds a transaction's status response from the network obtained through the process-status API
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct TransactionProcessStatus {
191    pub error: String,
192    pub code: String,
193    pub data: Option<TransactionProcessStatusData>,
194}
195
196// ArgCreateTransaction will hold the transaction fields
197#[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// SendTransactionResponse holds the response received from the network when broadcasting a transaction
220#[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// SimulateGasTransactionResponse holds the response received from the network when estimating cost of a transaction
234#[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// SendTransactionsResponse holds the response received from the network when broadcasting multiple transactions
249#[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}