Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-10 19:50:01 +04:00
parent 06d1f35002
commit 6ed1946ac3
16 changed files with 191 additions and 220 deletions

View file

@ -1,9 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>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, ) }</ID>
<ID>MultilineLambdaItParameter:ManageTokensUpdateFetcher.kt$ManageTokensUpdateFetcher${ if (it.key == toUpdate[index].key) { Batch(it.key, updatedItems) } else { null } }</ID>
<ID>UnsafeCallOnNullableType:DefaultCustomTokensRepository.kt$DefaultCustomTokensRepository$coinNetwork.decimalCount!!</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -13,11 +13,11 @@ import com.tangem.data.managetokens.utils.TokenAddressesConverter
import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.token.UserTokensResponseStore 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.canHandleBlockchain
import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains
import com.tangem.domain.card.common.extensions.supportedBlockchains import com.tangem.domain.card.common.extensions.supportedBlockchains
import com.tangem.domain.card.common.util.cardTypesResolver 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.AddCustomTokenForm
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.CustomTokensRepository
@ -32,7 +32,7 @@ import kotlinx.coroutines.withContext
@Suppress("LongParameterList") @Suppress("LongParameterList")
internal class DefaultCustomTokensRepository( internal class DefaultCustomTokensRepository(
private val tangemTechApi: TangemTechApi, private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val userTokensResponseStore: UserTokensResponseStore, private val userTokensResponseStore: UserTokensResponseStore,
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
private val excludedBlockchains: ExcludedBlockchains, private val excludedBlockchains: ExcludedBlockchains,
@ -96,9 +96,8 @@ internal class DefaultCustomTokensRepository(
networkId: Network.ID, networkId: Network.ID,
derivationPath: Network.DerivationPath, derivationPath: Network.DerivationPath,
): CryptoCurrency.Token? = withContext(dispatchers.io) { ): CryptoCurrency.Token? = withContext(dispatchers.io) {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
"User wallet [$userWalletId] not found while finding token"
}
val network = requireNotNull( val network = requireNotNull(
networkFactory.create( networkFactory.create(
networkId = networkId, networkId = networkId,
@ -138,7 +137,7 @@ internal class DefaultCustomTokensRepository(
rawId = CryptoCurrency.RawID(coin.id), rawId = CryptoCurrency.RawID(coin.id),
name = coin.name, name = coin.name,
symbol = coin.symbol, symbol = coin.symbol,
decimals = coinNetwork.decimalCount!!.toInt(), decimals = requireNotNull(coinNetwork.decimalCount).toInt(),
contractAddress = tokenAddress, contractAddress = tokenAddress,
) )
} else { } else {
@ -152,9 +151,7 @@ internal class DefaultCustomTokensRepository(
networkId: Network.ID, networkId: Network.ID,
derivationPath: Network.DerivationPath, derivationPath: Network.DerivationPath,
): CryptoCurrency.Coin { ): CryptoCurrency.Coin {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
"User wallet [$userWalletId] not found while creating coin"
}
val network = requireNotNull( val network = requireNotNull(
networkFactory.create( networkFactory.create(
networkId = networkId, networkId = networkId,
@ -189,9 +186,7 @@ internal class DefaultCustomTokensRepository(
derivationPath: Network.DerivationPath, derivationPath: Network.DerivationPath,
formValues: AddCustomTokenForm.Validated.All, formValues: AddCustomTokenForm.Validated.All,
): CryptoCurrency.Token { ): CryptoCurrency.Token {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
"User wallet [$userWalletId] not found while creating custom token"
}
val network = requireNotNull( val network = requireNotNull(
networkFactory.create( networkFactory.create(
networkId = networkId, networkId = networkId,
@ -217,6 +212,7 @@ internal class DefaultCustomTokensRepository(
) )
} }
@Deprecated("Use ManageCryptoCurrenciesUseCase")
override suspend fun removeCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) = override suspend fun removeCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) =
withContext(dispatchers.io) { withContext(dispatchers.io) {
val cryptoCurrency = when (currency) { val cryptoCurrency = when (currency) {
@ -273,20 +269,15 @@ internal class DefaultCustomTokensRepository(
} }
override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> = withContext(dispatchers.io) { override suspend fun getSupportedNetworks(userWalletId: UserWalletId): List<Network> = withContext(dispatchers.io) {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { when (val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)) {
"User wallet [$userWalletId] not found while getting supported networks"
}
when (userWallet) {
is UserWallet.Hot -> { is UserWallet.Hot -> {
Blockchain.entries.mapNotNull { Blockchain.entries.mapNotNull { blockchain ->
// TODO: refactor [REDACTED_JIRA]\ // TODO: refactor [REDACTED_JIRA]
if (it.isTestnet() || it in excludedBlockchains || it in hotWalletExcludedBlockchains) { val isExcluded = blockchain in excludedBlockchains || blockchain in hotWalletExcludedBlockchains
return@mapNotNull null if (blockchain.isTestnet() || isExcluded) return@mapNotNull null
}
networkFactory.create( networkFactory.create(
blockchain = it, blockchain = blockchain,
extraDerivationPath = null, extraDerivationPath = null,
userWallet = userWallet, userWallet = userWallet,
) )

View file

@ -22,11 +22,11 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.orDefault import com.tangem.datasource.api.tangemTech.models.orDefault
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.token.UserTokensResponseStore 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.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.card.common.TapWorkarounds.isTestCard import com.tangem.domain.card.common.TapWorkarounds.isTestCard
import com.tangem.domain.card.common.extensions.* import com.tangem.domain.card.common.extensions.*
import com.tangem.domain.card.common.util.cardTypesResolver 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.*
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
import com.tangem.domain.managetokens.repository.ManageTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository
@ -46,7 +46,7 @@ import com.tangem.utils.coroutines.runSuspendCatching
@Suppress("LongParameterList", "LargeClass") @Suppress("LongParameterList", "LargeClass")
internal class DefaultManageTokensRepository( internal class DefaultManageTokensRepository(
private val tangemTechApi: TangemTechApi, private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val userTokenSaver: UserTokensSaver, private val userTokenSaver: UserTokensSaver,
private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher, private val manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
private val userTokensResponseStore: UserTokensResponseStore, private val userTokensResponseStore: UserTokensResponseStore,
@ -88,7 +88,7 @@ internal class DefaultManageTokensRepository(
prefetchDistance = batchSize, prefetchDistance = batchSize,
batchSize = batchSize, batchSize = batchSize,
subFetcher = { request, _, isFirstBatchFetching -> 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) { if (userWallet is UserWallet.Cold && userWallet.scanResponse.card.isTestCard) {
when (val params = request.params) { when (val params = request.params) {
@ -438,7 +438,7 @@ internal class DefaultManageTokensRepository(
userWalletId: UserWalletId, userWalletId: UserWalletId,
sourceNetwork: SourceNetwork, sourceNetwork: SourceNetwork,
): CurrencyUnsupportedState? { ): CurrencyUnsupportedState? {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) val userWallet = userWalletsListRepository.getSyncStrict(id = userWalletId)
val blockchain = sourceNetwork.id.toBlockchain() val blockchain = sourceNetwork.id.toBlockchain()
return when (sourceNetwork) { return when (sourceNetwork) {
@ -452,7 +452,7 @@ internal class DefaultManageTokensRepository(
rawNetworkId: String, rawNetworkId: String,
isMainNetwork: Boolean, isMainNetwork: Boolean,
): CurrencyUnsupportedState? { ): CurrencyUnsupportedState? {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId) val userWallet = userWalletsListRepository.getSyncStrict(id = userWalletId)
val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId) val blockchain = Blockchain.fromNetworkId(networkId = rawNetworkId)
?: error("Can not create blockchain with given networkId -> $rawNetworkId") ?: error("Can not create blockchain with given networkId -> $rawNetworkId")
return if (isMainNetwork) { return if (isMainNetwork) {

View file

@ -11,8 +11,8 @@ import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.config.testnet.TestnetTokensStorage import com.tangem.datasource.local.config.testnet.TestnetTokensStorage
import com.tangem.datasource.local.token.UserTokensResponseStore 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.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.managetokens.repository.CustomTokensRepository import com.tangem.domain.managetokens.repository.CustomTokensRepository
import com.tangem.domain.managetokens.repository.ManageTokensRepository import com.tangem.domain.managetokens.repository.ManageTokensRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
@ -31,7 +31,7 @@ internal object ManageTokensDataModule {
@Singleton @Singleton
fun provideManageTokensRepository( fun provideManageTokensRepository(
tangemTechApi: TangemTechApi, tangemTechApi: TangemTechApi,
userWalletsStore: UserWalletsStore, userWalletsListRepository: UserWalletsListRepository,
manageTokensUpdateFetcher: ManageTokensUpdateFetcher, manageTokensUpdateFetcher: ManageTokensUpdateFetcher,
userTokensResponseStore: UserTokensResponseStore, userTokensResponseStore: UserTokensResponseStore,
userTokensSaver: UserTokensSaver, userTokensSaver: UserTokensSaver,
@ -45,7 +45,7 @@ internal object ManageTokensDataModule {
): ManageTokensRepository { ): ManageTokensRepository {
return DefaultManageTokensRepository( return DefaultManageTokensRepository(
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore, userWalletsListRepository = userWalletsListRepository,
manageTokensUpdateFetcher = manageTokensUpdateFetcher, manageTokensUpdateFetcher = manageTokensUpdateFetcher,
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
userTokenSaver = userTokensSaver, userTokenSaver = userTokensSaver,
@ -63,7 +63,7 @@ internal object ManageTokensDataModule {
@Singleton @Singleton
fun provideCustomTokensRepository( fun provideCustomTokensRepository(
tangemTechApi: TangemTechApi, tangemTechApi: TangemTechApi,
userWalletsStore: UserWalletsStore, userWalletsListRepository: UserWalletsListRepository,
userTokensResponseStore: UserTokensResponseStore, userTokensResponseStore: UserTokensResponseStore,
walletManagersFacade: WalletManagersFacade, walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
@ -73,7 +73,7 @@ internal object ManageTokensDataModule {
): CustomTokensRepository { ): CustomTokensRepository {
return DefaultCustomTokensRepository( return DefaultCustomTokensRepository(
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore, userWalletsListRepository = userWalletsListRepository,
userTokensResponseStore = userTokensResponseStore, userTokensResponseStore = userTokensResponseStore,
walletManagersFacade = walletManagersFacade, walletManagersFacade = walletManagersFacade,
excludedBlockchains = excludedBlockchains, excludedBlockchains = excludedBlockchains,

View file

@ -51,9 +51,9 @@ internal class ManageTokensUpdateFetcher @Inject constructor() :
update { update {
BatchUpdateResult.Success( BatchUpdateResult.Success(
data = mapNotNull { data = mapNotNull { batch ->
if (it.key == toUpdate[index].key) { if (batch.key == toUpdate[index].key) {
Batch(it.key, updatedItems) Batch(batch.key, updatedItems)
} else { } else {
null null
} }

View file

@ -16,8 +16,8 @@ import com.tangem.data.visa.config.VisaLibLoader
import com.tangem.data.visa.utils.* import com.tangem.data.visa.utils.*
import com.tangem.datasource.api.visa.VisaApi import com.tangem.datasource.api.visa.VisaApi
import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse 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.card.common.util.cardTypesResolver
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.models.wallet.requireColdWallet
@ -40,7 +40,7 @@ internal class DefaultVisaRepository @Inject constructor(
private val visaLibLoader: VisaLibLoader, private val visaLibLoader: VisaLibLoader,
private val quotesFetcher: QuotesFetcher, private val quotesFetcher: QuotesFetcher,
private val cacheRegistry: CacheRegistry, private val cacheRegistry: CacheRegistry,
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val visaApiRequestMaker: VisaApiRequestMaker, private val visaApiRequestMaker: VisaApiRequestMaker,
private val visaApi: VisaApi, 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() if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoAddress()
val userWallet = findVisaUserWallet(userWalletId) val userWallet = findVisaUserWallet(userWalletId)
@ -219,9 +219,7 @@ internal class DefaultVisaRepository @Inject constructor(
} }
private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
"No user wallet found: $userWalletId"
}
if (!userWallet.requireColdWallet().scanResponse.cardTypesResolver.isVisaWallet()) { if (!userWallet.requireColdWallet().scanResponse.cardTypesResolver.isVisaWallet()) {
error("VISA wallet required: $userWalletId") error("VISA wallet required: $userWalletId")
} }

View file

@ -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.common.response.getOrThrow
import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest
import com.tangem.datasource.api.visa.VisaApi 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.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.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.requireColdWallet import com.tangem.domain.models.wallet.requireColdWallet
@ -23,7 +24,7 @@ import javax.inject.Inject
typealias VisaAuthorizationHeader = String typealias VisaAuthorizationHeader = String
internal class VisaApiRequestMaker @Inject constructor( internal class VisaApiRequestMaker @Inject constructor(
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val visaAuthApi: VisaApi, private val visaAuthApi: VisaApi,
private val accessCodeDataConverter: AccessCodeDataConverter, private val accessCodeDataConverter: AccessCodeDataConverter,
private val dispatcherProvider: CoroutineDispatcherProvider, private val dispatcherProvider: CoroutineDispatcherProvider,
@ -47,11 +48,11 @@ internal class VisaApiRequestMaker @Inject constructor(
val newTokens = runCatching { val newTokens = runCatching {
refreshAccessTokens(authTokens.refreshToken) refreshAccessTokens(authTokens.refreshToken)
}.getOrElse { }.getOrElse { throwable ->
if (it is ApiResponseError.HttpException && if (throwable is ApiResponseError.HttpException &&
it.code == ApiResponseError.HttpException.Code.UNAUTHORIZED throwable.code == ApiResponseError.HttpException.Code.UNAUTHORIZED
) { ) {
userWalletsStore.update(userWalletId) { userWallet -> userWalletsListRepository.update(userWalletId) { userWallet ->
userWallet.requireColdWallet().copy( userWallet.requireColdWallet().copy(
scanResponse = userWallet.scanResponse.copy( scanResponse = userWallet.scanResponse.copy(
// visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired, // visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired,
@ -62,7 +63,7 @@ internal class VisaApiRequestMaker @Inject constructor(
throw RefreshTokenExpiredException() throw RefreshTokenExpiredException()
} }
userWalletsStore.update(userWalletId) { userWallet -> userWalletsListRepository.update(userWalletId) { userWallet ->
userWallet.requireColdWallet().copy( userWallet.requireColdWallet().copy(
scanResponse = userWallet.scanResponse.copy( scanResponse = userWallet.scanResponse.copy(
// visaCardActivationStatus = VisaCardActivationStatus.Activated( // visaCardActivationStatus = VisaCardActivationStatus.Activated(
@ -92,7 +93,7 @@ internal class VisaApiRequestMaker @Inject constructor(
@Throws @Throws
private fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { private fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens {
val userWallet = findVisaUserWallet(userWalletId) findVisaUserWallet(userWalletId)
// val status = userWallet.requireColdWallet().scanResponse.visaCardActivationStatus // val status = userWallet.requireColdWallet().scanResponse.visaCardActivationStatus
// ?: error("Visa card activation status not found") // ?: error("Visa card activation status not found")
val status: VisaCardActivationStatus = TODO("Fix visaCardActivationStatus retrieval") val status: VisaCardActivationStatus = TODO("Fix visaCardActivationStatus retrieval")
@ -105,9 +106,7 @@ internal class VisaApiRequestMaker @Inject constructor(
} }
private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet {
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
"No user wallet found: $userWalletId"
}
if (!userWallet.requireColdWallet().scanResponse.cardTypesResolver.isVisaWallet()) { if (!userWallet.requireColdWallet().scanResponse.cardTypesResolver.isVisaWallet()) {
error("VISA wallet required: $userWalletId") error("VISA wallet required: $userWalletId")
} }

View file

@ -10,6 +10,10 @@ android {
namespace = "com.tangem.data.wallet" namespace = "com.tangem.data.wallet"
} }
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies { dependencies {
implementation(projects.data.common) implementation(projects.data.common)
@ -47,9 +51,7 @@ dependencies {
implementation(deps.timber) implementation(deps.timber)
/** tests */ /** tests */
testImplementation(projects.test.core)
testImplementation(projects.common.test) testImplementation(projects.common.test)
testImplementation(deps.test.junit) testRuntimeOnly(deps.test.junit5.engine)
testImplementation(deps.test.coroutine)
testImplementation(deps.test.truth)
testImplementation(deps.test.mockk)
} }

View file

@ -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.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject 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.models.AppsFlyerConversionData
import com.tangem.domain.wallets.repository.WalletsPromoRepository import com.tangem.domain.wallets.repository.WalletsPromoRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -19,7 +19,7 @@ import timber.log.Timber
internal class DefaultWalletsPromoRepository( internal class DefaultWalletsPromoRepository(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val tangemTechApi: TangemTechApi, private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : WalletsPromoRepository { ) : WalletsPromoRepository {
@ -60,7 +60,7 @@ internal class DefaultWalletsPromoRepository(
} }
private suspend fun bind(refcode: String, campaign: String?) { 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( val result = tangemTechApi.bindWalletsByReferralCode(
body = BindWalletsByReferralCodeBody(walletIds = walletIds, refcode = refcode, campaign = campaign), body = BindWalletsByReferralCodeBody(walletIds = walletIds, refcode = refcode, campaign = campaign),

View file

@ -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.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.getSyncOrNull
import com.tangem.datasource.local.preferences.utils.store 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.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
@ -47,7 +47,7 @@ typealias SeedPhraseNotificationsStatuses = Map<UserWalletId, SeedPhraseNotifica
internal class DefaultWalletsRepository( internal class DefaultWalletsRepository(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val tangemTechApi: TangemTechApi, private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<SeedPhraseNotificationsStatuses>, private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<SeedPhraseNotificationsStatuses>,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val authProvider: AuthProvider, private val authProvider: AuthProvider,
@ -151,7 +151,7 @@ internal class DefaultWalletsRepository(
} }
private suspend fun fetchSeedPhraseNotificationStatus(userWalletId: UserWalletId) { 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) { if (userWallet != null && userWallet !is UserWallet.Cold) {
updateNotificationVisibility(id = userWalletId, value = SeedPhraseNotificationsStatus.NOT_NEEDED) 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) { 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( tangemTechApi.updateWallet(
walletId = walletId.stringValue, walletId = walletId.stringValue,
@ -332,7 +332,7 @@ internal class DefaultWalletsRepository(
} }
override suspend fun upgradeWallet(walletId: UserWalletId) = withContext(dispatchers.io) { override suspend fun upgradeWallet(walletId: UserWalletId) = withContext(dispatchers.io) {
val userWallet = userWalletsStore.getSyncStrict(key = walletId) val userWallet = userWalletsListRepository.getSyncStrict(id = walletId)
tangemTechApi.updateWallet( tangemTechApi.updateWallet(
walletId = walletId.stringValue, walletId = walletId.stringValue,

View file

@ -1,9 +1,9 @@
package com.tangem.data.wallets.derivations package com.tangem.data.wallets.derivations
import com.tangem.common.CompletionResult import arrow.core.getOrElse
import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.ByteArrayKey
import com.tangem.crypto.hdWallet.DerivationPath 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.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network 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.operations.derivation.ExtendedPublicKeysMap
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class DefaultDerivationsRepository @Inject constructor( internal class DefaultDerivationsRepository @Inject constructor(
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val hotDerivationsRepository: HotMapDerivationsRepository, private val hotDerivationsRepository: HotMapDerivationsRepository,
private val coldDerivationsRepository: ColdMapDerivationsRepository, private val coldDerivationsRepository: ColdMapDerivationsRepository,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : DerivationsRepository { ) : DerivationsRepository {
override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) { override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
if (currencies.isEmpty()) {
Timber.d("Nothing to derive")
return
}
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network)) derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
} }
@ -34,7 +40,7 @@ internal class DefaultDerivationsRepository @Inject constructor(
networkIds: List<Network.RawID>, networkIds: List<Network.RawID>,
accountIndex: DerivationIndex, accountIndex: DerivationIndex,
) { ) {
val userWallet = userWalletsStore.getSyncStrict(userWalletId) val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
when (userWallet) { when (userWallet) {
is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds) is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworkIds(userWallet, networkIds)
is UserWallet.Hot -> { is UserWallet.Hot -> {
@ -46,7 +52,7 @@ internal class DefaultDerivationsRepository @Inject constructor(
} }
override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>) { override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>) {
val userWallet = userWalletsStore.getSyncStrict(userWalletId) val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
when (userWallet) { when (userWallet) {
is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworks(userWallet, networks) is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeysByNetworks(userWallet, networks)
is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeysByNetworks(userWallet, networks) is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeysByNetworks(userWallet, networks)
@ -59,7 +65,7 @@ internal class DefaultDerivationsRepository @Inject constructor(
userWalletId: UserWalletId, userWalletId: UserWalletId,
derivations: Map<ByteArrayKey, List<DerivationPath>>, derivations: Map<ByteArrayKey, List<DerivationPath>>,
): Map<ByteArrayKey, ExtendedPublicKeysMap> { ): Map<ByteArrayKey, ExtendedPublicKeysMap> {
val userWallet = userWalletsStore.getSyncStrict(userWalletId) val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
return when (userWallet) { return when (userWallet) {
is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeys(userWallet, derivations) is UserWallet.Cold -> coldDerivationsRepository.derivePublicKeys(userWallet, derivations)
is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeys(userWallet, derivations) is UserWallet.Hot -> hotDerivationsRepository.derivePublicKeys(userWallet, derivations)
@ -73,7 +79,7 @@ internal class DefaultDerivationsRepository @Inject constructor(
userWalletId: UserWalletId, userWalletId: UserWalletId,
networksWithDerivationPath: Map<BackendId, String?>, networksWithDerivationPath: Map<BackendId, String?>,
): Boolean { ): Boolean {
return when (val userWallet = userWalletsStore.getSyncStrict(userWalletId)) { return when (val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)) {
is UserWallet.Cold -> coldDerivationsRepository.hasMissedDerivations(userWallet, networksWithDerivationPath) is UserWallet.Cold -> coldDerivationsRepository.hasMissedDerivations(userWallet, networksWithDerivationPath)
is UserWallet.Hot -> hotDerivationsRepository.hasMissedDerivations(userWallet, networksWithDerivationPath) is UserWallet.Hot -> hotDerivationsRepository.hasMissedDerivations(userWallet, networksWithDerivationPath)
} }
@ -88,14 +94,7 @@ internal class DefaultDerivationsRepository @Inject constructor(
return@withContext // No update needed return@withContext // No update needed
} }
val updateResult = userWalletsStore.update( userWalletsListRepository.saveWithoutLock(userWallet = newUserWallet, canOverride = true)
userWalletId = newUserWallet.walletId, .getOrElse { throw IllegalStateException("Unable to update user wallet: $it") }
update = { userWalletToUpdate -> newUserWallet },
)
when (updateResult) {
is CompletionResult.Failure -> throw updateResult.error
is CompletionResult.Success -> updateResult.data
}
} }
} }

View file

@ -15,8 +15,8 @@ import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.appsflyer.AppsFlyerStore import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.datasource.local.datastore.RuntimeStateStore import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.preferences.AppPreferencesStore 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.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository
import com.tangem.domain.wallets.derivations.DerivationsRepository import com.tangem.domain.wallets.derivations.DerivationsRepository
import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository import com.tangem.domain.wallets.derivations.HotMapDerivationsRepository
@ -41,7 +41,7 @@ internal object WalletsDataModule {
fun providesWalletsRepository( fun providesWalletsRepository(
appPreferencesStore: AppPreferencesStore, appPreferencesStore: AppPreferencesStore,
tangemTechApi: TangemTechApi, tangemTechApi: TangemTechApi,
userWalletsStore: UserWalletsStore, userWalletsListRepository: UserWalletsListRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
authProvider: AuthProvider, authProvider: AuthProvider,
walletServerBinder: WalletServerBinder, walletServerBinder: WalletServerBinder,
@ -52,7 +52,7 @@ internal object WalletsDataModule {
return DefaultWalletsRepository( return DefaultWalletsRepository(
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore, userWalletsListRepository = userWalletsListRepository,
seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()), seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()),
dispatchers = dispatchers, dispatchers = dispatchers,
authProvider = authProvider, authProvider = authProvider,
@ -74,13 +74,13 @@ internal object WalletsDataModule {
fun provideWalletsPromoRepository( fun provideWalletsPromoRepository(
appPreferencesStore: AppPreferencesStore, appPreferencesStore: AppPreferencesStore,
tangemTechApi: TangemTechApi, tangemTechApi: TangemTechApi,
userWalletsStore: UserWalletsStore, userWalletsListRepository: UserWalletsListRepository,
dispatchers: CoroutineDispatcherProvider, dispatchers: CoroutineDispatcherProvider,
): WalletsPromoRepository { ): WalletsPromoRepository {
return DefaultWalletsPromoRepository( return DefaultWalletsPromoRepository(
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore, userWalletsListRepository = userWalletsListRepository,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

View file

@ -7,7 +7,7 @@ import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.data.common.network.NetworkFactory import com.tangem.data.common.network.NetworkFactory
import com.tangem.data.wallets.derivations.MissedDerivationsFinder 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.account.DerivationIndex
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
@ -23,7 +23,7 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class DefaultHotMapDerivationsRepository @Inject constructor( internal class DefaultHotMapDerivationsRepository @Inject constructor(
private val userWalletsStore: UserWalletsStore, private val userWalletsListRepository: UserWalletsListRepository,
private val networkFactory: NetworkFactory, private val networkFactory: NetworkFactory,
private val hotWalletAccessor: HotWalletAccessor, private val hotWalletAccessor: HotWalletAccessor,
private val dispatchers: CoroutineDispatcherProvider, 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 // Get the updated user wallet from the store to ensure we have the latest data
// in case it was modified during the derive operation // 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 = val newKeys =
result.responses.associate { ByteArrayKey(it.seedKey.publicKey) to ExtendedPublicKeysMap(it.publicKeys) } result.responses.associate { ByteArrayKey(it.seedKey.publicKey) to ExtendedPublicKeysMap(it.publicKeys) }

View file

@ -54,7 +54,7 @@ class DefaultWalletsRepositoryTest {
repository = DefaultWalletsRepository( repository = DefaultWalletsRepository(
appPreferencesStore = appPreferenceStore, appPreferencesStore = appPreferenceStore,
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore = mockk(), userWalletsListRepository = mockk(),
seedPhraseNotificationVisibilityStore = mockk(), seedPhraseNotificationVisibilityStore = mockk(),
dispatchers = dispatchers, dispatchers = dispatchers,
authProvider = mockk(), authProvider = mockk(),
@ -222,7 +222,7 @@ class DefaultWalletsRepositoryTest {
repository = DefaultWalletsRepository( repository = DefaultWalletsRepository(
appPreferencesStore = appPreferenceStore, appPreferencesStore = appPreferenceStore,
tangemTechApi = tangemTechApi, tangemTechApi = tangemTechApi,
userWalletsStore = mockk(), userWalletsListRepository = mockk(),
seedPhraseNotificationVisibilityStore = mockk(), seedPhraseNotificationVisibilityStore = mockk(),
dispatchers = dispatchers, dispatchers = dispatchers,
authProvider = authProvider, authProvider = authProvider,

View file

@ -1,44 +1,36 @@
package com.tangem.data.wallets.derivations package com.tangem.data.wallets.derivations
import android.annotation.SuppressLint import arrow.core.right
import com.google.common.truth.Truth 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.card.MockScanResponseFactory
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory 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.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.UserWallet
import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery import io.mockk.*
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.test.runTest 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] [REDACTED_AUTHOR]
*/ */
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class DefaultDerivationsRepositoryTest { internal class DefaultDerivationsRepositoryTest {
private val tangemSdkManager = mockk<TangemSdkManager>() private val userWalletsListRepository = mockk<UserWalletsListRepository>()
private val userWalletsStore = mockk<UserWalletsStore>() private val coldDerivationsRepository: ColdMapDerivationsRepository = mockk()
private val repository = DefaultDerivationsRepository( private val repository = DefaultDerivationsRepository(
userWalletsStore = userWalletsStore, userWalletsListRepository = userWalletsListRepository,
dispatchers = TestingCoroutineDispatcherProvider(),
hotDerivationsRepository = mockk(), hotDerivationsRepository = mockk(),
coldDerivationsRepository = DefaultColdMapDerivationsRepository( coldDerivationsRepository = coldDerivationsRepository,
tangemSdkManager = tangemSdkManager, dispatchers = TestingCoroutineDispatcherProvider(),
networkFactory = NetworkFactory(excludedBlockchains = ExcludedBlockchains()),
dispatchers = TestingCoroutineDispatcherProvider(),
),
) )
private val defaultUserWalletId = UserWalletId("011") private val defaultUserWalletId = UserWalletId("011")
@ -51,127 +43,94 @@ internal class DefaultDerivationsRepositoryTest {
hasBackupError = false, hasBackupError = false,
) )
@AfterEach
fun tearDown() {
clearMocks(userWalletsListRepository, coldDerivationsRepository)
}
@Test @Test
fun `error if userWalletId not found`() = runTest { 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 { runCatching {
repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList()) repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = currencies)
} }
.onSuccess { error("Should throws exception") } .onSuccess { error("Should throws exception") }
.onFailure { Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java) } .onFailure { Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java) }
coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } coVerify(exactly = 1) { userWalletsListRepository.getSyncStrict(defaultUserWalletId) }
coVerify(inverse = true) { tangemSdkManager.derivePublicKeys(null, any(), any()) } coVerify(inverse = true) {
coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } 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 @Test
fun `success if currencies is empty`() = runTest { fun `success if currencies is empty`() = runTest {
val userWallet = defaultUserWallet.copy( repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList())
scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()),
)
coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet
runCatching { repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = emptyList()) } coVerify(inverse = true) {
.onSuccess { Truth.assertThat(it) } userWalletsListRepository.getSyncStrict(any())
.onFailure { error("Should returns success") } coldDerivationsRepository.derivePublicKeysByNetworks(any(), any())
userWalletsListRepository.saveWithoutLock(any(), any())
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),
)
} }
.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 @Test
fun `error if tangemSdkManager throws exception`() = runTest { fun `error if coldDerivationsRepository throws exception`() = runTest {
val userWallet = defaultUserWallet.copy( val currencies = MockCryptoCurrencyFactory(defaultUserWallet).ethereum.let(::listOf)
scanResponse = MockScanResponseFactory.create(cardConfig = MultiWalletCardConfig, derivedKeys = emptyMap()),
) coEvery { userWalletsListRepository.getSyncStrict(defaultUserWalletId) } returns defaultUserWallet
coEvery { userWalletsStore.getSyncStrict(defaultUserWalletId) } returns userWallet coEvery {
coEvery { tangemSdkManager.derivePublicKeys(null, any(), any()) } throws ScanCardException.UserCancelled() coldDerivationsRepository.derivePublicKeysByNetworks(
userWallet = defaultUserWallet,
networks = any(),
)
} throws IllegalStateException()
runCatching { runCatching {
repository.derivePublicKeys( repository.derivePublicKeys(userWalletId = defaultUserWalletId, currencies = currencies)
userWalletId = defaultUserWalletId,
currencies = MockCryptoCurrencyFactory(userWallet).ethereum.let(::listOf),
)
} }
.onSuccess { error("Should throws exception") } .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) } coVerifyOrder {
coVerify(exactly = 1) { tangemSdkManager.derivePublicKeys(null, any(), any()) } userWalletsListRepository.getSyncStrict(defaultUserWalletId)
coVerify(inverse = true) { userWalletsStore.update(defaultUserWalletId, any()) } coldDerivationsRepository.derivePublicKeysByNetworks(
} userWallet = defaultUserWallet,
networks = currencies.map(CryptoCurrency.Coin::network),
@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),
) )
} }
.onSuccess { Truth.assertThat(it) }
.onFailure { error("Should returns success but $it") }
coVerify(exactly = 1) { userWalletsStore.getSyncStrict(defaultUserWalletId) } coVerify(inverse = true) { userWalletsListRepository.saveWithoutLock(any(), any()) }
coVerify(exactly = 1) { tangemSdkManager.derivePublicKeys(null, any(), any()) } }
coVerify(exactly = 1) { userWalletsStore.update(defaultUserWalletId, 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)
}
} }
} }

View file

@ -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<SaveWalletError, UserWallet> = 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
}