From e0a845a4748d3a2beea87ce0dd6cd0366ad9f498 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Oct 2025 15:36:03 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../DefaultWalletAccountsResponseFactory.kt | 13 +++-- ...efaultWalletAccountsResponseFactoryTest.kt | 52 ++++++++----------- .../DefaultCardCryptoCurrencyFactory.kt | 42 +++++++-------- .../currency/UserTokensResponseFactory.kt | 41 +++++++++++++++ .../tangem/data/common/di/DataCommonModule.kt | 5 ++ .../data/common/network/NetworkFactory.kt | 4 +- .../common/tokens/DefaultWalletBlockchains.kt | 33 ++++++++++++ .../DefaultCardCryptoCurrencyFactoryTest.kt | 10 +++- 8 files changed, 138 insertions(+), 62 deletions(-) create mode 100644 data/common/src/main/kotlin/com/tangem/data/common/tokens/DefaultWalletBlockchains.kt diff --git a/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt b/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt index ed9d37a708..ad87b1f3e3 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactory.kt @@ -1,8 +1,8 @@ package com.tangem.data.account.utils import com.tangem.data.account.converter.CryptoPortfolioConverter -import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO @@ -21,7 +21,7 @@ import javax.inject.Inject * @property userWalletsListRepository repository to get user wallet information * @property cryptoPortfolioCF converter factory to convert crypto portfolio accounts * @property userTokensResponseFactory factory to create [UserTokensResponse] - * @property cardCryptoCurrencyFactory factory to get default coins for multi-currency wallet + * @property networkFactory factory to create network derivation path * [REDACTED_AUTHOR] */ @@ -29,7 +29,7 @@ internal class DefaultWalletAccountsResponseFactory @Inject constructor( private val userWalletsListRepository: UserWalletsListRepository, private val cryptoPortfolioCF: CryptoPortfolioConverter.Factory, private val userTokensResponseFactory: UserTokensResponseFactory, - private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory, + private val networkFactory: NetworkFactory, ) { suspend fun create(userWalletId: UserWalletId, userTokensResponse: UserTokensResponse?): GetWalletAccountsResponse { @@ -61,10 +61,9 @@ internal class DefaultWalletAccountsResponseFactory @Inject constructor( private fun UserTokensResponse?.orDefault(userWallet: UserWallet?): UserTokensResponse { if (this != null) return this - return userTokensResponseFactory.createUserTokensResponse( - currencies = userWallet?.let(cardCryptoCurrencyFactory::createDefaultCoinsForMultiCurrencyWallet).orEmpty(), - isGroupedByNetwork = false, - isSortedByBalance = false, + return userTokensResponseFactory.createDefaultResponse( + userWallet = userWallet, + networkFactory = networkFactory, accountId = userWallet?.let { AccountId.forCryptoPortfolio(userWalletId = it.walletId, derivationIndex = DerivationIndex.Main) }, diff --git a/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt b/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt index 250423c3d7..3a7d6800b1 100644 --- a/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/utils/DefaultWalletAccountsResponseFactoryTest.kt @@ -3,8 +3,8 @@ package com.tangem.data.account.utils import com.google.common.truth.Truth import com.tangem.data.account.converter.CryptoPortfolioConverter import com.tangem.data.account.converter.createWalletAccountDTO -import com.tangem.data.common.currency.CardCryptoCurrencyFactory import com.tangem.data.common.currency.UserTokensResponseFactory +import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.UserTokensResponse import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse import com.tangem.domain.account.models.AccountList @@ -27,13 +27,13 @@ class DefaultWalletAccountsResponseFactoryTest { private val cryptoPortfolioCF = mockk() private val cryptoPortfolioConverter = mockk() private val userTokensResponseFactory = mockk() - private val cardCryptoCurrencyFactory = mockk() + private val networkFactory = mockk() private val factory = DefaultWalletAccountsResponseFactory( userWalletsListRepository = userWalletsListRepository, cryptoPortfolioCF = cryptoPortfolioCF, userTokensResponseFactory = userTokensResponseFactory, - cardCryptoCurrencyFactory = cardCryptoCurrencyFactory, + networkFactory = networkFactory, ) private val userWalletId = UserWalletId("011") @@ -50,7 +50,7 @@ class DefaultWalletAccountsResponseFactoryTest { cryptoPortfolioCF, cryptoPortfolioConverter, userTokensResponseFactory, - cardCryptoCurrencyFactory, + networkFactory, ) } @@ -65,10 +65,10 @@ class DefaultWalletAccountsResponseFactoryTest { coEvery { userWalletsListRepository.userWalletsSync() } returns emptyList() every { - userTokensResponseFactory.createUserTokensResponse( - currencies = emptyList(), - isGroupedByNetwork = false, - isSortedByBalance = false, + userTokensResponseFactory.createDefaultResponse( + userWallet = null, + networkFactory = networkFactory, + accountId = null, ) } returns userTokensResponse @@ -89,10 +89,10 @@ class DefaultWalletAccountsResponseFactoryTest { coVerifyOrder { userWalletsListRepository.userWalletsSync() - userTokensResponseFactory.createUserTokensResponse( - currencies = emptyList(), - isGroupedByNetwork = false, - isSortedByBalance = false, + userTokensResponseFactory.createDefaultResponse( + userWallet = null, + networkFactory = networkFactory, + accountId = null, ) } } @@ -107,21 +107,17 @@ class DefaultWalletAccountsResponseFactoryTest { val accounts = AccountList.empty(userWallet.walletId).accounts .filterIsInstance() - val defaultCoins = listOf(mockk()) coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) - every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns defaultCoins val defaultResponse = UserTokensResponse( group = UserTokensResponse.GroupType.NETWORK, sort = UserTokensResponse.SortType.BALANCE, tokens = listOf(mockk(relaxed = true)), ) - every { - userTokensResponseFactory.createUserTokensResponse( - currencies = defaultCoins, - isGroupedByNetwork = false, - isSortedByBalance = false, + userTokensResponseFactory.createDefaultResponse( + userWallet = userWallet, + networkFactory = networkFactory, accountId = accounts.first().accountId, ) } returns defaultResponse @@ -148,11 +144,9 @@ class DefaultWalletAccountsResponseFactoryTest { coVerifyOrder { userWalletsListRepository.userWalletsSync() cryptoPortfolioConverter.convertListBack(accounts) - cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) - userTokensResponseFactory.createUserTokensResponse( - currencies = defaultCoins, - isGroupedByNetwork = false, - isSortedByBalance = false, + userTokensResponseFactory.createDefaultResponse( + userWallet = userWallet, + networkFactory = networkFactory, accountId = accounts.first().accountId, ) } @@ -169,17 +163,17 @@ class DefaultWalletAccountsResponseFactoryTest { .filterIsInstance() coEvery { userWalletsListRepository.userWalletsSync() } returns listOf(userWallet) - every { cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet) } returns emptyList() + val defaultResponse = UserTokensResponse( group = UserTokensResponse.GroupType.NETWORK, sort = UserTokensResponse.SortType.BALANCE, tokens = emptyList(), ) + every { - userTokensResponseFactory.createUserTokensResponse( - currencies = emptyList(), - isGroupedByNetwork = false, - isSortedByBalance = false, + userTokensResponseFactory.createDefaultResponse( + userWallet = userWallet, + networkFactory = networkFactory, accountId = accounts.first().accountId, ) } returns defaultResponse diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt index f0e538b4f9..0fbe2d013f 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactory.kt @@ -1,12 +1,15 @@ package com.tangem.data.common.currency -import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.data.common.account.WalletAccountsFetcher +import com.tangem.data.common.tokens.getDefaultWalletBlockchains +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.card.common.TapWorkarounds.isTestCard +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.models.currency.CryptoCurrency @@ -23,10 +26,13 @@ import com.tangem.domain.models.wallet.isMultiCurrency * @property userWalletsStore user wallets store * @property userTokensResponseStore user tokens response store */ +@Suppress("LongParameterList") internal class DefaultCardCryptoCurrencyFactory( private val demoConfig: DemoConfig, private val excludedBlockchains: ExcludedBlockchains, private val userWalletsStore: UserWalletsStore, + private val accountsFeatureToggles: AccountsFeatureToggles, + private val walletAccountsFetcher: WalletAccountsFetcher, private val userTokensResponseStore: UserTokensResponseStore, private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory, ) : CardCryptoCurrencyFactory { @@ -99,25 +105,7 @@ internal class DefaultCardCryptoCurrencyFactory( override fun createDefaultCoinsForMultiCurrencyWallet(userWallet: UserWallet): List { require(userWallet.isMultiCurrency) { "It isn't multi-currency wallet" } - val blockchains = when (userWallet) { - is UserWallet.Cold -> { - val card = userWallet.scanResponse.card - - var blockchainsInternal = if (demoConfig.isDemoCardId(card.cardId)) { - demoConfig.demoBlockchains - } else { - listOf(Blockchain.Bitcoin, Blockchain.Ethereum) - } - - if (card.isTestCard) { - blockchainsInternal = blockchainsInternal.mapNotNull { it.getTestnetVersion() } - } - - blockchainsInternal - } - - is UserWallet.Hot -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum) - } + val blockchains = getDefaultWalletBlockchains(userWallet, demoConfig) return blockchains.mapNotNull { cryptoCurrencyFactory.createCoin( @@ -152,7 +140,7 @@ internal class DefaultCardCryptoCurrencyFactory( userWallet: UserWallet, networks: Set, ): Map> { - val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId) + val response = getUserTokensResponse(userWalletId = userWallet.walletId) ?: return emptyMap() val existingNetworkWithCurrencies = responseCryptoCurrenciesFactory.createCurrencies( @@ -170,7 +158,7 @@ internal class DefaultCardCryptoCurrencyFactory( userWallet: UserWallet, rawIds: Set, ): Map> { - val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId) + val response = getUserTokensResponse(userWalletId = userWallet.walletId) ?: return emptyMap() val networkIds = rawIds.map { it.toBlockchain().toNetworkId() } @@ -182,6 +170,14 @@ internal class DefaultCardCryptoCurrencyFactory( .groupBy { it.network.id.rawId } } + private suspend fun getUserTokensResponse(userWalletId: UserWalletId): UserTokensResponse? { + return if (accountsFeatureToggles.isFeatureEnabled) { + walletAccountsFetcher.getSaved(userWalletId)?.toUserTokensResponse() + } else { + userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) + } + } + private fun getSingleWalletCurrencies(userWallet: UserWallet.Cold): SingleWalletCurrencies { val resolver = userWallet.cardTypesResolver val blockchain = resolver.getBlockchain() diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt index c36aa9fdbf..41f66c5b28 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensResponseFactory.kt @@ -1,8 +1,15 @@ package com.tangem.data.common.currency +import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.blockchainsdk.utils.toNetworkId +import com.tangem.data.common.network.NetworkFactory +import com.tangem.data.common.tokens.getDefaultWalletBlockchains import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.derivations.derivationStyleProvider import javax.inject.Inject // TODO: [REDACTED_JIRA] @@ -43,4 +50,38 @@ class UserTokensResponseFactory @Inject constructor() { ) } } + + fun createDefaultResponse( + userWallet: UserWallet?, + networkFactory: NetworkFactory, + accountId: AccountId? = null, + ): UserTokensResponse { + val tokens = userWallet?.let { + getDefaultWalletBlockchains(userWallet = it, demoConfig = DemoConfig()) + .map { blockchain -> + val derivationPath = networkFactory.createDerivationPath( + blockchain = blockchain, + extraDerivationPath = null, + cardDerivationStyleProvider = userWallet.derivationStyleProvider, + ).value + + UserTokensResponse.Token( + id = blockchain.toCoinId(), + accountId = accountId?.value, + networkId = blockchain.toNetworkId(), + derivationPath = derivationPath, + name = blockchain.getCoinName(), + symbol = blockchain.currency, + decimals = blockchain.decimals(), + contractAddress = null, + ) + } + } + + return UserTokensResponse( + group = UserTokensResponse.GroupType.NONE, + sort = UserTokensResponse.SortType.MANUAL, + tokens = tokens.orEmpty(), + ) + } } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt index 79af422911..ed6a6d9545 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt @@ -1,6 +1,7 @@ package com.tangem.data.common.di import com.tangem.blockchainsdk.utils.ExcludedBlockchains +import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.cache.etag.DefaultETagsStore import com.tangem.data.common.cache.etag.ETagsStore import com.tangem.data.common.currency.* @@ -30,6 +31,8 @@ internal object DataCommonModule { fun provideCardCryptoCurrencyFactory( excludedBlockchains: ExcludedBlockchains, userWalletsStore: UserWalletsStore, + accountsFeatureToggles: AccountsFeatureToggles, + walletAccountsFetcher: WalletAccountsFetcher, userTokensResponseStore: UserTokensResponseStore, responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory, ): CardCryptoCurrencyFactory { @@ -37,6 +40,8 @@ internal object DataCommonModule { demoConfig = DemoConfig(), excludedBlockchains = excludedBlockchains, userWalletsStore = userWalletsStore, + accountsFeatureToggles = accountsFeatureToggles, + walletAccountsFetcher = walletAccountsFetcher, userTokensResponseStore = userTokensResponseStore, responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory, ) diff --git a/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt index 94fb83724a..6605a39757 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/network/NetworkFactory.kt @@ -7,10 +7,10 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.domain.card.common.extensions.canHandleToken -import com.tangem.domain.wallets.derivations.derivationStyleProvider import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.derivations.DerivationStyleProvider +import com.tangem.domain.wallets.derivations.derivationStyleProvider import timber.log.Timber import javax.inject.Inject @@ -130,7 +130,7 @@ class NetworkFactory @Inject constructor( return true } - private fun createDerivationPath( + fun createDerivationPath( blockchain: Blockchain, extraDerivationPath: String?, cardDerivationStyleProvider: DerivationStyleProvider?, diff --git a/data/common/src/main/kotlin/com/tangem/data/common/tokens/DefaultWalletBlockchains.kt b/data/common/src/main/kotlin/com/tangem/data/common/tokens/DefaultWalletBlockchains.kt new file mode 100644 index 0000000000..9815907b64 --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/tokens/DefaultWalletBlockchains.kt @@ -0,0 +1,33 @@ +package com.tangem.data.common.tokens + +import com.tangem.blockchain.common.Blockchain +import com.tangem.domain.card.common.TapWorkarounds.isTestCard +import com.tangem.domain.demo.models.DemoConfig +import com.tangem.domain.models.wallet.UserWallet + +/** + * Returns the default blockchains for the multi-currency wallet. + * + * @param userWallet The user's wallet, which can be either a cold or hot wallet. + * @param demoConfig Configuration for demo cards, which may specify different default blockchains. + */ +fun getDefaultWalletBlockchains(userWallet: UserWallet, demoConfig: DemoConfig): Collection { + return when (userWallet) { + is UserWallet.Cold -> { + val card = userWallet.scanResponse.card + + var blockchainsInternal = if (demoConfig.isDemoCardId(card.cardId)) { + demoConfig.demoBlockchains + } else { + listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + } + + if (card.isTestCard) { + blockchainsInternal = blockchainsInternal.mapNotNull { it.getTestnetVersion() } + } + + blockchainsInternal + } + is UserWallet.Hot -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum) + } +} \ No newline at end of file diff --git a/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt b/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt index ceb7c2fc1c..9c65d12bd9 100644 --- a/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt +++ b/data/common/src/test/kotlin/com/tangem/data/common/currency/DefaultCardCryptoCurrencyFactoryTest.kt @@ -9,10 +9,12 @@ import com.tangem.common.test.domain.card.MockScanResponseFactory import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory import com.tangem.common.test.domain.wallet.MockUserWalletFactory import com.tangem.common.test.utils.ProvideTestModels +import com.tangem.data.common.account.WalletAccountsFetcher import com.tangem.data.common.network.NetworkFactory import com.tangem.datasource.api.tangemTech.models.UserTokensResponse 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.util.cardTypesResolver import com.tangem.domain.card.configs.GenericCardConfig import com.tangem.domain.demo.models.DemoConfig @@ -37,6 +39,8 @@ internal class DefaultCardCryptoCurrencyFactoryTest { private val userWalletsStore: UserWalletsStore = mockk() private val userTokensResponseStore: UserTokensResponseStore = mockk() private val excludedBlockchains = ExcludedBlockchains() + private val accountsFeatureToggles = mockk() + private val walletAccountsFetcher = mockk() private val factory = DefaultCardCryptoCurrencyFactory( demoConfig = DemoConfig(), @@ -46,6 +50,8 @@ internal class DefaultCardCryptoCurrencyFactoryTest { responseCryptoCurrenciesFactory = ResponseCryptoCurrenciesFactory( networkFactory = NetworkFactory(excludedBlockchains = excludedBlockchains), ), + accountsFeatureToggles = accountsFeatureToggles, + walletAccountsFetcher = walletAccountsFetcher, ) private val cryptoCurrencyFactory = MockCryptoCurrencyFactory() @@ -57,7 +63,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest { @BeforeEach fun init() { - clearMocks(userWalletsStore, userTokensResponseStore, iconUri) + clearMocks(userWalletsStore, userTokensResponseStore, accountsFeatureToggles, walletAccountsFetcher, iconUri) mockkStatic(Uri::class) every { Uri.parse(any()) } returns iconUri @@ -75,6 +81,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest { val userTokensResponse = model.userTokensResponse val network = ethereum.network + every { accountsFeatureToggles.isFeatureEnabled } returns false coEvery { userWalletsStore.getSyncStrict(key = userWallet.walletId) } returns userWallet coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse @@ -236,6 +243,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest { val networks = setOf(ethereum.network, bitcoin.network) val userTokensResponse = model.userTokensResponse + every { accountsFeatureToggles.isFeatureEnabled } returns false coEvery { userTokensResponseStore.getSyncOrNull(userWallet.walletId) } returns userTokensResponse // Act