diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eabd10f670..042bf40240 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -113,6 +113,7 @@ dependencies { implementation(projects.domain.legacy) implementation(projects.libs.blockchainSdk) implementation(projects.domain.account) + implementation(projects.domain.account.status) implementation(projects.domain.models) implementation(projects.domain.core) api(projects.domain.common) diff --git a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt index 7c946ee340..fda29040bc 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt @@ -3,6 +3,8 @@ package com.tangem.tap.di.domain import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.account.fetcher.SingleAccountListFetcher import com.tangem.domain.account.repository.AccountsCRUDRepository +import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase +import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher import com.tangem.domain.account.tokens.MainAccountTokensMigration import com.tangem.domain.account.usecase.* import dagger.Module @@ -50,10 +52,12 @@ internal object AccountDomainModule { fun provideRecoverCryptoPortfolioUseCase( accountsCRUDRepository: AccountsCRUDRepository, mainAccountTokensMigration: MainAccountTokensMigration, + cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, ): RecoverCryptoPortfolioUseCase { return RecoverCryptoPortfolioUseCase( crudRepository = accountsCRUDRepository, mainAccountTokensMigration = mainAccountTokensMigration, + cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher, ) } diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt index d9376f4dd2..b11098e47f 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/di/AccountStatusUseCaseModule.kt @@ -6,6 +6,7 @@ import com.tangem.domain.account.status.usecase.GetAccountCurrencyByAddressUseCa import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2 import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase +import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.express.ExpressServiceFetcher @@ -70,14 +71,12 @@ internal object AccountStatusUseCaseModule { @Provides @Singleton - fun provideSaveCryptoCurrenciesUseCase( + fun provideManageCryptoCurrenciesUseCase( singleAccountListSupplier: SingleAccountListSupplier, accountsCRUDRepository: AccountsCRUDRepository, currenciesRepository: CurrenciesRepository, derivationsRepository: DerivationsRepository, - multiNetworkStatusFetcher: MultiNetworkStatusFetcher, - multiQuoteStatusFetcher: MultiQuoteStatusFetcher, - multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, stakingIdFactory: StakingIdFactory, networksCleaner: NetworksCleaner, stakingCleaner: StakingCleaner, @@ -89,9 +88,7 @@ internal object AccountStatusUseCaseModule { accountsCRUDRepository = accountsCRUDRepository, currenciesRepository = currenciesRepository, derivationsRepository = derivationsRepository, - multiNetworkStatusFetcher = multiNetworkStatusFetcher, - multiQuoteStatusFetcher = multiQuoteStatusFetcher, - multiYieldBalanceFetcher = multiYieldBalanceFetcher, + cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher, stakingIdFactory = stakingIdFactory, networksCleaner = networksCleaner, stakingCleaner = stakingCleaner, @@ -100,4 +97,24 @@ internal object AccountStatusUseCaseModule { dispatchers = dispatchers, ) } + + @Provides + @Singleton + fun provideCryptoCurrencyBalanceFetcher( + accountsCRUDRepository: AccountsCRUDRepository, + multiNetworkStatusFetcher: MultiNetworkStatusFetcher, + multiQuoteStatusFetcher: MultiQuoteStatusFetcher, + multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + stakingIdFactory: StakingIdFactory, + dispatchers: CoroutineDispatcherProvider, + ): CryptoCurrencyBalanceFetcher { + return CryptoCurrencyBalanceFetcher( + accountsCRUDRepository = accountsCRUDRepository, + multiNetworkStatusFetcher = multiNetworkStatusFetcher, + multiQuoteStatusFetcher = multiQuoteStatusFetcher, + multiYieldBalanceFetcher = multiYieldBalanceFetcher, + stakingIdFactory = stakingIdFactory, + parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.default), + ) + } } \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt index 919d4a6f5d..d37b6ac322 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/ManageCryptoCurrenciesUseCase.kt @@ -5,6 +5,7 @@ import arrow.core.raise.Raise import arrow.core.raise.catch import com.tangem.domain.account.producer.SingleAccountListProducer import com.tangem.domain.account.repository.AccountsCRUDRepository +import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.core.utils.eitherOn import com.tangem.domain.express.ExpressServiceFetcher @@ -14,11 +15,8 @@ import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher import com.tangem.domain.networks.utils.NetworksCleaner -import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.staking.StakingIdFactory -import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher import com.tangem.domain.staking.utils.StakingCleaner import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.wallets.derivations.DerivationsRepository @@ -30,14 +28,15 @@ import timber.log.Timber * Use case for saving crypto currencies to a specific account. * * @property singleAccountListSupplier Supplier to get account details. + * @property accountsCRUDRepository Repository for performing CRUD operations on accounts. * @property currenciesRepository Repository for managing currencies. * @property derivationsRepository Repository for deriving public keys. - * @property multiNetworkStatusFetcher Fetcher for updating network statuses. - * @property multiQuoteStatusFetcher Fetcher for updating quote statuses. - * @property multiYieldBalanceFetcher Fetcher for updating yield balances. + * @property cryptoCurrencyBalanceFetcher Fetcher for updating crypto currency balances. * @property stakingIdFactory Factory for creating staking IDs. * @property networksCleaner Cleaner for removing obsolete network data. * @property stakingCleaner Cleaner for removing obsolete staking data. + * @property expressServiceFetcher Fetcher for updating express service data. + * @property parallelUpdatingScope Coroutine scope for parallel updates. * @property dispatchers Coroutine dispatchers for managing threading. * [REDACTED_AUTHOR] @@ -48,9 +47,7 @@ class ManageCryptoCurrenciesUseCase( private val accountsCRUDRepository: AccountsCRUDRepository, private val currenciesRepository: CurrenciesRepository, private val derivationsRepository: DerivationsRepository, - private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, - private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, - private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, private val stakingIdFactory: StakingIdFactory, private val networksCleaner: NetworksCleaner, private val stakingCleaner: StakingCleaner, @@ -98,7 +95,7 @@ class ManageCryptoCurrenciesUseCase( launch { accountsCRUDRepository.syncTokens(userWalletId) } } - refreshBalances(userWalletId = userWalletId, currencies = modifiedCurrencyList.added) + cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = modifiedCurrencyList.added) refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total) clearMetadata(userWalletId = userWalletId, currencies = modifiedCurrencyList.removed) } @@ -132,7 +129,7 @@ class ManageCryptoCurrenciesUseCase( saveAccount(account = account.copy(cryptoCurrencies = modifiedCurrencyList.total.toSet())) parallelUpdatingScope.launch { - refreshBalances(userWalletId = userWalletId, currencies = listOf(tokenToAdd)) + cryptoCurrencyBalanceFetcher(userWalletId = userWalletId, currencies = listOf(tokenToAdd)) refreshExpress(userWalletId = userWalletId, currencies = modifiedCurrencyList.total) } @@ -253,46 +250,6 @@ class ManageCryptoCurrenciesUseCase( ) } - private suspend fun refreshBalances(userWalletId: UserWalletId, currencies: List) { - if (currencies.isEmpty()) return - - coroutineScope { - launch { refreshNetworks(userWalletId = userWalletId, currencies = currencies) } - launch { refreshYieldBalances(userWalletId = userWalletId, currencies = currencies) } - launch { refreshQuotes(currencies = currencies) } - } - } - - private suspend fun refreshNetworks(userWalletId: UserWalletId, currencies: List) { - multiNetworkStatusFetcher( - params = MultiNetworkStatusFetcher.Params( - userWalletId = userWalletId, - networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network), - ), - ) - - accountsCRUDRepository.syncTokens(userWalletId) - } - - private suspend fun refreshYieldBalances(userWalletId: UserWalletId, currencies: List) { - val stakingIds = currencies.mapNotNullTo(hashSetOf()) { - stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull() - } - - multiYieldBalanceFetcher( - params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds), - ) - } - - private suspend fun refreshQuotes(currencies: List) { - multiQuoteStatusFetcher( - params = MultiQuoteStatusFetcher.Params( - currenciesIds = currencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId }, - appCurrencyId = null, - ), - ) - } - private suspend fun refreshExpress(userWalletId: UserWalletId, currencies: List) { if (currencies.isEmpty()) return diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/RecoverCryptoPortfolioUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt similarity index 82% rename from domain/account/src/main/java/com/tangem/domain/account/usecase/RecoverCryptoPortfolioUseCase.kt rename to domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt index 8e52598bb2..feebdbedd1 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/RecoverCryptoPortfolioUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.account.usecase +package com.tangem.domain.account.status.usecase import arrow.core.Either import arrow.core.getOrElse @@ -9,6 +9,7 @@ import arrow.core.raise.ensure import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.account.repository.AccountsCRUDRepository +import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher import com.tangem.domain.account.tokens.MainAccountTokensMigration import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountId @@ -25,6 +26,7 @@ import com.tangem.domain.models.wallet.UserWalletId class RecoverCryptoPortfolioUseCase( private val crudRepository: AccountsCRUDRepository, private val mainAccountTokensMigration: MainAccountTokensMigration, + private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher, ) { /** @@ -53,6 +55,8 @@ class RecoverCryptoPortfolioUseCase( derivationIndex = recoveredAccount.derivationIndex, ) + refreshBalances(accountId = accountId) + recoveredAccount } @@ -93,6 +97,23 @@ class RecoverCryptoPortfolioUseCase( ) } + private suspend fun refreshBalances(accountId: AccountId): Either = either { + val currencies = catch( + block = { crudRepository.getAccountSync(accountId = accountId) }, + catch = { raise(Error.DataOperationFailed(cause = it)) }, + ) + .getOrElse { raise(Error.DataOperationFailed(message = "Account not found: $accountId")) } + .cryptoCurrencies + .toList() + + catch( + block = { + cryptoCurrencyBalanceFetcher(userWalletId = accountId.userWalletId, currencies = currencies) + }, + catch = { raise(Error.DataOperationFailed(cause = it)) }, + ) + } + /** * Represents possible errors that can occur during the add operation */ diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt new file mode 100644 index 0000000000..adde054406 --- /dev/null +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/utils/CryptoCurrencyBalanceFetcher.kt @@ -0,0 +1,124 @@ +package com.tangem.domain.account.status.utils + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.account.repository.AccountsCRUDRepository +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher +import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher +import com.tangem.domain.staking.StakingIdFactory +import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher +import com.tangem.domain.tokens.wallet.FetchingSource +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber + +/** + * Utility class responsible for fetching and refreshing the balances of various crypto currencies + * associated with a user's wallet. + * + * @property accountsCRUDRepository Repository for managing account data. + * @property multiNetworkStatusFetcher Fetcher for updating network statuses. + * @property multiQuoteStatusFetcher Fetcher for updating quote statuses. + * @property multiYieldBalanceFetcher Fetcher for updating yield balances. + * @property stakingIdFactory Factory for creating staking IDs. + * @property parallelUpdatingScope Coroutine scope for parallel balance updates. + * +[REDACTED_AUTHOR] + */ +class CryptoCurrencyBalanceFetcher( + private val accountsCRUDRepository: AccountsCRUDRepository, + private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher, + private val multiQuoteStatusFetcher: MultiQuoteStatusFetcher, + private val multiYieldBalanceFetcher: MultiYieldBalanceFetcher, + private val stakingIdFactory: StakingIdFactory, + private val parallelUpdatingScope: CoroutineScope, +) { + + private val mutex = Mutex() + + operator fun invoke(userWalletId: UserWalletId, currencies: List) { + if (currencies.isEmpty()) return + + parallelUpdatingScope.launch { + mutex.withLock { + refreshBalances(userWalletId, currencies) + } + } + } + + private suspend fun refreshBalances(userWalletId: UserWalletId, currencies: List) { + coroutineScope { + val results = listOf( + async { + FetchingSource.NETWORK to refreshNetworks(userWalletId = userWalletId, currencies = currencies) + }, + async { + FetchingSource.STAKING to refreshYieldBalances(userWalletId = userWalletId, currencies = currencies) + }, + async { FetchingSource.QUOTE to refreshQuotes(currencies = currencies) }, + ) + .awaitAll() + + val errors = results.mapNotNull { (source, maybeResult) -> + val error = maybeResult.leftOrNull() ?: return@mapNotNull null + + source to error + } + + check(errors.isEmpty()) { + val message = "Failed to fetch next sources for $userWalletId:\n" + + errors.joinToString(separator = "\n") { "${it.first.name} – ${it.second}" } + + Timber.e(message) + + message + } + } + } + + private suspend fun refreshNetworks( + userWalletId: UserWalletId, + currencies: List, + ): Either = either { + val either = multiNetworkStatusFetcher( + params = MultiNetworkStatusFetcher.Params( + userWalletId = userWalletId, + networks = currencies.mapTo(hashSetOf(), CryptoCurrency::network), + ), + ) + + arrow.core.raise.catch( + block = { accountsCRUDRepository.syncTokens(userWalletId) }, + catch = { + Timber.e(it, "Failed to sync tokens for wallet: $userWalletId") + }, + ) + + return either + } + + private suspend fun refreshYieldBalances( + userWalletId: UserWalletId, + currencies: List, + ): Either { + val stakingIds = currencies.mapNotNullTo(hashSetOf()) { + stakingIdFactory.create(userWalletId = userWalletId, cryptoCurrency = it).getOrNull() + } + + return multiYieldBalanceFetcher( + params = MultiYieldBalanceFetcher.Params(userWalletId = userWalletId, stakingIds = stakingIds), + ) + } + + private suspend fun refreshQuotes(currencies: List): Either { + return multiQuoteStatusFetcher( + params = MultiQuoteStatusFetcher.Params( + currenciesIds = currencies.mapNotNullTo(hashSetOf()) { it.id.rawCurrencyId }, + appCurrencyId = null, + ), + ) + } +} \ No newline at end of file diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/RecoverCryptoPortfolioUseCaseTest.kt b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt similarity index 75% rename from domain/account/src/test/kotlin/com/tangem/domain/account/usecase/RecoverCryptoPortfolioUseCaseTest.kt rename to domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt index 8a16fd034c..f6fe05e41d 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/RecoverCryptoPortfolioUseCaseTest.kt +++ b/domain/account/status/src/test/kotlin/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCaseTest.kt @@ -1,4 +1,4 @@ -package com.tangem.domain.account.usecase +package com.tangem.domain.account.status.usecase import arrow.core.None import arrow.core.left @@ -8,17 +8,17 @@ import com.google.common.truth.Truth import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.models.ArchivedAccount import com.tangem.domain.account.repository.AccountsCRUDRepository +import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase.Error.DataOperationFailed +import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher import com.tangem.domain.account.tokens.MainAccountTokensMigration -import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase.Error -import com.tangem.domain.account.utils.createAccount -import com.tangem.domain.models.account.AccountId -import com.tangem.domain.models.account.DerivationIndex +import com.tangem.domain.models.account.* import com.tangem.domain.models.wallet.UserWalletId import io.mockk.* import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import kotlin.random.Random /** [REDACTED_AUTHOR] @@ -28,14 +28,16 @@ class RecoverCryptoPortfolioUseCaseTest { private val crudRepository: AccountsCRUDRepository = mockk(relaxUnitFun = true) private val mainAccountTokensMigration: MainAccountTokensMigration = mockk() + private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher = mockk(relaxUnitFun = true) private val useCase = RecoverCryptoPortfolioUseCase( crudRepository = crudRepository, mainAccountTokensMigration = mainAccountTokensMigration, + cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher, ) @BeforeEach fun resetMocks() { - clearMocks(crudRepository) + clearMocks(crudRepository, mainAccountTokensMigration, cryptoCurrencyBalanceFetcher) } @Test @@ -57,6 +59,7 @@ class RecoverCryptoPortfolioUseCaseTest { coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns archivedAccount.toOption() coEvery { mainAccountTokensMigration.migrate(userWalletId, account.derivationIndex) } returns Unit.right() + coEvery { crudRepository.getAccountSync(account.accountId) } returns account.toOption() // Act val actual = useCase(account.accountId) @@ -69,21 +72,23 @@ class RecoverCryptoPortfolioUseCaseTest { crudRepository.getAccountListSync(userWalletId) crudRepository.getArchivedAccountSync(account.accountId) crudRepository.saveAccounts(updatedAccountList) + crudRepository.getAccountSync(account.accountId) + cryptoCurrencyBalanceFetcher(userWalletId, account.cryptoCurrencies.toList()) } } @Test fun `invoke should return error if getAccounts returns None`() = runTest { // Arrange - val accountId = AccountId.forCryptoPortfolio( + val accountId = AccountId.Companion.forCryptoPortfolio( userWalletId = userWalletId, - derivationIndex = DerivationIndex.Main, + derivationIndex = DerivationIndex.Companion.Main, ) coEvery { crudRepository.getAccountListSync(userWalletId) } returns None // Act - val actual = useCase(accountId).leftOrNull() as Error.DataOperationFailed + val actual = useCase(accountId).leftOrNull() as DataOperationFailed // Assert val expected = IllegalStateException("Account list not found for wallet $userWalletId") @@ -100,9 +105,9 @@ class RecoverCryptoPortfolioUseCaseTest { @Test fun `invoke should return error if getAccounts throws exception`() = runTest { // Arrange - val accountId = AccountId.forCryptoPortfolio( + val accountId = AccountId.Companion.forCryptoPortfolio( userWalletId = userWalletId, - derivationIndex = DerivationIndex.Main, + derivationIndex = DerivationIndex.Companion.Main, ) val exception = IllegalStateException("Test error") @@ -112,7 +117,7 @@ class RecoverCryptoPortfolioUseCaseTest { val actual = useCase(accountId) // Assert - val expected = Error.DataOperationFailed(exception).left() + val expected = DataOperationFailed(exception).left() Truth.assertThat(actual).isEqualTo(expected) coVerifySequence { crudRepository.getAccountListSync(userWalletId) } @@ -126,7 +131,7 @@ class RecoverCryptoPortfolioUseCaseTest { fun `invoke should return error if getArchivedAccount throws exception`() = runTest { // Arrange val account = createAccount(userWalletId) - val accountList = AccountList.empty(userWalletId) + val accountList = AccountList.Companion.empty(userWalletId) val exception = IllegalStateException("Test error") coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() @@ -136,7 +141,7 @@ class RecoverCryptoPortfolioUseCaseTest { val actual = useCase(account.accountId) // Assert - val expected = Error.DataOperationFailed(exception).left() + val expected = DataOperationFailed(exception).left() Truth.assertThat(actual).isEqualTo(expected) coVerifySequence { @@ -150,13 +155,13 @@ class RecoverCryptoPortfolioUseCaseTest { fun `invoke should return error if getArchivedAccount returns null`() = runTest { // Arrange val account = createAccount(userWalletId) - val accountList = AccountList.empty(userWalletId) + val accountList = AccountList.Companion.empty(userWalletId) coEvery { crudRepository.getAccountListSync(userWalletId) } returns accountList.toOption() coEvery { crudRepository.getArchivedAccountSync(account.accountId) } returns None // Act - val actual = useCase(account.accountId).leftOrNull() as Error.DataOperationFailed + val actual = useCase(account.accountId).leftOrNull() as DataOperationFailed // Assert val expected = IllegalStateException("Account not found: ${account.accountId}") @@ -174,7 +179,7 @@ class RecoverCryptoPortfolioUseCaseTest { fun `invoke should return error if saveAccounts throws exception`() = runTest { // Arrange val account = createAccount(userWalletId) - val accountList = AccountList.empty(userWalletId) + val accountList = AccountList.Companion.empty(userWalletId) val archivedAccount = ArchivedAccount( accountId = account.accountId, name = account.accountName, @@ -195,7 +200,7 @@ class RecoverCryptoPortfolioUseCaseTest { val actual = useCase(account.accountId) // Assert - val expected = Error.DataOperationFailed(exception).left() + val expected = DataOperationFailed(exception).left() Truth.assertThat(actual).isEqualTo(expected) coVerifySequence { @@ -205,6 +210,23 @@ class RecoverCryptoPortfolioUseCaseTest { } } + private fun createAccount( + userWalletId: UserWalletId, + name: String = "Test Account", + icon: CryptoPortfolioIcon = CryptoPortfolioIcon.ofDefaultCustomAccount(), + derivationIndex: Int = Random.nextInt(1, 21), + ): Account.CryptoPortfolio { + val derivationIndex = DerivationIndex(derivationIndex).getOrNull()!! + + return Account.CryptoPortfolio( + accountId = AccountId.forCryptoPortfolio(userWalletId = userWalletId, derivationIndex = derivationIndex), + accountName = AccountName(name).getOrNull()!!, + icon = icon, + derivationIndex = derivationIndex, + cryptoCurrencies = emptySet(), + ) + } + private companion object { val userWalletId = UserWalletId("011") } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt index efa9097358..a30ede50c9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/FetchingSource.kt @@ -5,7 +5,7 @@ package com.tangem.domain.tokens.wallet * [REDACTED_AUTHOR] */ -internal enum class FetchingSource { +enum class FetchingSource { NETWORK, QUOTE, STAKING, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt index 9c2bdc1c95..6ead9dcbc6 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/ArchivedAccountListModel.kt @@ -14,8 +14,8 @@ import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.message.ToastMessage import com.tangem.core.ui.utils.showErrorDialog import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase import com.tangem.domain.account.usecase.GetArchivedAccountsUseCase -import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase import com.tangem.domain.models.account.AccountId import com.tangem.features.account.ArchivedAccountListComponent import com.tangem.features.account.archived.entity.AccountArchivedUM diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/error/AccountFeatureError.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/error/AccountFeatureError.kt index dc12ce1e49..416a02cabb 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/error/AccountFeatureError.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/error/AccountFeatureError.kt @@ -1,9 +1,9 @@ package com.tangem.features.account.createedit.error import com.tangem.core.error.UniversalError +import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase import com.tangem.domain.account.usecase.AddCryptoPortfolioUseCase import com.tangem.domain.account.usecase.GetUnoccupiedAccountIndexUseCase -import com.tangem.domain.account.usecase.RecoverCryptoPortfolioUseCase import com.tangem.domain.account.usecase.UpdateCryptoPortfolioUseCase sealed interface AccountFeatureError : UniversalError {