casper_rust_wasm_sdk/sdk/rpcs/
get_balance.rs1use 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(all(feature = "js", target_arch = "wasm32"))]
15use gloo_utils::format::JsValueSerdeExt;
16use rand::RngExt;
17#[cfg(all(feature = "js", target_arch = "wasm32"))]
18use serde::{Deserialize, Serialize};
19#[cfg(all(feature = "js", target_arch = "wasm32"))]
20use wasm_bindgen::prelude::*;
21
22#[cfg(all(feature = "js", target_arch = "wasm32"))]
24#[derive(Debug, Deserialize, Clone, Serialize)]
25#[cfg_attr(feature = "js", wasm_bindgen)]
26pub struct GetBalanceResult(_GetBalanceResult);
27
28#[cfg(all(feature = "js", target_arch = "wasm32"))]
29impl From<GetBalanceResult> for _GetBalanceResult {
30 fn from(result: GetBalanceResult) -> Self {
31 result.0
32 }
33}
34
35#[cfg(all(feature = "js", target_arch = "wasm32"))]
36impl From<_GetBalanceResult> for GetBalanceResult {
37 fn from(result: _GetBalanceResult) -> Self {
38 GetBalanceResult(result)
39 }
40}
41
42#[cfg(all(feature = "js", target_arch = "wasm32"))]
43#[cfg_attr(feature = "js", wasm_bindgen)]
44impl GetBalanceResult {
45 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
47 #[cfg(feature = "js")]
48 pub fn api_version(&self) -> JsValue {
49 JsValue::from_serde(&self.0.api_version).unwrap()
50 }
51
52 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
54 #[cfg(feature = "js")]
55 pub fn balance_value(&self) -> JsValue {
56 JsValue::from_serde(&self.0.balance_value).unwrap()
57 }
58
59 #[cfg_attr(feature = "js", wasm_bindgen(getter))]
61 pub fn merkle_proof(&self) -> String {
62 self.0.merkle_proof.clone()
63 }
64
65 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "toJson"))]
67 #[cfg(feature = "js")]
68 pub fn to_json(&self) -> JsValue {
69 JsValue::from_serde(&self.0).unwrap_or(JsValue::null())
70 }
71}
72
73#[derive(Default, Debug, Deserialize, Clone, Serialize)]
75#[cfg(all(feature = "js", target_arch = "wasm32"))]
76#[cfg_attr(
77 feature = "js",
78 wasm_bindgen(js_name = "getBalanceOptions", getter_with_clone)
79)]
80pub struct GetBalanceOptions {
81 pub state_root_hash_as_string: Option<String>,
82 pub state_root_hash: Option<Digest>,
83 pub purse_uref_as_string: Option<String>,
84 pub purse_uref: Option<URef>,
85 pub rpc_address: Option<String>,
86 pub verbosity: Option<Verbosity>,
87}
88
89#[cfg(all(feature = "js", target_arch = "wasm32"))]
90#[cfg_attr(feature = "js", wasm_bindgen)]
91impl SDK {
92 #[cfg(feature = "js")]
102 pub fn get_balance_options(&self, options: JsValue) -> Result<GetBalanceOptions, JsError> {
103 options
104 .into_serde::<GetBalanceOptions>()
105 .map_err(|err| JsError::new(&format!("Error deserializing options: {err:?}")))
106 }
107
108 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "get_balance"))]
122 #[cfg(feature = "js")]
123 pub async fn get_balance_js_alias(
124 &self,
125 options: Option<GetBalanceOptions>,
126 ) -> Result<GetBalanceResult, JsError> {
127 let GetBalanceOptions {
128 state_root_hash_as_string,
129 state_root_hash,
130 purse_uref_as_string,
131 purse_uref,
132 verbosity,
133 rpc_address,
134 } = options.unwrap_or_default();
135
136 let purse_uref = if let Some(purse_uref) = purse_uref {
137 GetBalanceInput::PurseUref(purse_uref)
138 } else if let Some(purse_uref_as_string) = purse_uref_as_string {
139 GetBalanceInput::PurseUrefAsString(purse_uref_as_string)
140 } else {
141 let err = "Error: Missing purse uref as string or purse uref";
142 return Err(JsError::new(err));
143 };
144
145 let result = if let Some(hash) = state_root_hash {
146 self.get_balance(purse_uref, Some(hash.to_digest()), verbosity, rpc_address)
147 .await
148 } else if let Some(hash) = state_root_hash_as_string.clone() {
149 let hash = if !hash.is_empty() {
150 match Digest::new(&hash) {
151 Ok(digest) => digest.to_string(),
152 _ => "".to_string(),
153 }
154 } else {
155 "".to_string()
156 };
157 self.get_balance(purse_uref, Some(hash.as_str()), verbosity, rpc_address)
158 .await
159 } else {
160 self.get_balance(purse_uref, None::<&str>, verbosity, rpc_address)
161 .await
162 };
163
164 match result {
165 Ok(data) => Ok(data.result.into()),
166 Err(err) => {
167 let err = &format!("Error occurred with {err:?}");
168 Err(JsError::new(err))
169 }
170 }
171 }
172
173 #[cfg_attr(feature = "js", wasm_bindgen(js_name = "state_get_balance"))]
183 #[deprecated(note = "This function is an alias. Please use `get_balance` instead.")]
184 #[allow(deprecated)]
185 #[cfg(feature = "js")]
186 pub async fn state_get_balance(
187 &self,
188 options: Option<GetBalanceOptions>,
189 ) -> Result<GetBalanceResult, JsError> {
190 self.get_balance_js_alias(options).await
191 }
192}
193
194#[derive(Debug, Clone)]
196pub enum GetBalanceInput {
197 PurseUref(URef),
198 PurseUrefAsString(String),
199}
200
201impl SDK {
202 pub async fn get_balance(
219 &self,
220 purse_uref: GetBalanceInput,
221 state_root_hash: Option<impl ToDigest>,
222 verbosity: Option<Verbosity>,
223 rpc_address: Option<String>,
224 ) -> Result<SuccessResponse<_GetBalanceResult>, SdkError> {
225 let state_root_hash = if let Some(state_root_hash) = state_root_hash {
227 if state_root_hash.is_empty() {
228 let state_root_hash = self
229 .get_state_root_hash(
230 None,
231 None,
232 Some(self.get_rpc_address(rpc_address.clone())),
233 )
234 .await;
235
236 match state_root_hash {
237 Ok(state_root_hash) => {
238 let state_root_hash: Digest =
239 state_root_hash.result.state_root_hash.unwrap().into();
240 state_root_hash
241 }
242 Err(_) => "".to_digest(),
243 }
244 } else {
245 state_root_hash.to_digest()
246 }
247 } else {
248 let state_root_hash = self
249 .get_state_root_hash(None, None, Some(self.get_rpc_address(rpc_address.clone())))
250 .await;
251
252 match state_root_hash {
253 Ok(state_root_hash) => {
254 let state_root_hash: Digest =
255 state_root_hash.result.state_root_hash.unwrap().into();
256 state_root_hash
257 }
258 Err(_) => "".to_digest(),
259 }
260 };
261 let random_id = rand::rng().random::<u64>().to_string();
262 match purse_uref {
263 GetBalanceInput::PurseUref(purse_uref) => get_balance_lib(
264 JsonRpcId::from(random_id),
265 &self.get_rpc_address(rpc_address),
266 self.get_verbosity(verbosity).into(),
267 state_root_hash.into(),
268 purse_uref.into(),
269 )
270 .await
271 .map_err(SdkError::from),
272 GetBalanceInput::PurseUrefAsString(purse_uref) => get_balance_cli(
273 &random_id,
274 &self.get_rpc_address(rpc_address),
275 self.get_verbosity(verbosity).into(),
276 &state_root_hash.to_string(),
277 &purse_uref,
278 )
279 .await
280 .map_err(SdkError::from),
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::helpers::public_key_from_secret_key;
289 use sdk_tests::tests::helpers::{
290 get_enable_addressable_entity, get_network_constants, get_user_secret_key,
291 };
292
293 async fn get_main_purse() -> URef {
294 let sdk = SDK::new(None, None, None);
295 let (rpc_address, _, _, _, _) = get_network_constants();
296 let secret_key = get_user_secret_key(None).unwrap();
297 let account = public_key_from_secret_key(&secret_key).unwrap();
298
299 let main_purse = if get_enable_addressable_entity() {
300 sdk.get_entity(None, Some(account), None, None, Some(rpc_address))
301 .await
302 .unwrap()
303 .result
304 .entity_result
305 .addressable_entity()
306 .unwrap()
307 .entity
308 .main_purse()
309 } else {
310 #[allow(deprecated)]
311 sdk.get_account(None, Some(account), None, None, Some(rpc_address))
312 .await
313 .unwrap()
314 .result
315 .account
316 .main_purse()
317 };
318
319 main_purse.into()
320 }
321
322 #[tokio::test]
323 async fn test_get_balance_with_none_values() {
324 let sdk = SDK::new(None, None, None);
326 let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
327 let error_message = "failed to parse node address as valid URL";
328
329 let result = sdk
331 .get_balance(
332 purse_uref,
333 Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), None,
335 None,
336 )
337 .await;
338
339 assert!(result.is_err());
341 let err_string = result.err().unwrap().to_string();
342 assert!(err_string.contains(error_message));
343 }
344
345 #[tokio::test]
346 async fn test_get_balance_with_purse_uref() {
347 let sdk = SDK::new(None, None, None);
349 let (rpc_address, _, _, _, _) = get_network_constants();
350 let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
351
352 let result = sdk
354 .get_balance(purse_uref, None::<&str>, None, Some(rpc_address))
355 .await;
356
357 assert!(result.is_ok());
359 }
360
361 #[tokio::test]
362 async fn test_get_balance_with_purse_uref_as_string() {
363 let sdk = SDK::new(None, None, None);
365 let (rpc_address, _, _, _, _) = get_network_constants();
366 let purse_uref =
367 GetBalanceInput::PurseUrefAsString(get_main_purse().await.to_formatted_string());
368
369 let result = sdk
371 .get_balance(purse_uref, None::<&str>, None, Some(rpc_address))
372 .await;
373
374 assert!(result.is_ok());
376 }
377
378 #[tokio::test]
379 async fn test_get_balance_with_state_root_hash() {
380 let sdk = SDK::new(None, None, None);
382 let (rpc_address, _, _, _, _) = get_network_constants();
383
384 let state_root_hash: Digest = sdk
385 .get_state_root_hash(None, Some(Verbosity::High), Some(rpc_address.clone()))
386 .await
387 .unwrap()
388 .result
389 .state_root_hash
390 .unwrap()
391 .into();
392 let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
393
394 let result = sdk
396 .get_balance(purse_uref, Some(state_root_hash), None, Some(rpc_address))
397 .await;
398
399 assert!(result.is_ok());
401 }
402
403 #[tokio::test]
404 async fn test_get_balance_with_error() {
405 let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
407 let error_message = "error sending request";
408 let purse_uref = GetBalanceInput::PurseUref(get_main_purse().await);
409 let result = sdk
411 .get_balance(
412 purse_uref,
413 Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), None,
415 None,
416 )
417 .await;
418
419 assert!(result.is_err());
421 let err_string = result.err().unwrap().to_string();
422 assert!(err_string.contains(error_message));
423 }
424}