Updated on 2026-08-14
This commit is contained in:
parent
9fb5124c38
commit
3b8da73b9c
18 changed files with 373 additions and 433 deletions
|
|
@ -1,35 +1,30 @@
|
|||
package com.tangem.data.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.right
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.datasource.api.tangemTech.models.account.flattenTokens
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher.Params
|
||||
import com.tangem.domain.tokens.MultiWalletAccountListFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletAccountListFetcher.Params
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
/**
|
||||
* Implementation of [MultiWalletCryptoCurrenciesFetcher] that fetches crypto currencies of all accounts
|
||||
* Implementation of [MultiWalletAccountListFetcher] that fetches the account list for a multi-currency wallet
|
||||
* by delegating to [WalletAccountsFetcher].
|
||||
*
|
||||
* @property userWalletsListRepository repository to get user wallets
|
||||
* @property walletAccountsFetcher instance of [WalletAccountsFetcher] to fetch accounts for a multi wallet
|
||||
* @property expressServiceFetcher fetcher of express service
|
||||
* @property dispatchers dispatchers
|
||||
* @property dispatchers provider for coroutine dispatchers used to run fetch operations
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AccountListCryptoCurrenciesFetcher(
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val walletAccountsFetcher: WalletAccountsFetcher,
|
||||
private val expressServiceFetcher: ExpressServiceFetcher,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MultiWalletCryptoCurrenciesFetcher {
|
||||
) : MultiWalletAccountListFetcher {
|
||||
|
||||
override suspend fun invoke(params: Params): Either<Throwable, Unit> {
|
||||
return Either.catchOn(dispatchers.default) {
|
||||
|
|
@ -37,16 +32,7 @@ internal class AccountListCryptoCurrenciesFetcher(
|
|||
|
||||
if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet")
|
||||
|
||||
val response = walletAccountsFetcher.fetch(userWalletId = params.userWalletId)
|
||||
|
||||
expressServiceFetcher.fetch(
|
||||
userWallet = userWallet,
|
||||
assetIds = response.flattenTokens().mapTo(hashSetOf()) {
|
||||
ExpressAsset.ID(networkId = it.networkId, contractAddress = it.contractAddress)
|
||||
},
|
||||
)
|
||||
|
||||
Unit.right()
|
||||
walletAccountsFetcher.fetch(userWalletId = params.userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,7 @@ package com.tangem.data.tokens.di
|
|||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.data.tokens.AccountListCryptoCurrenciesFetcher
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletAccountListFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -14,20 +13,18 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal class MultiWalletCryptoCurrenciesFetcherModule {
|
||||
internal class MultiWalletAccountListFetcherModule {
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideMultiWalletCryptoCurrenciesFetcher(
|
||||
fun provideMultiWalletAccountListFetcher(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
walletAccountsFetcher: WalletAccountsFetcher,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): MultiWalletCryptoCurrenciesFetcher {
|
||||
): MultiWalletAccountListFetcher {
|
||||
return AccountListCryptoCurrenciesFetcher(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.data.tokens.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
|
||||
import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository
|
||||
import com.tangem.data.tokens.repository.DefaultTokenReceiveWarningsViewedRepository
|
||||
|
|
@ -11,7 +9,6 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.TokenReceiveWarningActionStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
||||
|
|
@ -34,21 +31,15 @@ internal object TokensDataModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
cacheRegistry = cacheRegistry,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,119 +5,33 @@ import com.tangem.blockchain.common.TransactionStatus
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.domain.common.tokens.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.getTokenId
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.domain.card.CardTypesResolver
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.express.models.ExpressAsset
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.tokens.model.FeePaidCurrency
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
|
||||
|
||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultCurrenciesRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val expressServiceFetcher: ExpressServiceFetcher,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : CurrenciesRepository {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
|
||||
override suspend fun getSingleCurrencyWalletPrimaryCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
refresh: Boolean,
|
||||
): CryptoCurrency {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
userWallet.requireColdWallet()
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(
|
||||
userWallet = userWallet,
|
||||
)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencies = listOf(currency),
|
||||
refresh = refresh,
|
||||
)
|
||||
|
||||
currency
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSingleCurrencyWalletWithCardCurrencies(
|
||||
userWalletId: UserWalletId,
|
||||
refresh: Boolean,
|
||||
): List<CryptoCurrency> {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
val scanResponse = userWallet.requireColdWallet().scanResponse
|
||||
|
||||
val currencies = if (scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = userWallet)
|
||||
} else {
|
||||
cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet = userWallet)
|
||||
.run {
|
||||
listOf(this)
|
||||
}
|
||||
}
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
userWallet = userWallet,
|
||||
cryptoCurrencies = currencies,
|
||||
refresh = refresh,
|
||||
)
|
||||
|
||||
currencies
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSingleCurrencyWalletWithCardCurrency(
|
||||
userWalletId: UserWalletId,
|
||||
id: CryptoCurrency.ID,
|
||||
): CryptoCurrency {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
userWallet.requireColdWallet()
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
userWallet = userWallet,
|
||||
)
|
||||
.find { it.id == id }
|
||||
requireNotNull(currency) { "Unable to find currency with provided ID: $id" }
|
||||
fetchExpressAssetsByNetworkIds(userWallet, listOf(currency))
|
||||
currency
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isSendBlockedByPendingTransactions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -223,56 +137,4 @@ internal class DefaultCurrenciesRepository(
|
|||
val blockchain = Blockchain.fromNetworkId(network.backendId)
|
||||
return blockchain?.isNetworkFeeZero() == true
|
||||
}
|
||||
|
||||
override fun getCardTypesResolver(userWalletId: UserWalletId): CardTypesResolver? {
|
||||
return (userWalletsListRepository.getSyncStrict(userWalletId) as? UserWallet.Cold)?.cardTypesResolver
|
||||
}
|
||||
|
||||
private suspend fun fetchExpressAssetsByNetworkIds(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean = false,
|
||||
) {
|
||||
val tokens = cryptoCurrencies.mapTo(hashSetOf()) { currency ->
|
||||
val tokenCurrency = currency as? CryptoCurrency.Token
|
||||
ExpressAsset.ID(
|
||||
networkId = currency.network.backendId,
|
||||
contractAddress = tokenCurrency?.contractAddress,
|
||||
)
|
||||
}
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getAssetsCacheKey(userWallet.walletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
coroutineScope {
|
||||
launch { expressServiceFetcher.fetch(userWallet, tokens) }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun getAssetsCacheKey(userWalletId: UserWalletId): String = "assets_cache_key_${userWalletId.stringValue}"
|
||||
|
||||
private fun ensureIsCorrectUserWallet(userWallet: UserWallet, isMultiCurrencyWalletExpected: Boolean) {
|
||||
val userWalletId = userWallet.walletId
|
||||
|
||||
val message = when {
|
||||
!userWallet.isMultiCurrency && isMultiCurrencyWalletExpected -> {
|
||||
"Multi currency wallet expected, but single currency wallet was found: $userWalletId"
|
||||
}
|
||||
|
||||
userWallet.isMultiCurrency && !isMultiCurrencyWalletExpected -> {
|
||||
"Single currency wallet expected, but multi currency wallet was found: $userWalletId"
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (message != null) {
|
||||
val error = DataError.UserWalletError.WrongUserWallet(message)
|
||||
|
||||
Timber.e(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
package com.tangem.data.tokens
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.data.common.account.WalletAccountsFetcher
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletAccountListFetcher
|
||||
import com.tangem.test.core.assertEither
|
||||
import com.tangem.test.core.assertEitherRight
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
|
|
@ -25,13 +23,11 @@ internal class AccountListCryptoCurrenciesFetcherTest {
|
|||
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true)
|
||||
private val walletAccountsFetcher: WalletAccountsFetcher = mockk(relaxUnitFun = true)
|
||||
private val expressServiceFetcher: ExpressServiceFetcher = mockk()
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
|
||||
private val fetcher = AccountListCryptoCurrenciesFetcher(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletAccountsFetcher = walletAccountsFetcher,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
|
|
@ -43,7 +39,7 @@ internal class AccountListCryptoCurrenciesFetcherTest {
|
|||
@Test
|
||||
fun `returns failure if wallet is not multi-currency`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
val params = MultiWalletAccountListFetcher.Params(userWalletId = userWalletId)
|
||||
val mockUserWallet = mockk<UserWallet> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns false
|
||||
|
|
@ -68,7 +64,7 @@ internal class AccountListCryptoCurrenciesFetcherTest {
|
|||
@Test
|
||||
fun `returns accounts if wallet is multi-currency`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
val params = MultiWalletAccountListFetcher.Params(userWalletId = userWalletId)
|
||||
val mockUserWallet = mockk<UserWallet> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
|
|
@ -79,7 +75,6 @@ internal class AccountListCryptoCurrenciesFetcherTest {
|
|||
|
||||
every { userWalletsListRepository.userWallets } returns userWalletsFlow
|
||||
coEvery { walletAccountsFetcher.fetch(userWalletId = params.userWalletId) } returns response
|
||||
coEvery { expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = emptySet()) } returns Unit.right()
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
|
@ -90,14 +85,13 @@ internal class AccountListCryptoCurrenciesFetcherTest {
|
|||
coVerify(ordering = Ordering.SEQUENCE) {
|
||||
userWalletsListRepository.userWallets
|
||||
walletAccountsFetcher.fetch(userWalletId = params.userWalletId)
|
||||
expressServiceFetcher.fetch(userWallet = mockUserWallet, assetIds = emptySet())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns error if walletAccountsFetcher returns error`() = runTest {
|
||||
// Arrange
|
||||
val params = MultiWalletCryptoCurrenciesFetcher.Params(userWalletId = userWalletId)
|
||||
val params = MultiWalletAccountListFetcher.Params(userWalletId = userWalletId)
|
||||
val mockUserWallet = mockk<UserWallet> {
|
||||
every { walletId } returns userWalletId
|
||||
every { isMultiCurrency } returns true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue