From 500bc90ca3573c6ac6cc87e9422e9ffedd6f5483 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Oct 2025 12:36:36 +0400 Subject: [PATCH] Updated on 2026-08-14 --- data/account/build.gradle.kts | 1 + .../converter/CryptoPortfolioConverter.kt | 4 +- .../data/account/di/AccountDataModule.kt | 2 + ...ltiWalletCryptoCurrenciesProducerModule.kt | 26 ++++++++ .../AccountListCryptoCurrenciesProducer.kt | 64 +++++++++++++++++++ ...aultMultiWalletCryptoCurrenciesProducer.kt | 2 +- .../DefaultAccountsCRUDRepository.kt | 15 +++++ ...MultiWalletCryptoCurrenciesProducerTest.kt | 2 +- .../DefaultAccountsCRUDRepositoryTest.kt | 3 + .../currency/UserTokensResponseFactory.kt | 8 ++- ...ltiWalletCryptoCurrenciesProducerModule.kt | 20 ------ .../repository/AccountsCRUDRepository.kt | 3 + .../usecase/SaveCryptoCurrenciesUseCase.kt | 2 +- 13 files changed, 126 insertions(+), 26 deletions(-) create mode 100644 data/account/src/main/kotlin/com/tangem/data/account/di/MultiWalletCryptoCurrenciesProducerModule.kt create mode 100644 data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt rename data/{tokens/src/main/kotlin/com/tangem/data/tokens => account/src/main/kotlin/com/tangem/data/account/producer}/DefaultMultiWalletCryptoCurrenciesProducer.kt (98%) rename data/{tokens/src/test/kotlin/com/tangem/data/tokens => account/src/test/java/com/tangem/data/account/producer}/DefaultMultiWalletCryptoCurrenciesProducerTest.kt (99%) delete mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesProducerModule.kt diff --git a/data/account/build.gradle.kts b/data/account/build.gradle.kts index 58b5628a0d..a818b24ccb 100644 --- a/data/account/build.gradle.kts +++ b/data/account/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { api(projects.domain.card) api(projects.domain.common) api(projects.domain.models) + api(projects.domain.tokens) // endregion // region Project - Data diff --git a/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt b/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt index 0117b2b653..fd06cb7bba 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/converter/CryptoPortfolioConverter.kt @@ -50,7 +50,9 @@ internal class CryptoPortfolioConverter @AssistedInject constructor( derivationIndex = value.derivationIndex.value, icon = value.icon.value.name, iconColor = value.icon.color.name, - tokens = value.cryptoCurrencies.map(userTokensResponseFactory::createResponseToken), + tokens = value.cryptoCurrencies.map { + userTokensResponseFactory.createResponseToken(currency = it, accountId = value.accountId) + }, ) } diff --git a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt index 198b1291fd..002af1472c 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/di/AccountDataModule.kt @@ -41,6 +41,7 @@ internal object AccountDataModule { walletAccountsSaver: WalletAccountsSaver, accountsResponseStoreFactory: AccountsResponseStoreFactory, userWalletsStore: UserWalletsStore, + userTokensSaver: UserTokensSaver, eTagsStore: ETagsStore, accountConverterFactoryContainer: AccountConverterFactoryContainer, dispatchers: CoroutineDispatcherProvider, @@ -51,6 +52,7 @@ internal object AccountDataModule { accountsResponseStoreFactory = accountsResponseStoreFactory, archivedAccountsStoreFactory = ArchivedAccountsStoreFactory, userWalletsStore = userWalletsStore, + userTokensSaver = userTokensSaver, eTagsStore = eTagsStore, convertersContainer = accountConverterFactoryContainer, dispatchers = dispatchers, diff --git a/data/account/src/main/kotlin/com/tangem/data/account/di/MultiWalletCryptoCurrenciesProducerModule.kt b/data/account/src/main/kotlin/com/tangem/data/account/di/MultiWalletCryptoCurrenciesProducerModule.kt new file mode 100644 index 0000000000..e283366b5b --- /dev/null +++ b/data/account/src/main/kotlin/com/tangem/data/account/di/MultiWalletCryptoCurrenciesProducerModule.kt @@ -0,0 +1,26 @@ +package com.tangem.data.account.di + +import com.tangem.data.account.producer.AccountListCryptoCurrenciesProducer +import com.tangem.data.account.producer.DefaultMultiWalletCryptoCurrenciesProducer +import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object MultiWalletCryptoCurrenciesProducerModule { + + @Singleton + @Provides + fun provideMultiWalletCryptoCurrenciesProducerFactory( + accountsFeatureToggles: AccountsFeatureToggles, + defaultImpl: DefaultMultiWalletCryptoCurrenciesProducer.Factory, + accountsImpl: AccountListCryptoCurrenciesProducer.Factory, + ): MultiWalletCryptoCurrenciesProducer.Factory { + return if (accountsFeatureToggles.isFeatureEnabled) accountsImpl else defaultImpl + } +} \ No newline at end of file diff --git a/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt b/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt new file mode 100644 index 0000000000..7217e10a20 --- /dev/null +++ b/data/account/src/main/kotlin/com/tangem/data/account/producer/AccountListCryptoCurrenciesProducer.kt @@ -0,0 +1,64 @@ +package com.tangem.data.account.producer + +import arrow.core.Option +import arrow.core.some +import com.tangem.data.account.store.AccountsResponseStoreFactory +import com.tangem.data.account.utils.toUserTokensResponse +import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.isMultiCurrency +import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.* + +/** + * Implementation of [MultiWalletCryptoCurrenciesProducer] that produces crypto currencies of all accounts + * + * @property params params + * @property userWalletsStore UserWallet's store + * @property accountsResponseStoreFactory factory to create store with accounts response + * @property responseCryptoCurrenciesFactory factory for creating [CryptoCurrency] from `UserTokensResponse` + * @property dispatchers dispatchers + * +[REDACTED_AUTHOR] + */ +internal class AccountListCryptoCurrenciesProducer @AssistedInject constructor( + @Assisted val params: MultiWalletCryptoCurrenciesProducer.Params, + private val userWalletsStore: UserWalletsStore, + private val accountsResponseStoreFactory: AccountsResponseStoreFactory, + private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory, + private val dispatchers: CoroutineDispatcherProvider, +) : MultiWalletCryptoCurrenciesProducer { + + override val fallback: Option> = emptySet().some() + + override fun produce(): Flow> { + val userWallet = userWalletsStore.getSyncStrict(key = params.userWalletId) + + if (!userWallet.isMultiCurrency) { + error("${this::class.simpleName} supports only multi-currency wallet") + } + + return accountsResponseStoreFactory.create(userWalletId = userWallet.walletId).data + .distinctUntilChanged() + .map { response -> + if (response == null) return@map emptySet() + + responseCryptoCurrenciesFactory.createCurrencies( + response = response.toUserTokensResponse(), + userWallet = userWallet, + ).toSet() + } + .onEmpty { emit(emptySet()) } + .flowOn(dispatchers.default) + } + + @AssistedFactory + interface Factory : MultiWalletCryptoCurrenciesProducer.Factory { + override fun create(params: MultiWalletCryptoCurrenciesProducer.Params): AccountListCryptoCurrenciesProducer + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesProducer.kt b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt similarity index 98% rename from data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesProducer.kt rename to data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt index dc71df52b3..8c46088747 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesProducer.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducer.kt @@ -1,4 +1,4 @@ -package com.tangem.data.tokens +package com.tangem.data.account.producer import arrow.core.Option import arrow.core.some diff --git a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt index 6dc1771d27..5cbc0ba8e0 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/repository/DefaultAccountsCRUDRepository.kt @@ -9,8 +9,10 @@ import com.tangem.data.account.store.AccountsResponseStore import com.tangem.data.account.store.AccountsResponseStoreFactory import com.tangem.data.account.store.ArchivedAccountsStore import com.tangem.data.account.store.ArchivedAccountsStoreFactory +import com.tangem.data.account.utils.toUserTokensResponse import com.tangem.data.common.account.WalletAccountsSaver import com.tangem.data.common.cache.etag.ETagsStore +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse @@ -28,6 +30,7 @@ import com.tangem.utils.extensions.replaceBy import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext +import timber.log.Timber /** [REDACTED_AUTHOR] @@ -39,6 +42,7 @@ internal class DefaultAccountsCRUDRepository( private val accountsResponseStoreFactory: AccountsResponseStoreFactory, private val archivedAccountsStoreFactory: ArchivedAccountsStoreFactory, private val userWalletsStore: UserWalletsStore, + private val userTokensSaver: UserTokensSaver, private val eTagsStore: ETagsStore, private val convertersContainer: AccountConverterFactoryContainer, private val dispatchers: CoroutineDispatcherProvider, @@ -131,6 +135,17 @@ internal class DefaultAccountsCRUDRepository( } } + override suspend fun syncTokens(userWalletId: UserWalletId) { + val response = getAccountsResponseSync(userWalletId = userWalletId) + + if (response == null) { + Timber.e("Can't sync tokens. No accounts response found for wallet: $userWalletId") + return + } + + userTokensSaver.push(userWalletId = userWalletId, response = response.toUserTokensResponse()) + } + override suspend fun getTotalAccountsCountSync(userWalletId: UserWalletId): Option = option { val accountListResponse = getAccountsResponseSync(userWalletId = userWalletId) diff --git a/data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesProducerTest.kt b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt similarity index 99% rename from data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesProducerTest.kt rename to data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt index df9fa14413..bbdc8679ef 100644 --- a/data/tokens/src/test/kotlin/com/tangem/data/tokens/DefaultMultiWalletCryptoCurrenciesProducerTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiWalletCryptoCurrenciesProducerTest.kt @@ -1,4 +1,4 @@ -package com.tangem.data.tokens +package com.tangem.data.account.producer import com.google.common.truth.Truth import com.tangem.blockchain.common.Blockchain diff --git a/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt b/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt index 9507b208c1..5881a3194e 100644 --- a/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/repository/DefaultAccountsCRUDRepositoryTest.kt @@ -11,6 +11,7 @@ import com.tangem.data.account.store.ArchivedAccountsStore import com.tangem.data.account.store.ArchivedAccountsStoreFactory import com.tangem.data.common.account.WalletAccountsSaver import com.tangem.data.common.cache.etag.ETagsStore +import com.tangem.data.common.currency.UserTokensSaver import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse @@ -52,6 +53,7 @@ class DefaultAccountsCRUDRepositoryTest { private val archivedAccountsStore = ArchivedAccountsStore(runtimeStore = archivedAccountsInnerStore) private val userWalletsStore: UserWalletsStore = mockk() + private val userTokensSaver: UserTokensSaver = mockk() private val eTagsStore: ETagsStore = mockk() private val convertersContainer: AccountConverterFactoryContainer = mockk() @@ -64,6 +66,7 @@ class DefaultAccountsCRUDRepositoryTest { accountsResponseStoreFactory = accountsResponseStoreFactory, archivedAccountsStoreFactory = archivedAccountsStoreFactory, userWalletsStore = userWalletsStore, + userTokensSaver = userTokensSaver, eTagsStore = eTagsStore, convertersContainer = convertersContainer, dispatchers = TestingCoroutineDispatcherProvider(), 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 84b75233ac..c36aa9fdbf 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,18 +1,21 @@ package com.tangem.data.common.currency import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.currency.CryptoCurrency import javax.inject.Inject +// TODO: [REDACTED_JIRA] class UserTokensResponseFactory @Inject constructor() { fun createUserTokensResponse( currencies: List, isGroupedByNetwork: Boolean, isSortedByBalance: Boolean, + accountId: AccountId? = null, ): UserTokensResponse { return UserTokensResponse( - tokens = currencies.map(::createResponseToken), + tokens = currencies.map { createResponseToken(currency = it, accountId = accountId) }, group = if (isGroupedByNetwork) { UserTokensResponse.GroupType.NETWORK } else { @@ -26,10 +29,11 @@ class UserTokensResponseFactory @Inject constructor() { ) } - fun createResponseToken(currency: CryptoCurrency): UserTokensResponse.Token { + fun createResponseToken(currency: CryptoCurrency, accountId: AccountId? = null): UserTokensResponse.Token { return with(currency) { UserTokensResponse.Token( id = id.rawCurrencyId?.value, + accountId = accountId?.value, networkId = network.backendId, derivationPath = network.derivationPath.value, name = name, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesProducerModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesProducerModule.kt deleted file mode 100644 index 03fa952be2..0000000000 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/MultiWalletCryptoCurrenciesProducerModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.data.tokens.di - -import com.tangem.data.tokens.DefaultMultiWalletCryptoCurrenciesProducer -import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface MultiWalletCryptoCurrenciesProducerModule { - - @Singleton - @Binds - fun bindMultiWalletCryptoCurrenciesProducerFactory( - impl: DefaultMultiWalletCryptoCurrenciesProducer.Factory, - ): MultiWalletCryptoCurrenciesProducer.Factory -} \ No newline at end of file diff --git a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt index 5eb7a0433a..e910bf695e 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/repository/AccountsCRUDRepository.kt @@ -75,6 +75,9 @@ interface AccountsCRUDRepository { */ suspend fun saveAccount(account: Account.CryptoPortfolio) + /** Synchronizes tokens for a specific [userWalletId] with remote data source */ + suspend fun syncTokens(userWalletId: UserWalletId) + /** * Retrieves the total count of accounts associated with a specific user wallet including archived accounts * diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/SaveCryptoCurrenciesUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/SaveCryptoCurrenciesUseCase.kt index b89a3f03f5..ce55e827fc 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/SaveCryptoCurrenciesUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/SaveCryptoCurrenciesUseCase.kt @@ -201,7 +201,7 @@ class SaveCryptoCurrenciesUseCase( ), ) - currenciesRepository.syncTokens(userWalletId) + accountsCRUDRepository.syncTokens(userWalletId) } private suspend fun refreshYieldBalances(userWalletId: UserWalletId, currencies: List) {