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