Skip to main content

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