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(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#[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 #[wasm_bindgen(getter)]
47 pub fn api_version(&self) -> JsValue {
48 JsValue::from_serde(&self.0.api_version).unwrap()
49 }
50
51 #[wasm_bindgen(getter)]
53 pub fn balance_value(&self) -> JsValue {
54 JsValue::from_serde(&self.0.balance_value).unwrap()
55 }
56
57 #[wasm_bindgen(getter)]
59 pub fn merkle_proof(&self) -> String {
60 self.0.merkle_proof.clone()
61 }
62
63 #[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#[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 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 #[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 #[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#[derive(Debug, Clone)]
187pub enum GetBalanceInput {
188 PurseUref(URef),
189 PurseUrefAsString(String),
190}
191
192impl SDK {
193 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 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 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 let result = sdk
322 .get_balance(
323 purse_uref,
324 Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), None,
326 None,
327 )
328 .await;
329
330 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 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 let result = sdk
345 .get_balance(purse_uref, None::<&str>, None, Some(rpc_address))
346 .await;
347
348 assert!(result.is_ok());
350 }
351
352 #[tokio::test]
353 async fn test_get_balance_with_purse_uref_as_string() {
354 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 let result = sdk
362 .get_balance(purse_uref, None::<&str>, None, Some(rpc_address))
363 .await;
364
365 assert!(result.is_ok());
367 }
368
369 #[tokio::test]
370 async fn test_get_balance_with_state_root_hash() {
371 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 let result = sdk
387 .get_balance(purse_uref, Some(state_root_hash), None, Some(rpc_address))
388 .await;
389
390 assert!(result.is_ok());
392 }
393
394 #[tokio::test]
395 async fn test_get_balance_with_error() {
396 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 let result = sdk
402 .get_balance(
403 purse_uref,
404 Some("7d3dc9c74fe93e83fe6cc7a9830ba223035ad4fd4fd464489640742069ca31ed"), None,
406 None,
407 )
408 .await;
409
410 assert!(result.is_err());
412 let err_string = result.err().unwrap().to_string();
413 assert!(err_string.contains(error_message));
414 }
415}