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#[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 #[wasm_bindgen(getter)]
52 pub fn api_version(&self) -> JsValue {
53 JsValue::from_serde(&self.0.api_version).unwrap()
54 }
55
56 #[wasm_bindgen(getter)]
58 pub fn balance(&self) -> JsValue {
59 JsValue::from_serde(&self.0.balance).unwrap()
60 }
61
62 #[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#[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 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 #[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 #[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 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 let sdk = SDK::new(None, None, None);
303 let error_message = "failed to parse node address as valid URL";
304
305 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!(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 let sdk = SDK::new(None, None, None);
328 let error_message = "Error: Missing purse identifier";
329
330 let result = sdk
332 .query_balance(None, None, None, None, None, None, None)
333 .await;
334
335 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 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 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!(result.is_ok());
364 }
365
366 #[tokio::test]
367 async fn test_query_balance_with_state_root_hash() {
368 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 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!(result.is_ok());
396 }
397
398 #[tokio::test]
399 async fn test_query_balance_with_block_id() {
400 let sdk = SDK::new(None, None, None);
402 let verbosity = Some(Verbosity::High);
403 let (rpc_address, _, _, _, _) = get_network_constants();
404
405 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!(result.is_ok());
420 }
421
422 #[tokio::test]
423 async fn test_query_balance_with_purse_identifier() {
424 let sdk = SDK::new(None, None, None);
426 let verbosity = Some(Verbosity::High);
427 let (rpc_address, _, _, _, _) = get_network_constants();
428
429 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!(result.is_ok());
444 }
445
446 #[tokio::test]
447 async fn test_query_balance_with_purse_identifier_as_string() {
448 let sdk = SDK::new(None, None, None);
450 let verbosity = Some(Verbosity::High);
451 let (rpc_address, _, _, _, _) = get_network_constants();
452
453 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!(result.is_ok());
468 }
469
470 #[tokio::test]
471 async fn test_query_balance_with_error() {
472 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 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!(result.is_err());
492 let err_string = result.err().unwrap().to_string();
493 assert!(err_string.contains(error_message));
494 }
495}