diff --git a/data/manage-tokens/detekt-baseline-debug.xml b/data/manage-tokens/detekt-baseline-debug.xml deleted file mode 100644 index a9b6d430bb..0000000000 --- a/data/manage-tokens/detekt-baseline-debug.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - MultilineLambdaItParameter:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository${ // TODO: refactor https://tangem.atlassian.net/browse/AND-10006\ if (it.isTestnet() || it in excludedBlockchains || it in hotWalletExcludedBlockchains) { return@mapNotNull null } networkFactory.create( blockchain = it, extraDerivationPath = null, userWallet = userWallet, ) } - MultilineLambdaItParameter:ManageTokensUpdateFetcher.kt$ManageTokensUpdateFetcher${ if (it.key == toUpdate[index].key) { Batch(it.key, updatedItems) } else { null } } - UnsafeCallOnNullableType:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository$coinNetwork.decimalCount!! - - diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt index 1570f9a7c7..2617383978 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultCustomTokensRepository.kt @@ -13,11 +13,11 @@ import com.tangem.data.managetokens.utils.TokenAddressesConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.token.UserTokensResponseStore -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.common.extensions.canHandleBlockchain import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains import com.tangem.domain.card.common.extensions.supportedBlockchains import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.managetokens.model.AddCustomTokenForm import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.managetokens.repository.CustomTokensRepository @@ -32,7 +32,7 @@ import kotlinx.coroutines.withContext @Suppress("LongParameterList") internal class DefaultCustomTokensRepository( private val tangemTechApi: TangemTechApi, - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val userTokensResponseStore: UserTokensResponseStore, private val walletManagersFacade: WalletManagersFacade, private val excludedBlockchains: ExcludedBlockchains, @@ -96,9 +96,8 @@ internal class DefaultCustomTokensRepository( networkId: Network.ID, derivationPath: Network.DerivationPath, ): CryptoCurrency.Token? = withContext(dispatchers.io) { - val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "User wallet [$userWalletId] not found while finding token" - } + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) + val network = requireNotNull( networkFactory.create( networkId = networkId, @@ -138,7 +137,7 @@ internal class DefaultCustomTokensRepository( rawId = CryptoCurrency.RawID(coin.id), name = coin.name, symbol = coin.symbol, - decimals = coinNetwork.decimalCount!!.toInt(), + decimals = requireNotNull(coinNetwork.decimalCount).toInt(), contractAddress = tokenAddress, ) } else { @@ -152,9 +151,7 @@ internal class DefaultCustomTokensRepository( networkId: Network.ID, derivationPath: Network.DerivationPath, ): CryptoCurrency.Coin { - val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "User wallet [$userWalletId] not found while creating coin" - } + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) val network = requireNotNull( networkFactory.create( networkId = networkId, @@ -189,9 +186,7 @@ internal class DefaultCustomTokensRepository( derivationPath: Network.DerivationPath, formValues: AddCustomTokenForm.Validated.All, ): CryptoCurrency.Token { - val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "User wallet [$userWalletId] not found while creating custom token" - } + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) val network = requireNotNull( networkFactory.create( networkId = networkId, @@ -217,6 +212,7 @@ internal class DefaultCustomTokensRepository( ) } + @Deprecated("Use ManageCryptoCurrenciesUseCase") override suspend fun removeCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) = withContext(dispatchers.io) { val cryptoCurrency = when (currency) { @@ -273,20 +269,15 @@ internal class DefaultCustomTokensRepository( } override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List = withContext(dispatchers.io) { - val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "User wallet [$userWalletId] not found while getting supported networks" - } - - when (userWallet) { + when (val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)) { is UserWallet.Hot -> { - Blockchain.entries.mapNotNull { - // TODO: refactor [REDACTED_JIRA]\ - if (it.isTestnet() || it in excludedBlockchains || it in hotWalletExcludedBlockchains) { - return@mapNotNull null - } + Blockchain.entries.mapNotNull { blockchain -> + // TODO: refactor [REDACTED_JIRA] + val isExcluded = blockchain in excludedBlockchains || blockchain in hotWalletExcludedBlockchains + if (blockchain.isTestnet() || isExcluded) return@mapNotNull null networkFactory.create( - blockchain = it, + blockchain = blockchain, extraDerivationPath = null, userWallet = userWallet, ) diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index 84eea67a90..4786abe9db 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -22,11 +22,11 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.orDefault import com.tangem.datasource.local.config.testnet.TestnetTokensStorage import com.tangem.datasource.local.token.UserTokensResponseStore -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.card.common.TapWorkarounds.isTestCard import com.tangem.domain.card.common.extensions.* import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.managetokens.model.* import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.repository.ManageTokensRepository @@ -46,7 +46,7 @@ import com.tangem.utils.coroutines.runSuspendCatching @Suppress("LongParameterList", "LargeClass") internal class DefaultManageTokensRepository( private val tangemTechApi: TangemTechApi, - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val userTokenSaver: UserTokensSaver, private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher, private val userTokensResponseStore: UserTokensResponseStore, @@ -88,7 +88,7 @@ internal class DefaultManageTokensRepository( prefetchDistance = batchSize, batchSize = batchSize, subFetcher = { request, _, isFirstBatchFetching -> - val userWallet = request.params.userWalletId?.let(userWalletsStore::getSyncStrict) + val userWallet = request.params.userWalletId?.let(userWalletsListRepository::getSyncStrict) if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.isTestCard) { when (val params = request.params) { @@ -438,7 +438,7 @@ internal class DefaultManageTokensRepository( userWalletId: UserWalletId, sourceNetwork: SourceNetwork, ): CurrencyUnsupportedState? { - val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) + val userWallet = userWalletsListRepository.getSyncStrict(id = userWalletId) val blockchain = sourceNetwork.id.toBlockchain() return when (sourceNetwork) { @@ -452,7 +452,7 @@ internal class DefaultManageTokensRepository( rawNetworkId: String, isMainNetwork: Boolean, ): CurrencyUnsupportedState? { - val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) + val userWallet = userWalletsListRepository.getSyncStrict(id = userWalletId) val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId) ?: error("Can not create blockchain with given networkId -> $rawNetworkId") return if (isMainNetwork) { diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt index 97838b82c5..ea538bf3d2 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/di/ManageTokensDataModule.kt @@ -11,8 +11,8 @@ import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.config.testnet.TestnetTokensStorage import com.tangem.datasource.local.token.UserTokensResponseStore -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository import com.tangem.domain.walletmanager.WalletManagersFacade @@ -31,7 +31,7 @@ internal object ManageTokensDataModule { @Singleton fun provideManageTokensRepository( tangemTechApi: TangemTechApi, - userWalletsStore: UserWalletsStore, + userWalletsListRepository: UserWalletsListRepository, manageTokensUpdateFetcher: ManageTokensUpdateFetcher, userTokensResponseStore: UserTokensResponseStore, userTokensSaver: UserTokensSaver, @@ -45,7 +45,7 @@ internal object ManageTokensDataModule { ): ManageTokensRepository { return DefaultManageTokensRepository( tangemTechApi = tangemTechApi, - userWalletsStore = userWalletsStore, + userWalletsListRepository = userWalletsListRepository, manageTokensUpdateFetcher = manageTokensUpdateFetcher, userTokensResponseStore = userTokensResponseStore, userTokenSaver = userTokensSaver, @@ -63,7 +63,7 @@ internal object ManageTokensDataModule { @Singleton fun provideCustomTokensRepository( tangemTechApi: TangemTechApi, - userWalletsStore: UserWalletsStore, + userWalletsListRepository: UserWalletsListRepository, userTokensResponseStore: UserTokensResponseStore, walletManagersFacade: WalletManagersFacade, dispatchers: CoroutineDispatcherProvider, @@ -73,7 +73,7 @@ internal object ManageTokensDataModule { ): CustomTokensRepository { return DefaultCustomTokensRepository( tangemTechApi = tangemTechApi, - userWalletsStore = userWalletsStore, + userWalletsListRepository = userWalletsListRepository, userTokensResponseStore = userTokensResponseStore, walletManagersFacade = walletManagersFacade, excludedBlockchains = excludedBlockchains, diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt index 66bfc00bc5..8778b13b4d 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/utils/ManageTokensUpdateFetcher.kt @@ -51,9 +51,9 @@ internal class ManageTokensUpdateFetcher @Inject constructor() : update { BatchUpdateResult.Success( - data = mapNotNull { - if (it.key == toUpdate[index].key) { - Batch(it.key, updatedItems) + data = mapNotNull { batch -> + if (batch.key == toUpdate[index].key) { + Batch(batch.key, updatedItems) } else { null } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt index 2372e3639e..978d392878 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt @@ -16,8 +16,8 @@ import com.tangem.data.visa.config.VisaLibLoader import com.tangem.data.visa.utils.* import com.tangem.datasource.api.visa.VisaApi import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.requireColdWallet @@ -40,7 +40,7 @@ internal class DefaultVisaRepository @Inject constructor( private val visaLibLoader: VisaLibLoader, private val quotesFetcher: QuotesFetcher, private val cacheRegistry: CacheRegistry, - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val dispatchers: CoroutineDispatcherProvider, private val visaApiRequestMaker: VisaApiRequestMaker, private val visaApi: VisaApi, @@ -176,7 +176,7 @@ internal class DefaultVisaRepository @Inject constructor( } } - private suspend fun makeAddress(userWalletId: UserWalletId): String { + private fun makeAddress(userWalletId: UserWalletId): String { if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoAddress() val userWallet = findVisaUserWallet(userWalletId) @@ -219,9 +219,7 @@ internal class DefaultVisaRepository @Inject constructor( } private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { - val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "No user wallet found: $userWalletId" - } + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) if (!userWallet.requireColdWallet().scanResponse.cardTypesResolver.isVisaWallet()) { error("VISA wallet required: $userWalletId") } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt index a5f2592c55..ce95f24b90 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt @@ -7,8 +7,9 @@ import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest import com.tangem.datasource.api.visa.VisaApi -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.card.common.util.cardTypesResolver +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.update import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.requireColdWallet @@ -23,7 +24,7 @@ import javax.inject.Inject typealias VisaAuthorizationHeader = String internal class VisaApiRequestMaker @Inject constructor( - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val visaAuthApi: VisaApi, private val accessCodeDataConverter: AccessCodeDataConverter, private val dispatcherProvider: CoroutineDispatcherProvider, @@ -47,11 +48,11 @@ internal class VisaApiRequestMaker @Inject constructor( val newTokens = runCatching { refreshAccessTokens(authTokens.refreshToken) - }.getOrElse { - if (it is ApiResponseError.HttpException && - it.code == ApiResponseError.HttpException.Code.UNAUTHORIZED + }.getOrElse { throwable -> + if (throwable is ApiResponseError.HttpException && + throwable.code == ApiResponseError.HttpException.Code.UNAUTHORIZED ) { - userWalletsStore.update(userWalletId) { userWallet -> + userWalletsListRepository.update(userWalletId) { userWallet -> userWallet.requireColdWallet().copy( scanResponse = userWallet.scanResponse.copy( // visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired, @@ -62,7 +63,7 @@ internal class VisaApiRequestMaker @Inject constructor( throw RefreshTokenExpiredException() } - userWalletsStore.update(userWalletId) { userWallet -> + userWalletsListRepository.update(userWalletId) { userWallet -> userWallet.requireColdWallet().copy( scanResponse = userWallet.scanResponse.copy( // visaCardActivationStatus = VisaCardActivationStatus.Activated( @@ -92,7 +93,7 @@ internal class VisaApiRequestMaker @Inject constructor( @Throws private fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { - val userWallet = findVisaUserWallet(userWalletId) + findVisaUserWallet(userWalletId) // val status = userWallet.requireColdWallet().scanResponse.visaCardActivationStatus // ?: error("Visa card activation status not found") val status: VisaCardActivationStatus = TODO("Fix visaCardActivationStatus retrieval") @@ -105,9 +106,7 @@ internal class VisaApiRequestMaker @Inject constructor( } private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { - val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { - "No user wallet found: $userWalletId" - } + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) if (!userWallet.requireColdWallet().scanResponse.cardTypesResolver.isVisaWallet()) { error("VISA wallet required: $userWalletId") } diff --git a/data/wallets/build.gradle.kts b/data/wallets/build.gradle.kts index aafa1080db..512c474032 100644 --- a/data/wallets/build.gradle.kts +++ b/data/wallets/build.gradle.kts @@ -10,6 +10,10 @@ android { namespace = "com.tangem.data.wallet" } +tasks.withType().configureEach { + useJUnitPlatform() +} + dependencies { implementation(projects.data.common) @@ -47,9 +51,7 @@ dependencies { implementation(deps.timber) /** tests */ + testImplementation(projects.test.core) testImplementation(projects.common.test) - testImplementation(deps.test.junit) - testImplementation(deps.test.coroutine) - testImplementation(deps.test.truth) - testImplementation(deps.test.mockk) + testRuntimeOnly(deps.test.junit5.engine) } \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsPromoRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsPromoRepository.kt index de2bbc191e..5f50f1a7e5 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsPromoRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsPromoRepository.kt @@ -9,7 +9,7 @@ import com.tangem.datasource.api.tangemTech.models.BindWalletsByReferralCodeBody import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.storeObject -import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.wallets.models.AppsFlyerConversionData import com.tangem.domain.wallets.repository.WalletsPromoRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -19,7 +19,7 @@ import timber.log.Timber internal class DefaultWalletsPromoRepository( private val appPreferencesStore: AppPreferencesStore, private val tangemTechApi: TangemTechApi, - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val dispatchers: CoroutineDispatcherProvider, ) : WalletsPromoRepository { @@ -60,7 +60,7 @@ internal class DefaultWalletsPromoRepository( } private suspend fun bind(refcode: String, campaign: String?) { - val walletIds = userWalletsStore.userWalletsSync.map { it.walletId.stringValue } + val walletIds = userWalletsListRepository.userWallets.value.orEmpty().map { it.walletId.stringValue } val result = tangemTechApi.bindWalletsByReferralCode( body = BindWalletsByReferralCodeBody(walletIds = walletIds, refcode = refcode, campaign = campaign), diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index c9d66fa969..b6f5329aa5 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -24,8 +24,8 @@ import com.tangem.datasource.local.preferences.utils.getObjectMap import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.store -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus @@ -47,7 +47,7 @@ typealias SeedPhraseNotificationsStatuses = Map, private val dispatchers: CoroutineDispatcherProvider, private val authProvider: AuthProvider, @@ -151,7 +151,7 @@ internal class DefaultWalletsRepository( } private suspend fun fetchSeedPhraseNotificationStatus(userWalletId: UserWalletId) { - val userWallet = userWalletsStore.getSyncOrNull(key = userWalletId) + val userWallet = userWalletsListRepository.getSyncOrNull(id = userWalletId) if (userWallet != null && userWallet !is UserWallet.Cold) { updateNotificationVisibility(id = userWalletId, value = SeedPhraseNotificationsStatus.NOT_NEEDED) @@ -323,7 +323,7 @@ internal class DefaultWalletsRepository( } override suspend fun setWalletName(walletId: UserWalletId, walletName: String) = withContext(dispatchers.io) { - val userWallet = userWalletsStore.getSyncOrNull(key = walletId) + val userWallet = userWalletsListRepository.getSyncOrNull(id = walletId) tangemTechApi.updateWallet( walletId = walletId.stringValue, @@ -332,7 +332,7 @@ internal class DefaultWalletsRepository( } override suspend fun upgradeWallet(walletId: UserWalletId) = withContext(dispatchers.io) { - val userWallet = userWalletsStore.getSyncStrict(key = walletId) + val userWallet = userWalletsListRepository.getSyncStrict(id = walletId) tangemTechApi.updateWallet( walletId = walletId.stringValue, diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt index 3a3dcf27ac..e355e23257 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepository.kt @@ -1,9 +1,9 @@ package com.tangem.data.wallets.derivations -import com.tangem.common.CompletionResult +import arrow.core.getOrElse import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath -import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network @@ -16,16 +16,22 @@ import com.tangem.domain.wallets.usecase.BackendId import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject internal class DefaultDerivationsRepository @Inject constructor( - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val hotDerivationsRepository: HotMapDerivationsRepository, private val coldDerivationsRepository: ColdMapDerivationsRepository, private val dispatchers: CoroutineDispatcherProvider, ) : DerivationsRepository { override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List) { + if (currencies.isEmpty()) { + Timber.d("Nothing to derive") + return + } + derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) } @@ -34,7 +40,7 @@ internal class DefaultDerivationsRepository @Inject constructor( networkIds: List, accountIndex: DerivationIndex, ) { - val userWallet = userWalletsStore.getSyncStrict(userWalletId) + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) when (userWallet) { is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds) is UserWallet.Hot -> { @@ -46,7 +52,7 @@ internal class DefaultDerivationsRepository @Inject constructor( } override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List) { - val userWallet = userWalletsStore.getSyncStrict(userWalletId) + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) when (userWallet) { is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworks(userWallet, networks) is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeysByNetworks(userWallet, networks) @@ -59,7 +65,7 @@ internal class DefaultDerivationsRepository @Inject constructor( userWalletId: UserWalletId, derivations: Map>, ): Map { - val userWallet = userWalletsStore.getSyncStrict(userWalletId) + val userWallet = userWalletsListRepository.getSyncStrict(userWalletId) return when (userWallet) { is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeys(userWallet, derivations) is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeys(userWallet, derivations) @@ -73,7 +79,7 @@ internal class DefaultDerivationsRepository @Inject constructor( userWalletId: UserWalletId, networksWithDerivationPath: Map, ): Boolean { - return when (val userWallet = userWalletsStore.getSyncStrict(userWalletId)) { + return when (val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)) { is UserWallet.Cold -> coldDerivationsRepository.hasMissedDerivations(userWallet, networksWithDerivationPath) is UserWallet.Hot -> hotDerivationsRepository.hasMissedDerivations(userWallet, networksWithDerivationPath) } @@ -88,14 +94,7 @@ internal class DefaultDerivationsRepository @Inject constructor( return@withContext // No update needed } - val updateResult = userWalletsStore.update( - userWalletId = newUserWallet.walletId, - update = { userWalletToUpdate -> newUserWallet }, - ) - - when (updateResult) { - is CompletionResult.Failure -> throw updateResult.error - is CompletionResult.Success -> updateResult.data - } + userWalletsListRepository.saveWithoutLock(userWallet = newUserWallet, canOverride = true) + .getOrElse { throw IllegalStateException("Unable to update user wallet: $it") } } } \ No newline at end of file diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt index c5a72a7e5a..243b182f76 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/di/WalletsDataModule.kt @@ -15,8 +15,8 @@ import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.appsflyer.AppsFlyerStore import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository @@ -41,7 +41,7 @@ internal object WalletsDataModule { fun providesWalletsRepository( appPreferencesStore: AppPreferencesStore, tangemTechApi: TangemTechApi, - userWalletsStore: UserWalletsStore, + userWalletsListRepository: UserWalletsListRepository, dispatchers: CoroutineDispatcherProvider, authProvider: AuthProvider, walletServerBinder: WalletServerBinder, @@ -52,7 +52,7 @@ internal object WalletsDataModule { return DefaultWalletsRepository( appPreferencesStore = appPreferencesStore, tangemTechApi = tangemTechApi, - userWalletsStore = userWalletsStore, + userWalletsListRepository = userWalletsListRepository, seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()), dispatchers = dispatchers, authProvider = authProvider, @@ -74,13 +74,13 @@ internal object WalletsDataModule { fun provideWalletsPromoRepository( appPreferencesStore: AppPreferencesStore, tangemTechApi: TangemTechApi, - userWalletsStore: UserWalletsStore, + userWalletsListRepository: UserWalletsListRepository, dispatchers: CoroutineDispatcherProvider, ): WalletsPromoRepository { return DefaultWalletsPromoRepository( appPreferencesStore = appPreferencesStore, tangemTechApi = tangemTechApi, - userWalletsStore = userWalletsStore, + userWalletsListRepository = userWalletsListRepository, dispatchers = dispatchers, ) } diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt index 987dd65cc5..0484eb18f6 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/hot/DefaultHotMapDerivationsRepository.kt @@ -7,7 +7,7 @@ import com.tangem.common.extensions.toMapKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.data.common.network.NetworkFactory import com.tangem.data.wallets.derivations.MissedDerivationsFinder -import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network @@ -23,7 +23,7 @@ import timber.log.Timber import javax.inject.Inject internal class DefaultHotMapDerivationsRepository @Inject constructor( - private val userWalletsStore: UserWalletsStore, + private val userWalletsListRepository: UserWalletsListRepository, private val networkFactory: NetworkFactory, private val hotWalletAccessor: HotWalletAccessor, private val dispatchers: CoroutineDispatcherProvider, @@ -90,7 +90,7 @@ internal class DefaultHotMapDerivationsRepository @Inject constructor( // Get the updated user wallet from the store to ensure we have the latest data // in case it was modified during the derive operation - val updatedUserWallet = userWalletsStore.getSyncStrict(userWallet.walletId) as UserWallet.Hot + val updatedUserWallet = userWalletsListRepository.getSyncStrict(userWallet.walletId) as UserWallet.Hot val newKeys = result.responses.associate { ByteArrayKey(it.seedKey.publicKey) to ExtendedPublicKeysMap(it.publicKeys) } diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt index 30dd508d5b..71182a19c0 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/DefaultWalletsRepositoryTest.kt @@ -54,7 +54,7 @@ class DefaultWalletsRepositoryTest { repository = DefaultWalletsRepository( appPreferencesStore = appPreferenceStore, tangemTechApi = tangemTechApi, - userWalletsStore = mockk(), + userWalletsListRepository = mockk(), seedPhraseNotificationVisibilityStore = mockk(), dispatchers = dispatchers, authProvider = mockk(), @@ -222,7 +222,7 @@ class DefaultWalletsRepositoryTest { repository = DefaultWalletsRepository( appPreferencesStore = appPreferenceStore, tangemTechApi = tangemTechApi, - userWalletsStore = mockk(), + userWalletsListRepository = mockk(), seedPhraseNotificationVisibilityStore = mockk(), dispatchers = dispatchers, authProvider = authProvider, diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepositoryTest.kt b/data/wallets/src/test/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepositoryTest.kt index e9bccfd5a5..08a0df11ed 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepositoryTest.kt +++ b/data/wallets/src/test/java/com/tangem/data/wallets/derivations/DefaultDerivationsRepositoryTest.kt @@ -1,44 +1,36 @@ package com.tangem.data.wallets.derivations -import android.annotation.SuppressLint +import arrow.core.right import com.google.common.truth.Truth -import com.tangem.blockchainsdk.utils.ExcludedBlockchains -import com.tangem.common.CompletionResult import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory -import com.tangem.data.common.network.NetworkFactory -import com.tangem.data.wallets.cold.DefaultColdMapDerivationsRepository -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.card.ScanCardException import com.tangem.domain.card.configs.GenericCardConfig -import com.tangem.domain.card.configs.MultiWalletCardConfig +import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.operations.derivation.DerivationTaskResponse -import com.tangem.sdk.api.TangemSdkManager +import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk +import io.mockk.* import kotlinx.coroutines.test.runTest -import org.junit.Test +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance /** [REDACTED_AUTHOR] */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class DefaultDerivationsRepositoryTest { - private val tangemSdkManager = mockk() - private val userWalletsStore = mockk() + private val userWalletsListRepository = mockk() + private val coldDerivationsRepository: ColdMapDerivationsRepository = mockk() + private val repository = DefaultDerivationsRepository( - userWalletsStore = userWalletsStore, - dispatchers = TestingCoroutineDispatcherProvider(), + userWalletsListRepository = userWalletsListRepository, hotDerivationsRepository = mockk(), - coldDerivationsRepository = DefaultColdMapDerivationsRepository( - tangemSdkManager = tangemSdkManager, - networkFactory = NetworkFactory(excludedBlockchains = ExcludedBlockchains()), - dispatchers = TestingCoroutineDispatcherProvider(), - ), + coldDerivationsRepository = coldDerivationsRepository, + dispatchers = TestingCoroutineDispatcherProvider(), ) private val defaultUserWalletId = UserWalletId("011") @@ -51,127 +43,94 @@ internal class DefaultDerivationsRepositoryTest { hasBackupError = false, ) + @AfterEach + fun tearDown() { + clearMocks(userWalletsListRepository, coldDerivationsRepository) + } + @Test fun `error if userWalletId not found`() = runTest { - coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } throws IllegalStateException() + val currencies = MockCryptoCurrencyFactory(defaultUserWallet).ethereum.let(::listOf) + coEvery { userWalletsListRepository.getSyncStrict(defaultUserWalletId) } throws IllegalStateException() runCatching { - repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList()) + repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = currencies) } .onSuccess { error("Should throws exception") } .onFailure { Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java) } - coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } - coVerify(inverse = true) { tangemSdkManager.derivePublicKeys(null, any(), any()) } - coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } + coVerify(exactly = 1) { userWalletsListRepository.getSyncStrict(defaultUserWalletId) } + coVerify(inverse = true) { + coldDerivationsRepository.derivePublicKeysByNetworks(any(), any()) + userWalletsListRepository.saveWithoutLock(any(), any()) + } } - @SuppressLint("CheckResult") - @Test - fun `success if card is not supported derivations`() = runTest { - coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns defaultUserWallet - - repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList()) - - runCatching { } - .onSuccess { Truth.assertThat(it) } - .onFailure { - error("Should returns success") - } - - coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } - coVerify(inverse = true) { tangemSdkManager.derivePublicKeys(null, any(), any()) } - coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } - } - - @SuppressLint("CheckResult") @Test fun `success if currencies is empty`() = runTest { - val userWallet = defaultUserWallet.copy( - scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()), - ) - coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet + repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList()) - runCatching { repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList()) } - .onSuccess { Truth.assertThat(it) } - .onFailure { error("Should returns success") } - - coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } - coVerify(inverse = true) { tangemSdkManager.derivePublicKeys(null, any(), any()) } - coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } - } - - @SuppressLint("CheckResult") - @Test - fun `success if card already has derivations`() = runTest { - val userWallet = defaultUserWallet.copy( - scanResponse = MockScanResponseFactory.create( - cardConfig = MultiWalletCardConfig, - derivedKeys = DerivedKeysMocks.ethereumDerivedKeys, - ), - ) - - coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet - - runCatching { - repository.derivePublicKeys( - userWalletId = defaultUserWalletId, - currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf), - ) + coVerify(inverse = true) { + userWalletsListRepository.getSyncStrict(any()) + coldDerivationsRepository.derivePublicKeysByNetworks(any(), any()) + userWalletsListRepository.saveWithoutLock(any(), any()) } - .onSuccess { Truth.assertThat(it) } - .onFailure { error("Should returns success") } - - coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } - coVerify(inverse = true) { tangemSdkManager.derivePublicKeys(null, any(), any()) } - coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } } @Test - fun `error if tangemSdkManager throws exception`() = runTest { - val userWallet = defaultUserWallet.copy( - scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()), - ) - coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet - coEvery { tangemSdkManager.derivePublicKeys(null, any(), any()) } throws ScanCardException.UserCancelled() + fun `error if coldDerivationsRepository throws exception`() = runTest { + val currencies = MockCryptoCurrencyFactory(defaultUserWallet).ethereum.let(::listOf) + + coEvery { userWalletsListRepository.getSyncStrict(defaultUserWalletId) } returns defaultUserWallet + coEvery { + coldDerivationsRepository.derivePublicKeysByNetworks( + userWallet = defaultUserWallet, + networks = any(), + ) + } throws IllegalStateException() runCatching { - repository.derivePublicKeys( - userWalletId = defaultUserWalletId, - currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf), - ) + repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = currencies) } .onSuccess { error("Should throws exception") } - .onFailure { Truth.assertThat(it).isInstanceOf(ScanCardException.UserCancelled::class.java) } + .onFailure { Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java) } - coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } - coVerify(exactly = 1) { tangemSdkManager.derivePublicKeys(null, any(), any()) } - coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } - } - - @SuppressLint("CheckResult") - @Test - fun `success case`() = runTest { - val userWallet = defaultUserWallet.copy( - scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()), - ) - coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet - coEvery { tangemSdkManager.derivePublicKeys(null, any(), any()) } returns CompletionResult.Success( - DerivationTaskResponse(DerivedKeysMocks.ethereumDerivedKeys), - ) - coEvery { userWalletsStore.update(defaultUserWalletId, any()) } returns CompletionResult.Success(userWallet) - - runCatching { - repository.derivePublicKeys( - userWalletId = defaultUserWalletId, - currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf), + coVerifyOrder { + userWalletsListRepository.getSyncStrict(defaultUserWalletId) + coldDerivationsRepository.derivePublicKeysByNetworks( + userWallet = defaultUserWallet, + networks = currencies.map(CryptoCurrency.Coin::network), ) } - .onSuccess { Truth.assertThat(it) } - .onFailure { error("Should returns success but $it") } - coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } - coVerify(exactly = 1) { tangemSdkManager.derivePublicKeys(null, any(), any()) } - coVerify(exactly = 1) { userWalletsStore.update(defaultUserWalletId, any()) } + coVerify(inverse = true) { userWalletsListRepository.saveWithoutLock(any(), any()) } + } + + @Test + fun `success case`() = runTest { + val currencies = MockCryptoCurrencyFactory(defaultUserWallet).ethereum.let(::listOf) + val updatedWallet = defaultUserWallet.copy(cardsInWallet = setOf("AC01")) + + coEvery { userWalletsListRepository.getSyncStrict(defaultUserWalletId) } returns defaultUserWallet + coEvery { + coldDerivationsRepository.derivePublicKeysByNetworks( + userWallet = defaultUserWallet, + networks = currencies.map(CryptoCurrency.Coin::network), + ) + } returns updatedWallet + coEvery { + userWalletsListRepository.saveWithoutLock(updatedWallet, true) + } returns updatedWallet.right() + + repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = currencies) + + coVerifyOrder { + userWalletsListRepository.getSyncStrict(defaultUserWalletId) + coldDerivationsRepository.derivePublicKeysByNetworks( + userWallet = defaultUserWallet, + networks = currencies.map(CryptoCurrency.Coin::network), + ) + userWalletsListRepository.saveWithoutLock(updatedWallet, true) + } } } \ No newline at end of file diff --git a/domain/common/src/main/java/com/tangem/domain/common/wallets/UserWalletsListRepositoryExt.kt b/domain/common/src/main/java/com/tangem/domain/common/wallets/UserWalletsListRepositoryExt.kt new file mode 100644 index 0000000000..d1474f7fd8 --- /dev/null +++ b/domain/common/src/main/java/com/tangem/domain/common/wallets/UserWalletsListRepositoryExt.kt @@ -0,0 +1,32 @@ +package com.tangem.domain.common.wallets + +import arrow.core.Either +import arrow.core.raise.either +import com.tangem.domain.common.wallets.error.SaveWalletError +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId + +/** + * Update user wallet by [userWalletId] and return updated wallet. + * + * @param userWalletId The ID of the user wallet to update. + * @param transform A function that takes the current user wallet and returns the updated version. + * @return Either containing the updated user wallet on success or an error if the update fails. + */ +suspend fun UserWalletsListRepository.update( + userWalletId: UserWalletId, + transform: suspend (UserWallet) -> UserWallet, +): Either = either { + val userWallet = userWallets.value?.find { it.walletId == userWalletId } + requireNotNull(userWallet) { "Unable to find user wallet with provided ID: $userWalletId" } + + val updatedUserWallet = transform(userWallet) + + saveWithoutLock( + userWallet = updatedUserWallet, + canOverride = true, + ) + .bind() + + updatedUserWallet +} \ No newline at end of file