Skip to main content

casper_rust_wasm_sdk/sdk/rpcs/
query_balance_details.rs

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