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#[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 #[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 #[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 #[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#[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 #[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 #[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 #[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 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 let sdk = SDK::new(None, None, None);
311 let error_message = "failed to parse node address as valid URL";
312
313 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!(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 let sdk = SDK::new(None, None, None);
336 let error_message = "Error: Missing purse identifier";
337
338 let result = sdk
340 .query_balance(None, None, None, None, None, None, None)
341 .await;
342
343 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 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 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!(result.is_ok());
372 }
373
374 #[tokio::test]
375 async fn test_query_balance_with_state_root_hash() {
376 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 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!(result.is_ok());
404 }
405
406 #[tokio::test]
407 async fn test_query_balance_with_block_id() {
408 let sdk = SDK::new(None, None, None);
410 let verbosity = Some(Verbosity::High);
411 let (rpc_address, _, _, _, _) = get_network_constants();
412
413 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!(result.is_ok());
428 }
429
430 #[tokio::test]
431 async fn test_query_balance_with_purse_identifier() {
432 let sdk = SDK::new(None, None, None);
434 let verbosity = Some(Verbosity::High);
435 let (rpc_address, _, _, _, _) = get_network_constants();
436
437 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!(result.is_ok());
452 }
453
454 #[tokio::test]
455 async fn test_query_balance_with_purse_identifier_as_string() {
456 let sdk = SDK::new(None, None, None);
458 let verbosity = Some(Verbosity::High);
459 let (rpc_address, _, _, _, _) = get_network_constants();
460
461 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!(result.is_ok());
476 }
477
478 #[tokio::test]
479 async fn test_query_balance_with_error() {
480 let sdk = SDK::new(Some("http://localhost".to_string()), None, None);
482
483 let error_message = "error sending request";
484
485 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!(result.is_err());
500 let err_string = result.err().unwrap().to_string();
501 assert!(err_string.contains(error_message));
502 }
503}