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