Skip to main content

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    #[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// Events represents the events generated by a transaction with changed fields' types in order to make it friendly for API's json
98#[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// ApiLogs represents logs with changed fields' types in order to make it friendly for API's json
129#[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, // consider switching to BigUint if this proves insufficient
142    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// TransactionInfo holds a transaction info response from the network
166#[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// TransactionStatus holds a transaction's status response from the network
180#[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// TransactionProcessStatus holds a transaction's status response from the network obtained through the process-status API
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct TransactionProcessStatus {
196    pub error: String,
197    pub code: String,
198    pub data: Option<TransactionProcessStatusData>,
199}
200
201// ArgCreateTransaction will hold the transaction fields
202#[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// SendTransactionResponse holds the response received from the network when broadcasting a transaction
225#[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// SimulateGasTransactionResponse holds the response received from the network when estimating cost of a transaction
239#[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// SendTransactionsResponse holds the response received from the network when broadcasting multiple transactions
254#[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}