casper_rust_wasm_sdk/sdk/rpcs/
query_balance_details.rs

1#[cfg(target_arch = "wasm32")]
2use crate::types::digest::Digest;
3use crate::{
4    types::{
5        identifier::{
6            global_state_identifier::GlobalStateIdentifier, purse_identifier::PurseIdentifier,
7        },
8        sdk_error::SdkError,
9        verbosity::Verbosity,
10    },
11    SDK,
12};
13use casper_client::{
14    cli::parse::purse_identifier as parse_purse_identifier,
15    cli::query_balance_details as query_balance_details_cli,
16    query_balance_details as query_balance_details_lib,
17    rpcs::results::QueryBalanceDetailsResult as _QueryBalanceDetailsResult, JsonRpcId,
18    SuccessResponse,
19};
20#[cfg(target_arch = "wasm32")]
21use gloo_utils::format::JsValueSerdeExt;
22use rand::Rng;
23#[cfg(target_arch = "wasm32")]
24use serde::{Deserialize, Serialize};
25#[cfg(target_arch = "wasm32")]
26use wasm_bindgen::prelude::*;
27
28// Define a struct to wrap the QueryBalanceDetailsResult
29#[cfg(target_arch = "wasm32")]
30#[derive(Debug, Deserialize, Clone, Serialize)]
31#[wasm_bindgen]
32pub struct QueryBalanceDetailsResult(_QueryBalanceDetailsResult);
33
34#[cfg(target_arch = "wasm32")]
35impl From<QueryBalanceDetailsResult> for _QueryBalanceDetailsResult {
36    fn from(result: QueryBalanceDetailsResult) -> Self {
37        result.0
38    }
39}
40
41#[cfg(target_arch = "wasm32")]
42impl From<_QueryBalanceDetailsResult> for QueryBalanceDetailsResult {
43    fn from(result: _QueryBalanceDetailsResult) -> Self {
44        QueryBalanceDetailsResult(result)
45    }
46}
47
48#[cfg(target_arch = "wasm32")]
49#[wasm_bindgen]
50impl QueryBalanceDetailsResult {
51    /// Gets the API version as a JsValue.
52    #[wasm_bindgen(getter)]
53    pub fn api_version(&self) -> JsValue {
54        JsValue::from_serde(&self.0.api_version).unwrap()
55    }
56
57    #[wasm_bindgen(getter)]
58    pub fn total_balance(&self) -> JsValue {
59        JsValue::from_serde(&self.0.total_balance).unwrap()
60    }
61
62    #[wasm_bindgen(getter)]
63    pub fn available_balance(&self) -> JsValue {
64        JsValue::from_serde(&self.0.available_balance).unwrap()
65    }
66
67    #[wasm_bindgen(getter)]
68    pub fn total_balance_proof(&self) -> JsValue {
69        JsValue::from_serde(&self.0.total_balance_proof).unwrap()
70    }
71
72    #[wasm_bindgen(getter)]
73    pub fn holds(&self) -> JsValue {
74        JsValue::from_serde(&self.0.holds).unwrap()
75    }
76
77    /// Converts the QueryBalanceDetailsResult to a JsValue.
78    #[wasm_bindgen(js_name = "toJson")]
79    pub fn to_json(&self) -> JsValue {
80        JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
81    }
82}
83
84/// Options for the `query_balance` method.
85#[derive(Debug, Deserialize, Clone, Default, Serialize)]
86#[cfg(target_arch = "wasm32")]
87#[wasm_bindgen(js_name = "queryBalanceDetailsOptions", getter_with_clone)]
88pub struct QueryBalanceDetailsOptions {
89    pub purse_identifier_as_string: Option<String>,
90    pub purse_identifier: Option<PurseIdentifier>,
91    pub global_state_identifier: Option<GlobalStateIdentifier>,
92    pub state_root_hash_as_string: Option<String>,
93    pub state_root_hash: Option<Digest>,
94    pub maybe_block_id_as_string: Option<String>,
95    pub rpc_address: Option<String>,
96    pub verbosity: Option<Verbosity>,
97}
98
99#[cfg(target_arch = "wasm32")]
100#[wasm_bindgen]
101impl SDK {
102    /// Parses query balance options from a JsValue.
103    ///
104    /// # Arguments
105    ///
106    /// * `options` - A JsValue containing query balance options to be parsed.
107    ///
108    /// # Returns
109    ///
110    /// Parsed query balance options as a `QueryBalanceDetailsOptions` struct.
111    pub fn query_balance_details_options(
112        &self,
113        options: JsValue,
114    ) -> Result<QueryBalanceDetailsOptions, JsError> {
115        options
116            .into_serde::<QueryBalanceDetailsOptions>()
117            .map_err(|err| JsError::new(&format!("Error deserializing options: {:?}", err)))
118    }
119
120    /// Retrieves balance information using the provided options.
121    ///
122    /// # Arguments
123    ///
124    /// * `options` - An optional `QueryBalanceDetailsOptions` struct containing retrieval options.
125    ///
126    /// # Returns
127    ///
128    /// A `Result` containing either a `QueryBalanceDetailsResult` or a `JsError` in case of an error.
129    ///
130    /// # Errors
131    ///
132    /// Returns a `JsError` if there is an error during the retrieval process.
133    #[wasm_bindgen(js_name = "query_balance_details")]
134    pub async fn query_balance_details_js_alias(
135        &self,
136        options: Option<QueryBalanceDetailsOptions>,
137    ) -> Result<QueryBalanceDetailsResult, JsError> {
138        let QueryBalanceDetailsOptions {
139            global_state_identifier,
140            purse_identifier_as_string,
141            purse_identifier,
142            state_root_hash_as_string,
143            state_root_hash,
144            maybe_block_id_as_string,
145            verbosity,
146            rpc_address,
147        } = options.unwrap_or_default();
148
149        let result = if let Some(hash) = state_root_hash {
150            self.query_balance_details(
151                global_state_identifier,
152                purse_identifier_as_string,
153                purse_identifier,
154                Some(hash.to_string()),
155                None,
156                verbosity,
157                rpc_address,
158            )
159            .await
160        } else if let Some(hash) = state_root_hash_as_string {
161            self.query_balance_details(
162                global_state_identifier,
163                purse_identifier_as_string,
164                purse_identifier,
165                Some(hash.to_string()),
166                None,
167                verbosity,
168                rpc_address,
169            )
170            .await
171        } else if let Some(maybe_block_id_as_string) = maybe_block_id_as_string {
172            self.query_balance_details(
173                global_state_identifier,
174                purse_identifier_as_string,
175                purse_identifier,
176                None,
177                Some(maybe_block_id_as_string),
178                verbosity,
179                rpc_address,
180            )
181            .await
182        } else {
183            self.query_balance_details(
184                global_state_identifier,
185                purse_identifier_as_string,
186                purse_identifier,
187                None,
188                None,
189                verbosity,
190                rpc_address,
191            )
192            .await
193        };
194        match result {
195            Ok(data) => Ok(data.result.into()),
196            Err(err) => {
197                let err = &format!("Error occurred with {:?}", err);
198                Err(JsError::new(err))
199            }
200        }
201    }
202}
203
204impl SDK {
205    /// Retrieves balance information details based on the provided options.
206    ///
207    /// # Arguments
208    ///
209    /// * `maybe_global_state_identifier` - An optional `GlobalStateIdentifier` for specifying global state.
210    /// * `purse_identifier_as_string` - An optional string representing a purse identifier.
211    /// * `purse_identifier` - An optional `PurseIdentifier`.
212    /// * `state_root_hash` - An optional string representing a state root hash.
213    /// * `maybe_block_id` - An optional string representing a block identifier.
214    /// * `verbosity` - An optional `Verbosity` level for controlling the output verbosity.
215    /// * `rpc_address` - An optional string specifying the rpc address to use for the request.
216    ///
217    /// # Returns
218    ///
219    /// A `Result` containing either a `SuccessResponse<_QueryBalanceDetailsResult>` or a `SdkError` in case of an error.
220    ///
221    /// # Errors
222    ///
223    /// Returns a `SdkError` if there is an error during the retrieval process.
224    #[allow(clippy::too_many_arguments)]
225    pub async fn query_balance_details(
226        &self,
227        maybe_global_state_identifier: Option<GlobalStateIdentifier>,
228        purse_identifier_as_string: Option<String>,
229        purse_identifier: Option<PurseIdentifier>,
230        state_root_hash: Option<String>,
231        maybe_block_id: Option<String>,
232        verbosity: Option<Verbosity>,
233        rpc_address: Option<String>,
234    ) -> Result<SuccessResponse<_QueryBalanceDetailsResult>, SdkError> {
235        //log("query_balance_details!");
236
237        let purse_identifier: PurseIdentifier = if let Some(purse_identifier) = purse_identifier {
238            purse_identifier
239        } else if let Some(purse_id) = purse_identifier_as_string.clone() {
240            match parse_purse_identifier(&purse_id) {
241                Ok(parsed) => parsed.into(),
242                Err(err) => {
243                    return Err(err.into());
244                }
245            }
246        } else {
247            let err = "Error: Missing purse identifier".to_string();
248            return Err(SdkError::InvalidArgument {
249                context: "query_global_state",
250                error: err,
251            });
252        };
253
254        if let Some(maybe_global_state_identifier) = maybe_global_state_identifier {
255            query_balance_details_lib(
256                JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
257                &self.get_rpc_address(rpc_address),
258                self.get_verbosity(verbosity).into(),
259                Some(maybe_global_state_identifier.into()),
260                purse_identifier.into(),
261            )
262            .await
263            .map_err(SdkError::from)
264        } else if maybe_global_state_identifier.is_none() {
265            query_balance_details_lib(
266                JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
267                &self.get_rpc_address(rpc_address),
268                self.get_verbosity(verbosity).into(),
269                None,
270                purse_identifier.into(),
271            )
272            .await
273            .map_err(SdkError::from)
274        } else if let Some(state_root_hash) = state_root_hash {
275            query_balance_details_cli(
276                &rand::thread_rng().gen::<u64>().to_string(),
277                &self.get_rpc_address(rpc_address),
278                self.get_verbosity(verbosity).into(),
279                "",
280                &state_root_hash,
281                &purse_identifier.to_string(),
282            )
283            .await
284            .map_err(SdkError::from)
285        } else {
286            query_balance_details_cli(
287                &rand::thread_rng().gen::<u64>().to_string(),
288                &self.get_rpc_address(rpc_address),
289                self.get_verbosity(verbosity).into(),
290                &maybe_block_id.unwrap_or_default(),
291                "",
292                &purse_identifier.to_string(),
293            )
294            .await
295            .map_err(SdkError::from)
296        }
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use crate::{
304        helpers::public_key_from_secret_key,
305        types::{digest::Digest, public_key::PublicKey},
306    };
307    use sdk_tests::tests::helpers::{get_network_constants, get_user_secret_key};
308
309    fn get_purse_identifier() -> PurseIdentifier {
310        let secret_key = get_user_secret_key(None).unwrap();
311        let account = public_key_from_secret_key(&secret_key).unwrap();
312        let public_key = PublicKey::new(&account).unwrap();
313        PurseIdentifier::from_main_purse_under_public_key(public_key)
314    }
315
316    #[tokio::test]
317    async fn test_query_balance_details_with_none_values() {
318        // Arrange
319        let sdk = SDK::new(None, None, None);
320        let error_message = "failed to parse node address as valid URL";
321
322        // Act
323        let result = sdk
324            .query_balance_details(
325                None,
326                None,
327                Some(get_purse_identifier()),
328                None,
329                None,
330                None,
331                None,
332            )
333            .await;
334
335        // Assert
336        assert!(result.is_err());
337        let err_string = result.err().unwrap().to_string();
338        assert!(err_string.contains(error_message));
339    }
340
341    #[tokio::test]
342    async fn test_query_balance_details_with_missing_purse() {
343        // Arrange
344        let sdk = SDK::new(None, None, None);
345        let error_message = "Error: Missing purse identifier";
346
347        // Act
348        let result = sdk
349            .query_balance_details(None, None, None, None, None, None, None)
350            .await;
351
352        // Assert
353        assert!(result.is_err());
354        let err_string = result.err().unwrap().to_string();
355
356        assert!(err_string.contains(error_message));
357    }
358
359    #[tokio::test]
360    async fn test_query_balance_details_with_global_state_identifier() {
361        // Arrange
362        let sdk = SDK::new(None, None, None);
363        let global_state_identifier = GlobalStateIdentifier::from_block_height(1);
364        let verbosity = Some(Verbosity::High);
365        let (rpc_address, _, _, _, _) = get_network_constants();
366        // Act
367        let result = sdk
368            .query_balance_details(
369                Some(global_state_identifier.clone()),
370                None,
371                Some(get_purse_identifier()),
372                None,
373                None,
374                verbosity,
375                Some(rpc_address),
376            )
377            .await;
378
379        // Assert
380        assert!(result.is_ok());
381    }
382
383    #[tokio::test]
384    async fn test_query_balance_details_with_state_root_hash() {
385        // Arrange
386        let sdk = SDK::new(None, None, None);
387        let verbosity = Some(Verbosity::High);
388        let (rpc_address, _, _, _, _) = get_network_constants();
389        let state_root_hash: Digest = sdk
390            .get_state_root_hash(None, verbosity, Some(rpc_address.clone()))
391            .await
392            .unwrap()
393            .result
394            .state_root_hash
395            .unwrap()
396            .into();
397
398        // Act
399        let result = sdk
400            .query_balance_details(
401                None,
402                None,
403                Some(get_purse_identifier()),
404                Some(state_root_hash.to_string()),
405                None,
406                verbosity,
407                Some(rpc_address),
408            )
409            .await;
410
411        // Assert
412        assert!(result.is_ok());
413    }
414
415    #[tokio::test]
416    async fn test_query_balance_details_with_block_id() {
417        // Arrange
418        let sdk = SDK::new(None, None, None);
419        let verbosity = Some(Verbosity::High);
420        let (rpc_address, _, _, _, _) = get_network_constants();
421
422        // Act
423        let result = sdk
424            .query_balance_details(
425                None,
426                None,
427                Some(get_purse_identifier()),
428                None,
429                Some("1".to_string()),
430                verbosity,
431                Some(rpc_address.clone()),
432            )
433            .await;
434        // Assert
435        assert!(result.is_ok());
436    }
437
438    #[tokio::test]
439    async fn test_query_balance_details_with_purse_identifier() {
440        // Arrange
441        let sdk = SDK::new(None, None, None);
442        let verbosity = Some(Verbosity::High);
443        let (rpc_address, _, _, _, _) = get_network_constants();
444
445        // Act
446        let result = sdk
447            .query_balance_details(
448                None,
449                None,
450                Some(get_purse_identifier()),
451                None,
452                None,
453                verbosity,
454                Some(rpc_address.clone()),
455            )
456            .await;
457
458        // Assert
459        assert!(result.is_ok());
460    }
461
462    #[tokio::test]
463    async fn test_query_balance_details_with_purse_identifier_as_string() {
464        // Arrange
465        let sdk = SDK::new(None, None, None);
466        let verbosity = Some(Verbosity::High);
467        let (rpc_address, _, _, _, _) = get_network_constants();
468
469        // Act
470        let result = sdk
471            .query_balance_details(
472                None,
473                Some(get_purse_identifier().to_string()),
474                None,
475                None,
476                None,
477                verbosity,
478                Some(rpc_address),
479            )
480            .await;
481
482        // Assert
483        assert!(result.is_ok());
484    }
485
486    #[tokio::test]
487    async fn test_query_balance_details_with_error() {
488        // Arrange
489        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
490
491        let error_message = "error sending request for url (http://localhost/rpc)";
492
493        // Act
494        let result = sdk
495            .query_balance_details(
496                None,
497                Some(get_purse_identifier().to_string()),
498                None,
499                None,
500                None,
501                None,
502                None,
503            )
504            .await;
505
506        // Assert
507        assert!(result.is_err());
508        let err_string = result.err().unwrap().to_string();
509        assert!(err_string.contains(error_message));
510    }
511}