multiversx_sdk/data/
transaction.rs

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