1use super::Sdk;
2use crate::backend::transactions::{
3 commit_transaction, create_new_transaction, get_transaction_details, get_transactions_list,
4};
5use crate::error::Result;
6use crate::types::transactions::PurchaseDetails;
7use crate::types::{
8 newtypes::EncryptionPin,
9 transactions::{TxInfo, TxList},
10};
11use crate::wallet::error::WalletError;
12use api_types::api::networks::ApiProtocol;
13use api_types::api::transactions::{ApiApplicationMetadata, ApiTxStatus, PurchaseModel, Reason};
14use etopay_wallet::TransactionIntent;
15use etopay_wallet::types::CryptoAmount;
16use etopay_wallet::types::GasCostEstimation;
17use log::{debug, info};
18
19impl Sdk {
20 pub async fn create_purchase_request(
38 &self,
39 receiver: &str,
40 amount: CryptoAmount,
41 product_hash: &str,
42 app_data: &str,
43 purchase_type: &str,
44 ) -> Result<String> {
45 info!("Creating a new purchase request");
46 let Some(_active_user) = &self.active_user else {
47 return Err(crate::Error::UserNotInitialized);
48 };
49
50 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
51 let access_token = self
52 .access_token
53 .as_ref()
54 .ok_or(crate::error::Error::MissingAccessToken)?;
55 let network = self.active_network.clone().ok_or(crate::Error::MissingNetwork)?;
56
57 let purchase_model = PurchaseModel::try_from(purchase_type.to_string()).map_err(crate::error::Error::Parse)?;
58
59 let reason = match purchase_model {
60 PurchaseModel::CLIK => Reason::LIKE,
61 PurchaseModel::CPIC => Reason::PURCHASE,
62 };
63
64 let metadata = ApiApplicationMetadata {
65 product_hash: product_hash.into(),
66 reason: reason.to_string(),
67 purchase_model: purchase_model.to_string(),
68 app_data: app_data.into(),
69 };
70 let response = create_new_transaction(config, access_token, receiver, network.key, amount, metadata).await?;
71 let purchase_id = response.index;
72 debug!("Created purchase request with id: {purchase_id}");
73 Ok(purchase_id)
74 }
75
76 pub async fn get_purchase_details(&self, purchase_id: &str) -> Result<PurchaseDetails> {
90 info!("Getting purchase details with id {purchase_id}");
91 let Some(_active_user) = &self.active_user else {
92 return Err(crate::Error::UserNotInitialized);
93 };
94
95 let access_token = self
96 .access_token
97 .as_ref()
98 .ok_or(crate::error::Error::MissingAccessToken)?;
99
100 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
101 let response = get_transaction_details(config, access_token, purchase_id).await?;
102
103 let details = PurchaseDetails {
104 system_address: response.system_address,
105 amount: response.amount.try_into()?,
106 status: response.status,
107 network: response.network,
108 };
109 Ok(details)
110 }
111
112 pub async fn confirm_purchase_request(&mut self, pin: &EncryptionPin, purchase_id: &str) -> Result<()> {
128 info!("Confirming purchase request with id {purchase_id}");
129 self.verify_pin(pin).await?;
130
131 let Some(repo) = &mut self.repo else {
132 return Err(crate::Error::UserRepoNotInitialized);
133 };
134 let Some(active_user) = &mut self.active_user else {
135 return Err(crate::Error::UserNotInitialized);
136 };
137
138 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
139 let access_token = self
140 .access_token
141 .as_ref()
142 .ok_or(crate::error::Error::MissingAccessToken)?;
143 let tx_details = get_transaction_details(config, access_token, purchase_id).await?;
144
145 debug!("Tx details: {:?}", tx_details);
146
147 if tx_details.status != ApiTxStatus::Valid {
148 return Err(WalletError::InvalidTransaction(format!(
149 "Transaction is not valid, current status: {}.",
150 tx_details.status
151 )))?;
152 }
153
154 let current_network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
155
156 let network = &tx_details.network;
159 if network.key != current_network.key {
160 return Err(WalletError::InvalidTransaction(format!(
161 "Transaction to commit is in network_key {:?}, but {:?} is the currently active current_network_key.",
162 network.key, current_network.key
163 )))?;
164 }
165
166 let wallet = active_user
167 .wallet_manager
168 .try_get(
169 config,
170 &self.access_token,
171 repo,
172 network,
173 pin,
174 &active_user.mnemonic_derivation_options,
175 )
176 .await?;
177
178 let amount = tx_details.amount.try_into()?;
179
180 let intent = TransactionIntent {
181 address_to: tx_details.system_address.clone(),
182 amount,
183 data: Some(purchase_id.to_string().into_bytes()),
184 };
185
186 let tx_id = wallet.send_amount(&intent).await?;
187
188 let newly_created_transaction = wallet.get_wallet_tx(&tx_id).await?;
190 let mut user = repo.get(&active_user.username)?;
191 user.wallet_transactions.push(newly_created_transaction);
192 let _ = repo.set_wallet_transactions(&active_user.username, user.wallet_transactions);
193
194 debug!("Transaction id on network: {tx_id}");
195
196 commit_transaction(config, access_token, purchase_id, &tx_id).await?;
197
198 Ok(())
199 }
200
201 pub async fn send_amount(
219 &mut self,
220 pin: &EncryptionPin,
221 address: &str,
222 amount: CryptoAmount,
223 data: Option<Vec<u8>>,
224 ) -> Result<String> {
225 info!("Sending amount {amount:?} to receiver {address}");
226 self.verify_pin(pin).await?;
227
228 let Some(repo) = &mut self.repo else {
229 return Err(crate::Error::UserRepoNotInitialized);
230 };
231 let Some(active_user) = &mut self.active_user else {
232 return Err(crate::Error::UserNotInitialized);
233 };
234
235 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
236 let network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
237
238 let wallet = active_user
239 .wallet_manager
240 .try_get(
241 config,
242 &self.access_token,
243 repo,
244 network,
245 pin,
246 &active_user.mnemonic_derivation_options,
247 )
248 .await?;
249
250 let intent = TransactionIntent {
252 address_to: address.to_string(),
253 amount,
254 data,
255 };
256
257 let tx_id = match network.protocol {
258 ApiProtocol::EvmERC20 {
259 chain_id: _,
260 contract_address: _,
261 } => wallet.send_amount(&intent).await?,
262 ApiProtocol::Evm { chain_id: _ } | ApiProtocol::IotaRebased { .. } => {
263 let tx_id = wallet.send_amount(&intent).await?;
264
265 let newly_created_transaction = wallet.get_wallet_tx(&tx_id).await?;
267 let user = repo.get(&active_user.username)?;
268 let mut wallet_transactions = user.wallet_transactions;
269 wallet_transactions.push(newly_created_transaction);
270 let _ = repo.set_wallet_transactions(&active_user.username, wallet_transactions);
271 tx_id
272 }
273 };
274
275 Ok(tx_id)
276 }
277
278 pub async fn estimate_gas(
296 &mut self,
297 pin: &EncryptionPin,
298 address: &str,
299 amount: CryptoAmount,
300 data: Option<Vec<u8>>,
301 ) -> Result<GasCostEstimation> {
302 info!("Estimating gas for sending amount {amount:?} to receiver {address}");
303 self.verify_pin(pin).await?;
304
305 let Some(repo) = &mut self.repo else {
306 return Err(crate::Error::UserRepoNotInitialized);
307 };
308 let Some(active_user) = &mut self.active_user else {
309 return Err(crate::Error::UserNotInitialized);
310 };
311
312 let config = self.config.as_mut().ok_or(crate::Error::MissingConfig)?;
313 let network = self.active_network.as_ref().ok_or(crate::Error::MissingNetwork)?;
314
315 let wallet = active_user
316 .wallet_manager
317 .try_get(
318 config,
319 &self.access_token,
320 repo,
321 network,
322 pin,
323 &active_user.mnemonic_derivation_options,
324 )
325 .await?;
326
327 let intent = TransactionIntent {
329 address_to: address.to_string(),
330 amount,
331 data,
332 };
333
334 let estimate = wallet.estimate_gas_cost(&intent).await?;
335 info!("Estimate: {estimate:?}");
336
337 Ok(estimate)
338 }
339 pub async fn get_tx_list(&self, start: u32, limit: u32) -> Result<TxList> {
354 info!("Getting list of transactions");
355 let config = self.config.as_ref().ok_or(crate::Error::MissingConfig)?;
356
357 let user = self.get_user().await?;
358
359 let access_token = self
360 .access_token
361 .as_ref()
362 .ok_or(crate::error::Error::MissingAccessToken)?;
363 let txs_list = get_transactions_list(config, access_token, start, limit).await?;
364 log::debug!("Txs list for user {}: {:?}", user.username, txs_list);
365
366 Ok(TxList {
367 txs: txs_list
368 .txs
369 .into_iter()
370 .map(|val| {
371 Ok(TxInfo {
372 date: Some(val.created_at),
373 sender: val.incoming.username,
374 receiver: val.outgoing.username,
375 reference_id: val.index,
376 amount: val.incoming.amount.0.try_into()?,
377 currency: val.incoming.network.display_symbol,
378 application_metadata: val.application_metadata,
379 status: val.status,
380 transaction_hash: val.incoming.transaction_id,
381 course: val.incoming.exchange_rate.0.try_into()?,
382 })
383 })
384 .collect::<Result<Vec<_>>>()?,
385 })
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::core::core_testing_utils::handle_error_test_cases;
393 use crate::testing_utils::{
394 AUTH_PROVIDER, ETH_NETWORK_KEY, HEADER_X_APP_NAME, IOTA_NETWORK_KEY, PURCHASE_ID, TOKEN, TX_INDEX, USERNAME,
395 example_api_network, example_api_networks, example_get_user, example_tx_details, example_tx_metadata,
396 example_wallet_borrow, example_wallet_tx_info, set_config,
397 };
398 use crate::types::users::KycType;
399 use crate::{
400 core::Sdk,
401 user::MockUserRepo,
402 wallet_manager::{MockWalletManager, WalletBorrow},
403 };
404 use api_types::api::transactions::GetTxsDetailsResponse;
405 use api_types::api::transactions::{
406 ApiTransaction, ApiTransferDetails, CreateTransactionResponse, GetTransactionDetailsResponse,
407 };
408 use api_types::api::viviswap::detail::SwapPaymentDetailKey;
409 use etopay_wallet::MockWalletUser;
410 use etopay_wallet::types::{WalletTxInfo, WalletTxStatus};
411 use mockito::Matcher;
412 use rstest::rstest;
413 use rust_decimal_macros::dec;
414
415 fn examples_wallet_tx_list() -> GetTxsDetailsResponse {
416 let main_address = "atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r".to_string();
417 let aux_address = "atoi1qpnrumvaex24dy0duulp4q07lpa00w20ze6jfd0xly422kdcjxzakzsz5kf".to_string();
418
419 GetTxsDetailsResponse {
420 txs: vec![ApiTransaction {
421 index: "1127f4ba-a0b8-4ecc-a928-bbebc401ac1a".to_string(),
422 status: ApiTxStatus::Completed,
423 created_at: "2022-12-09T09:30:33.52Z".to_string(),
424 updated_at: "2022-12-09T09:30:33.52Z".to_string(),
425 fee_rate: dec!(0.2).into(),
426 incoming: ApiTransferDetails {
427 transaction_id: Some(
428 "0x215322f8afdba4e22463a9d8a2e25d96ab0cb9ae6d56ee5ab13065068dae46c0".to_string(),
429 ),
430 block_id: Some("0x215322f8afdba4e22463a9d8a2e25d96ab0cb9ae6d56ee5ab13065068dae46c0".to_string()),
431 username: "satoshi".into(),
432 address: main_address.clone(),
433 amount: dec!(920.89).into(),
434 exchange_rate: dec!(0.06015).into(),
435 network: example_api_network(IOTA_NETWORK_KEY.to_string()),
436 },
437 outgoing: ApiTransferDetails {
438 transaction_id: Some(
439 "0x215322f8afdba4e22463a9d8a2e25d96ab0cb9ae6d56ee5ab13065068dae46c0".to_string(),
440 ),
441 block_id: Some("0x215322f8afdba4e22463a9d8a2e25d96ab0cb9ae6d56ee5ab13065068dae46c0".to_string()),
442 username: "hulk".into(),
443 address: aux_address.clone(),
444 amount: dec!(920.89).into(),
445 exchange_rate: dec!(0.06015).into(),
446 network: example_api_network(IOTA_NETWORK_KEY.to_string()),
447 },
448 application_metadata: Some(example_tx_metadata()),
449 }],
450 }
451 }
452
453 #[rstest]
454 #[case::success(Ok(CreateTransactionResponse { index: TX_INDEX.into() }))]
455 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
456 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
457 #[case::missing_config(Err(crate::Error::MissingConfig))]
458 #[tokio::test]
459 async fn test_create_purchase_request(#[case] expected: Result<CreateTransactionResponse>) {
460 let (mut srv, config, _cleanup) = set_config().await;
462 let mut sdk = Sdk::new(config).unwrap();
463 sdk.set_networks(example_api_networks());
464 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
465 let mut mock_server = None;
466
467 match &expected {
468 Ok(_) => {
469 sdk.active_user = Some(crate::types::users::ActiveUser {
470 username: USERNAME.into(),
471 wallet_manager: Box::new(MockWalletManager::new()),
472 mnemonic_derivation_options: Default::default(),
473 });
474 sdk.access_token = Some(TOKEN.clone());
475
476 let mock_response = CreateTransactionResponse { index: TX_INDEX.into() };
477 let body = serde_json::to_string(&mock_response).unwrap();
478
479 mock_server = Some(
480 srv.mock("POST", "/api/transactions/create")
481 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
482 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
483 .with_status(201)
484 .with_header("content-type", "application/json")
485 .with_body(body)
486 .expect(1)
487 .create(),
488 );
489 }
490 Err(error) => {
491 handle_error_test_cases(error, &mut sdk, 0, 0).await;
492 }
493 }
494
495 let amount = CryptoAmount::try_from(dec!(10.0)).unwrap();
497 let response = sdk
498 .create_purchase_request("receiver", amount, "hash", "app_data", "CLIK")
499 .await;
500
501 match expected {
503 Ok(resp) => {
504 assert_eq!(response.unwrap(), resp.index);
505 }
506 Err(ref expected_err) => {
507 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
508 }
509 }
510 if let Some(m) = mock_server {
511 m.assert();
512 }
513 }
514
515 #[rstest]
516 #[case::success(Ok(()))]
517 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
518 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
519 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
520 #[case::missing_config(Err(crate::Error::MissingConfig))]
521 #[case::invalid_tx(Err(crate::Error::Wallet(WalletError::InvalidTransaction(format!(
522 "Transaction is not valid, current status: {}.",
523 ApiTxStatus::Invalid(vec!["ReceiverNotVerified".to_string()])
524 )))))]
525 #[tokio::test]
526 async fn test_commit_transaction(#[case] expected: Result<()>) {
527 let (mut srv, config, _cleanup) = set_config().await;
529 let mut sdk = Sdk::new(config).unwrap();
530 sdk.set_networks(example_api_networks());
531 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
532 let mut mock_server_details = None;
533 let mut mock_server_commit = None;
534
535 match &expected {
536 Ok(_) => {
537 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 2, KycType::Undefined);
538 mock_user_repo
539 .expect_set_wallet_transactions()
540 .once()
541 .returning(|_, _| Ok(()));
542
543 sdk.repo = Some(Box::new(mock_user_repo));
544
545 let mut mock_wallet_manager = MockWalletManager::new();
546 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
547 let mut mock_wallet_user = MockWalletUser::new();
548 mock_wallet_user
549 .expect_send_amount()
550 .once()
551 .returning(|_| Ok("tx_id".to_string()));
552
553 mock_wallet_user.expect_get_wallet_tx().once().returning(|_| {
554 Ok(WalletTxInfo {
555 date: String::new(),
556 block_number_hash: None,
557 transaction_hash: "tx_hash".to_string(),
558 sender: "sender".to_string(),
559 receiver: "receiver".to_string(),
560 amount: CryptoAmount::ZERO,
561 network_key: "key".to_string(),
562 status: WalletTxStatus::Pending,
563 explorer_url: None,
564 })
565 });
566
567 Ok(WalletBorrow::from(mock_wallet_user))
568 });
569 sdk.active_user = Some(crate::types::users::ActiveUser {
570 username: USERNAME.into(),
571 wallet_manager: Box::new(mock_wallet_manager),
572 mnemonic_derivation_options: Default::default(),
573 });
574
575 sdk.access_token = Some(TOKEN.clone());
576
577 let mock_tx_response = GetTransactionDetailsResponse {
578 system_address: "".to_string(),
579 amount: dec!(5.0).into(),
580 status: ApiTxStatus::Valid,
581 network: example_api_network(IOTA_NETWORK_KEY.to_string()),
582 };
583 let body = serde_json::to_string(&mock_tx_response).unwrap();
584
585 mock_server_details = Some(
586 srv.mock("GET", "/api/transactions/details?index=123")
587 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
588 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
589 .with_status(200)
590 .with_body(&body)
591 .with_header("content-type", "application/json")
592 .create(),
593 );
594
595 mock_server_commit = Some(
596 srv.mock("POST", "/api/transactions/commit")
597 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
598 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
599 .with_status(202)
600 .expect(1)
601 .with_header("content-type", "application/json")
602 .create(),
603 );
604 }
605 Err(crate::Error::Wallet(WalletError::InvalidTransaction(_))) => {
606 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
607 sdk.repo = Some(Box::new(mock_user_repo));
608
609 let mock_wallet_manager = example_wallet_borrow();
610 sdk.active_user = Some(crate::types::users::ActiveUser {
611 username: USERNAME.into(),
612 wallet_manager: Box::new(mock_wallet_manager),
613 mnemonic_derivation_options: Default::default(),
614 });
615
616 sdk.access_token = Some(TOKEN.clone());
617
618 let mock_tx_response = GetTransactionDetailsResponse {
619 system_address: "".to_string(),
620 amount: dec!(5.0).into(),
621 status: ApiTxStatus::Invalid(vec!["ReceiverNotVerified".to_string()]),
622 network: example_api_network(IOTA_NETWORK_KEY.to_string()),
623 };
624 let body = serde_json::to_string(&mock_tx_response).unwrap();
625
626 mock_server_details = Some(
627 srv.mock("GET", "/api/transactions/details?index=123")
628 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
629 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
630 .with_status(200)
631 .with_body(&body)
632 .with_header("content-type", "application/json")
633 .create(),
634 );
635 }
636 Err(error) => {
637 handle_error_test_cases(error, &mut sdk, 1, 1).await;
638 }
639 }
640
641 let pin = EncryptionPin::try_from_string("123456").unwrap();
643 let response = sdk.confirm_purchase_request(&pin, PURCHASE_ID).await;
644
645 match expected {
647 Ok(_) => response.unwrap(),
648 Err(ref err) => {
649 assert_eq!(response.unwrap_err().to_string(), err.to_string());
650 }
651 }
652 if mock_server_details.is_some() & mock_server_commit.is_some() {
653 mock_server_details.unwrap().assert();
654 mock_server_commit.unwrap().assert();
655 }
656 }
657
658 #[rstest]
659 #[case::success(Ok(example_tx_details()))]
660 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
661 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
662 #[case::missing_config(Err(crate::Error::MissingConfig))]
663 #[tokio::test]
664 async fn test_get_purchase_details(#[case] expected: Result<GetTransactionDetailsResponse>) {
665 let (mut srv, config, _cleanup) = set_config().await;
667 let mut sdk = Sdk::new(config).unwrap();
668 let mut mock_server = None;
669
670 match &expected {
671 Ok(_) => {
672 sdk.repo = Some(Box::new(MockUserRepo::new()));
673 sdk.active_user = Some(crate::types::users::ActiveUser {
674 username: USERNAME.into(),
675 wallet_manager: Box::new(MockWalletManager::new()),
676 mnemonic_derivation_options: Default::default(),
677 });
678 sdk.access_token = Some(TOKEN.clone());
679
680 let mock_response = example_tx_details();
681 let body = serde_json::to_string(&mock_response).unwrap();
682
683 mock_server = Some(
684 srv.mock("GET", "/api/transactions/details?index=123")
685 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
686 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
687 .with_status(200)
688 .with_body(&body)
689 .with_header("content-type", "application/json")
690 .with_body(&body)
691 .create(),
692 );
693 }
694 Err(error) => {
695 handle_error_test_cases(error, &mut sdk, 0, 0).await;
696 }
697 }
698
699 let response = sdk.get_purchase_details(PURCHASE_ID).await;
701
702 match expected {
704 Ok(resp) => {
705 assert_eq!(
706 GetTransactionDetailsResponse {
707 system_address: response.as_ref().unwrap().system_address.clone(),
708 amount: response.as_ref().unwrap().amount.into(),
709 status: response.unwrap().status,
710 network: example_api_network(IOTA_NETWORK_KEY.to_string()),
711 },
712 resp
713 );
714 }
715 Err(ref expected_err) => {
716 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
717 }
718 }
719 if let Some(m) = mock_server {
720 m.assert();
721 }
722 }
723
724 #[rstest]
725 #[case::success(Ok(()))]
726 #[case::repo_init_error(Err(crate::Error::UserRepoNotInitialized))]
727 #[case::user_init_error(Err(crate::Error::UserNotInitialized))]
728 #[case::missing_config(Err(crate::Error::MissingConfig))]
729 #[tokio::test]
730 async fn test_send_amount(#[case] expected: Result<()>) {
731 let (_srv, config, _cleanup) = set_config().await;
733 let mut sdk = Sdk::new(config).unwrap();
734 sdk.set_networks(example_api_networks());
735 sdk.set_network(IOTA_NETWORK_KEY.to_string()).await.unwrap();
736
737 match &expected {
738 Ok(_) => {
739 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 2, KycType::Undefined);
740 mock_user_repo
741 .expect_set_wallet_transactions()
742 .once()
743 .returning(|_, _| Ok(()));
744 sdk.repo = Some(Box::new(mock_user_repo));
745
746 let mut mock_wallet_manager = MockWalletManager::new();
747 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
748 let mut mock_wallet = MockWalletUser::new();
749 mock_wallet
750 .expect_send_amount()
751 .times(1)
752 .returning(move |_| Ok(String::from("transaction id")));
753 mock_wallet
754 .expect_get_wallet_tx()
755 .once()
756 .returning(|_| Ok(example_wallet_tx_info()));
757 Ok(WalletBorrow::from(mock_wallet))
758 });
759
760 sdk.active_user = Some(crate::types::users::ActiveUser {
761 username: USERNAME.into(),
762 wallet_manager: Box::new(mock_wallet_manager),
763 mnemonic_derivation_options: Default::default(),
764 });
765 }
766 Err(error) => {
767 handle_error_test_cases(error, &mut sdk, 1, 0).await;
768 }
769 }
770
771 let amount = CryptoAmount::try_from(dec!(25.0)).unwrap();
773 let response = sdk
774 .send_amount(
775 &EncryptionPin::try_from_string("123456").unwrap(),
776 "smrq1...",
777 amount,
778 Some(String::from("test message").into_bytes()),
779 )
780 .await;
781
782 match expected {
784 Ok(_) => {
785 response.unwrap();
786 }
787 Err(ref expected_err) => {
788 assert_eq!(response.err().unwrap().to_string(), expected_err.to_string());
789 }
790 }
791 }
792
793 #[tokio::test]
794 async fn test_send_amount_with_eth_should_trigger_a_call_to_set_wallet_transaction() {
795 let (_srv, config, _cleanup) = set_config().await;
797 let mut sdk = Sdk::new(config).unwrap();
798 sdk.set_networks(example_api_networks());
799 sdk.set_network(ETH_NETWORK_KEY.to_string()).await.unwrap();
800
801 let wallet_transaction = WalletTxInfo {
802 date: String::new(),
803 block_number_hash: Some((0, String::new())),
804 transaction_hash: String::from("tx_id"),
805 receiver: String::new(),
806 sender: String::new(),
807 amount: unsafe { CryptoAmount::new_unchecked(dec!(5)) },
809 network_key: ETH_NETWORK_KEY.to_string(),
810 status: WalletTxStatus::Pending,
811 explorer_url: Some(String::new()),
812 };
813
814 let wallet_transactions = vec![wallet_transaction.clone()].to_owned();
815
816 let mut mock_user_repo = example_get_user(SwapPaymentDetailKey::Eth, false, 2, KycType::Undefined);
817 mock_user_repo
818 .expect_set_wallet_transactions()
819 .times(1)
820 .returning(move |_, expected_wallet_transactions| {
821 assert_eq!(wallet_transactions, expected_wallet_transactions);
822 Ok(())
823 });
824 sdk.repo = Some(Box::new(mock_user_repo));
825
826 let mut mock_wallet_manager = MockWalletManager::new();
827 mock_wallet_manager.expect_try_get().returning(move |_, _, _, _, _, _| {
828 let mut mock_wallet = MockWalletUser::new();
829 mock_wallet
830 .expect_send_amount()
831 .times(1)
832 .returning(move |_| Ok(String::from("tx_id")));
833
834 let value = wallet_transaction.clone();
835 mock_wallet
836 .expect_get_wallet_tx()
837 .times(1)
838 .returning(move |_| Ok(value.clone()));
839
840 Ok(WalletBorrow::from(mock_wallet))
841 });
842
843 sdk.active_user = Some(crate::types::users::ActiveUser {
844 username: USERNAME.into(),
845 wallet_manager: Box::new(mock_wallet_manager),
846 mnemonic_derivation_options: Default::default(),
847 });
848
849 let amount = CryptoAmount::try_from(dec!(5.0)).unwrap();
851 let response = sdk
852 .send_amount(
853 &EncryptionPin::try_from_string("123456").unwrap(),
854 "0xb0b...",
855 amount,
856 Some(String::from("test message").into_bytes()),
857 )
858 .await;
859
860 response.unwrap();
862 }
863
864 #[rstest]
865 #[case::success(Ok(examples_wallet_tx_list()))]
866 #[case::unauthorized(Err(crate::Error::MissingAccessToken))]
867 #[case::missing_config(Err(crate::Error::MissingConfig))]
868 #[tokio::test]
869 async fn test_get_tx_list(#[case] expected: Result<GetTxsDetailsResponse>) {
870 let (mut srv, config, _cleanup) = set_config().await;
872 let mut sdk = Sdk::new(config).unwrap();
873
874 let start = 1u32;
875 let limit = 5u32;
876
877 let mut mock_server = None;
878 match &expected {
879 Ok(_) => {
880 let mock_user_repo = example_get_user(SwapPaymentDetailKey::Iota, false, 1, KycType::Undefined);
881 sdk.repo = Some(Box::new(mock_user_repo));
882 sdk.active_user = Some(crate::types::users::ActiveUser {
883 username: USERNAME.into(),
884 wallet_manager: Box::new(MockWalletManager::new()),
885 mnemonic_derivation_options: Default::default(),
886 });
887 sdk.access_token = Some(TOKEN.clone());
888
889 let txs_details_mock_response = examples_wallet_tx_list();
890 let body = serde_json::to_string(&txs_details_mock_response).unwrap();
891
892 mock_server = Some(
893 srv.mock("GET", "/api/transactions/txs-details")
894 .match_header(HEADER_X_APP_NAME, AUTH_PROVIDER)
895 .match_header("authorization", format!("Bearer {}", TOKEN.as_str()).as_str())
896 .match_query(Matcher::Exact(format!("is_sender=false&start={start}&limit={limit}")))
897 .with_status(200)
898 .with_body(&body)
899 .expect(1)
900 .with_header("content-type", "application/json")
901 .with_body(&body)
902 .create(),
903 );
904 }
905 Err(error) => {
906 handle_error_test_cases(error, &mut sdk, 0, 1).await;
907 }
908 }
909
910 let response = sdk.get_tx_list(start, limit).await;
912
913 match expected {
915 Ok(_) => assert!(response.is_ok()),
916 Err(ref err) => {
917 assert_eq!(response.unwrap_err().to_string(), err.to_string());
918 }
919 }
920 if let Some(m) = mock_server {
921 m.assert();
922 }
923 }
924}