casper_rust_wasm_sdk/sdk/rpcs/
get_balance.rs

1use crate::{
2    types::{
3        digest::{Digest, ToDigest},
4        sdk_error::SdkError,
5        uref::URef,
6        verbosity::Verbosity,
7    },
8    SDK,
9};
10use casper_client::{
11    cli::get_balance as get_balance_cli, get_balance as get_balance_lib,
12    rpcs::results::GetBalanceResult as _GetBalanceResult, JsonRpcId, SuccessResponse,
13};
14#[cfg(target_arch = "wasm32")]
15use gloo_utils::format::JsValueSerdeExt;
16use rand::Rng;
17#[cfg(target_arch = "wasm32")]
18use serde::{Deserialize, Serialize};
19#[cfg(target_arch = "wasm32")]
20use wasm_bindgen::prelude::*;
21
22// Define a struct to wrap the GetBalanceResult
23#[cfg(target_arch = "wasm32")]
24#[derive(Debug, Deserialize, Clone, Serialize)]
25#[wasm_bindgen]
26pub struct GetBalanceResult(_GetBalanceResult);
27
28#[cfg(target_arch = "wasm32")]
29impl From<GetBalanceResult> for _GetBalanceResult {
30    fn from(result: GetBalanceResult) -> Self {
31        result.0
32    }
33}
34
35#[cfg(target_arch = "wasm32")]
36impl From<_GetBalanceResult> for GetBalanceResult {
37    fn from(result: _GetBalanceResult) -> Self {
38        GetBalanceResult(result)
39    }
40}
41
42#[cfg(target_arch = "wasm32")]
43#[wasm_bindgen]
44impl GetBalanceResult {
45    /// Gets the API version as a JsValue.
46    #[wasm_bindgen(getter)]
47    pub fn api_version(&self) -> JsValue {
48        JsValue::from_serde(&self.0.api_version).unwrap()
49    }
50
51    /// Gets the balance value as a JsValue.
52    #[wasm_bindgen(getter)]
53    pub fn balance_value(&self) -> JsValue {
54        JsValue::from_serde(&self.0.balance_value).unwrap()
55    }
56
57    /// Gets the Merkle proof as a string.
58    #[wasm_bindgen(getter)]
59    pub fn merkle_proof(&self) -> String {
60        self.0.merkle_proof.clone()
61    }
62
63    /// Converts the GetBalanceResult to a JsValue.
64    #[wasm_bindgen(js_name = "toJson")]
65    pub fn to_json(&self) -> JsValue {
66        JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
67    }
68}
69
70/// Options for the `get_balance` method.
71#[derive(Default, Debug, Deserialize, Clone, Serialize)]
72#[cfg(target_arch = "wasm32")]
73#[wasm_bindgen(js_name = "getBalanceOptions", getter_with_clone)]
74pub struct GetBalanceOptions {
75    pub state_root_hash_as_string: Option<String>,
76    pub state_root_hash: Option<Digest>,
77    pub purse_uref_as_string: Option<String>,
78    pub purse_uref: Option<URef>,
79    pub rpc_address: Option<String>,
80    pub verbosity: Option<Verbosity>,
81}
82
83#[cfg(target_arch = "wasm32")]
84#[wasm_bindgen]
85impl SDK {
86    /// Parses balance options from a JsValue.
87    ///
88    /// # Arguments
89    ///
90    /// * `options` - A JsValue containing balance options to be parsed.
91    ///
92    /// # Returns
93    ///
94    /// Parsed balance options as a `GetBalanceOptions` struct.
95    pub fn get_balance_options(&self, options: JsValue) -> Result<GetBalanceOptions, JsError> {
96        options
97            .into_serde::<GetBalanceOptions>()
98            .map_err(|err| JsError::new(&format!("Error deserializing options: {:?}", err)))
99    }
100
101    /// Retrieves balance information using the provided options.
102    ///
103    /// # Arguments
104    ///
105    /// * `options` - An optional `GetBalanceOptions` struct containing retrieval options.
106    ///
107    /// # Returns
108    ///
109    /// A `Result` containing either a `GetBalanceResult` or a `JsError` in case of an error.
110    ///
111    /// # Errors
112    ///
113    /// Returns a `JsError` if there is an error during the retrieval process.
114    #[wasm_bindgen(js_name = "get_balance")]
115    pub async fn get_balance_js_alias(
116        &self,
117        options: Option<GetBalanceOptions>,
118    ) -> Result<GetBalanceResult, JsError> {
119        let GetBalanceOptions {
120            state_root_hash_as_string,
121            state_root_hash,
122            purse_uref_as_string,
123            purse_uref,
124            verbosity,
125            rpc_address,
126        } = options.unwrap_or_default();
127
128        let purse_uref = if let Some(purse_uref) = purse_uref {
129            GetBalanceInput::PurseUref(purse_uref)
130        } else if let Some(purse_uref_as_string) = purse_uref_as_string {
131            GetBalanceInput::PurseUrefAsString(purse_uref_as_string)
132        } else {
133            let err = "Error: Missing purse uref as string or purse uref";
134            return Err(JsError::new(err));
135        };
136
137        let result = if let Some(hash) = state_root_hash {
138            self.get_balance(purse_uref, Some(hash.to_digest()), verbosity, rpc_address)
139                .await
140        } else if let Some(hash) = state_root_hash_as_string.clone() {
141            let hash = if !hash.is_empty() {
142                match Digest::new(&hash) {
143                    Ok(digest) => digest.to_string(),
144                    _ => "".to_string(),
145                }
146            } else {
147                "".to_string()
148            };
149            self.get_balance(purse_uref, Some(hash.as_str()), verbosity, rpc_address)
150                .await
151        } else {
152            self.get_balance(purse_uref, None::<&str>, verbosity, rpc_address)
153                .await
154        };
155
156        match result {
157            Ok(data) => Ok(data.result.into()),
158            Err(err) => {
159                let err = &format!("Error occurred with {:?}", err);
160                Err(JsError::new(err))
161            }
162        }
163    }
164
165    /// JavaScript Alias for `get_balance`.
166    ///
167    /// # Arguments
168    ///
169    /// * `options` - An optional `GetBalanceOptions` struct containing retrieval options.
170    ///
171    /// # Returns
172    ///
173    /// A `Result` containing either a `GetBalanceResult` or a `JsError` in case of an error.
174    #[wasm_bindgen(js_name = "state_get_balance")]
175    #[deprecated(note = "This function is an alias. Please use `get_balance` instead.")]
176    #[allow(deprecated)]
177    pub async fn state_get_balance(
178        &self,
179        options: Option<GetBalanceOptions>,
180    ) -> Result<GetBalanceResult, JsError> {
181        self.get_balance_js_alias(options).await
182    }
183}
184
185/// Enum representing different ways to specify the purse uref.
186#[derive(Debug, Clone)]
187pub enum GetBalanceInput {
188    PurseUref(URef),
189    PurseUrefAsString(String),
190}
191
192impl SDK {
193    /// Retrieves balance information based on the provided options.
194    ///
195    /// # Arguments
196    ///
197    /// * `state_root_hash` - The state root hash to query for balance information.
198    /// * `purse_uref` - The purse uref specifying the purse for which to retrieve the balance.
199    /// * `verbosity` - An optional `Verbosity` level for controlling the output verbosity.
200    /// * `rpc_address` - An optional string specifying the rpc address to use for the request.
201    ///
202    /// # Returns
203    ///
204    /// A `Result` containing either a `GetBalanceResult` or a `SdkError` in case of an error.
205    ///
206    /// # Errors
207    ///
208    /// Returns a `SdkError` if there is an error during the retrieval process.
209    pub async fn get_balance(
210        &self,
211        purse_uref: GetBalanceInput,
212        state_root_hash: Option<impl ToDigest>,
213        verbosity: Option<Verbosity>,
214        rpc_address: Option<String>,
215    ) -> Result<SuccessResponse<_GetBalanceResult>, SdkError> {
216        //log("get_balance!");
217        let state_root_hash = if let Some(state_root_hash) = state_root_hash {
218            if state_root_hash.is_empty() {
219                let state_root_hash = self
220                    .get_state_root_hash(
221                        None,
222                        None,
223                        Some(self.get_rpc_address(rpc_address.clone())),
224                    )
225                    .await;
226
227                match state_root_hash {
228                    Ok(state_root_hash) => {
229                        let state_root_hash: Digest =
230                            state_root_hash.result.state_root_hash.unwrap().into();
231                        state_root_hash
232                    }
233                    Err(_) => "".to_digest(),
234                }
235            } else {
236                state_root_hash.to_digest()
237            }
238        } else {
239            let state_root_hash = self
240                .get_state_root_hash(None, None, Some(self.get_rpc_address(rpc_address.clone())))
241                .await;
242
243            match state_root_hash {
244                Ok(state_root_hash) => {
245                    let state_root_hash: Digest =
246                        state_root_hash.result.state_root_hash.unwrap().into();
247                    state_root_hash
248                }
249                Err(_) => "".to_digest(),
250            }
251        };
252
253        match purse_uref {
254            GetBalanceInput::PurseUref(purse_uref) => get_balance_lib(
255                JsonRpcId::from(rand::thread_rng().gen::<u64>().to_string()),
256                &self.get_rpc_address(rpc_address),
257                self.get_verbosity(verbosity).into(),
258                state_root_hash.into(),
259                purse_uref.into(),
260            )
261            .await
262            .map_err(SdkError::from),
263            GetBalanceInput::PurseUrefAsString(purse_uref) => get_balance_cli(
264                &rand::thread_rng().gen::<u64>().to_string(),
265                &self.get_rpc_address(rpc_address),
266                self.get_verbosity(verbosity).into(),
267                &state_root_hash.to_string(),
268                &purse_uref,
269            )
270            .await
271            .map_err(SdkError::from),
272        }
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use crate::helpers::public_key_from_secret_key;
280    use sdk_tests::tests::helpers::{
281        get_enable_addressable_entity, get_network_constants, get_user_secret_key,
282    };
283
284    async fn get_main_purse() -> URef {
285        let sdk = SDK::new(None, None, None);
286        let (rpc_address, _, _, _, _) = get_network_constants();
287        let secret_key = get_user_secret_key(None).unwrap();
288        let account = public_key_from_secret_key(&secret_key).unwrap();
289
290        let main_purse = if get_enable_addressable_entity() {
291            sdk.get_entity(None, Some(account), None, None, Some(rpc_address))
292                .await
293                .unwrap()
294                .result
295                .entity_result
296                .addressable_entity()
297                .unwrap()
298                .entity
299                .main_purse()
300        } else {
301            #[allow(deprecated)]
302            sdk.get_account(None, Some(account), None, None, Some(rpc_address))
303                .await
304                .unwrap()
305                .result
306                .account
307                .main_purse()
308        };
309
310        main_purse.into()
311    }
312
313    #[tokio::test]
314    async fn test_get_balance_with_none_values() {
315        // Arrange
316        let sdk = SDK::new(None, None, None);
317        let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
318        let error_message = "failed to parse node address as valid URL";
319
320        // Act
321        let result = sdk
322            .get_balance(
323                purse_uref,
324                Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), // get_balance does not support empty string as state_root_hash
325                None,
326                None,
327            )
328            .await;
329
330        // Assert
331        assert!(result.is_err());
332        let err_string = result.err().unwrap().to_string();
333        assert!(err_string.contains(error_message));
334    }
335
336    #[tokio::test]
337    async fn test_get_balance_with_purse_uref() {
338        // Arrange
339        let sdk = SDK::new(None, None, None);
340        let (rpc_address, _, _, _, _) = get_network_constants();
341        let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
342
343        // Act
344        let result = sdk
345            .get_balance(purse_uref, None::<&str>, None, Some(rpc_address))
346            .await;
347
348        // Assert
349        assert!(result.is_ok());
350    }
351
352    #[tokio::test]
353    async fn test_get_balance_with_purse_uref_as_string() {
354        // Arrange
355        let sdk = SDK::new(None, None, None);
356        let (rpc_address, _, _, _, _) = get_network_constants();
357        let purse_uref =
358            GetBalanceInput::PurseUrefAsString(get_main_purse().await.to_formatted_string());
359
360        // Act
361        let result = sdk
362            .get_balance(purse_uref, None::<&str>, None, Some(rpc_address))
363            .await;
364
365        // Assert
366        assert!(result.is_ok());
367    }
368
369    #[tokio::test]
370    async fn test_get_balance_with_state_root_hash() {
371        // Arrange
372        let sdk = SDK::new(None, None, None);
373        let (rpc_address, _, _, _, _) = get_network_constants();
374
375        let state_root_hash: Digest = sdk
376            .get_state_root_hash(None, Some(Verbosity::High), Some(rpc_address.clone()))
377            .await
378            .unwrap()
379            .result
380            .state_root_hash
381            .unwrap()
382            .into();
383        let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
384
385        // Act
386        let result = sdk
387            .get_balance(purse_uref, Some(state_root_hash), None, Some(rpc_address))
388            .await;
389
390        // Assert
391        assert!(result.is_ok());
392    }
393
394    #[tokio::test]
395    async fn test_get_balance_with_error() {
396        // Arrange
397        let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
398        let error_message = "error sending request for url (http://localhost/rpc)";
399        let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
400        // Act
401        let result = sdk
402            .get_balance(
403                purse_uref,
404                Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), // get_balance does not support empty string as state_root_hash
405                None,
406                None,
407            )
408            .await;
409
410        // Assert
411        assert!(result.is_err());
412        let err_string = result.err().unwrap().to_string();
413        assert!(err_string.contains(error_message));
414    }
415}