Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-17 22:28:06 +04:00
parent 7bd584c261
commit 568e735d23
10 changed files with 43 additions and 314 deletions

View file

@ -101,10 +101,6 @@ internal class DefaultStakingRepository(
private val yieldBalanceConverter = YieldBalanceConverter()
private val yieldBalanceListConverter = YieldBalanceListConverter(yieldBalanceConverter)
private val isYieldBalanceFetching = MutableStateFlow(
value = emptyMap<UserWalletId, Boolean>(),
)
private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) }
override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) {
@ -384,79 +380,48 @@ internal class DefaultStakingRepository(
) = withContext(dispatchers.io) {
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
try {
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
isYieldBalanceFetching.update {
it + (userWalletId to true)
}
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val yields = getEnabledYields().ifEmpty {
Timber.i("No enabled yields for $userWalletId")
stakingBalanceStore.store(userWalletId, emptySet())
val yields = getEnabledYields().ifEmpty {
Timber.i("No enabled yields for $userWalletId")
return@invokeOnExpire
}
val availableCurrencies = cryptoCurrencies
.mapNotNull { currency ->
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
if (integrationId != null && yields.any { it.id == integrationId }) {
addresses to integrationId
} else {
null
}
}
.flatMap { (addresses, integrationId) ->
addresses.map { address -> address to integrationId }
}
.map { getBalanceRequestData(it.first.value, it.second) }
.ifEmpty {
Timber.i("No yield balances available for $userWalletId")
stakingBalanceStore.store(userWalletId, emptySet())
return@invokeOnExpire
}
val availableCurrencies = cryptoCurrencies
.mapNotNull { currency ->
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
if (integrationId != null && yields.any { it.id == integrationId }) {
addresses to integrationId
} else {
null
}
}
.flatMap { (addresses, integrationId) ->
addresses.map { address -> address to integrationId }
}
.map { getBalanceRequestData(it.first.value, it.second) }
.ifEmpty {
Timber.i("No yield balances available for $userWalletId")
stakingBalanceStore.store(userWalletId, emptySet())
val result = stakeKitApi
.getMultipleYieldBalances(availableCurrencies)
.getOrThrow()
return@invokeOnExpire
}
val result = stakeKitApi
.getMultipleYieldBalances(availableCurrencies)
.getOrThrow()
stakingBalanceStore.store(userWalletId, result)
},
)
} finally {
isYieldBalanceFetching.update {
it - userWalletId
}
}
stakingBalanceStore.store(userWalletId, result)
},
)
}
override fun getMultiYieldBalanceFlow(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> = channelFlow {
if (!stakingFeatureToggle.isStakingEnabled) {
send(YieldBalanceList.Empty)
} else {
launch(dispatchers.io) {
stakingBalanceStore.get(userWalletId)
.collectLatest { send(yieldBalanceListConverter.convert(it)) }
}
withContext(dispatchers.io) {
fetchMultiYieldBalance(
userWalletId,
cryptoCurrencies,
)
}
}
}.cancellable()
override fun getMultiYieldBalance(
override fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> = channelFlow {

View file

@ -1,6 +1,5 @@
package com.tangem.data.tokens.repository
import arrow.core.raise.catch
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.compatibility.getL2CompatibilityTokenComparison
import com.tangem.blockchainsdk.utils.toCoinId
@ -27,8 +26,6 @@ import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.datasource.local.token.ExpressAssetsStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.core.error.DataError
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.lce.lceFlow
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -67,10 +64,6 @@ internal class DefaultCurrenciesRepository(
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers)
private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow(
value = emptyMap<UserWalletId, Boolean>(),
)
override suspend fun saveTokens(
userWalletId: UserWalletId,
currencies: List<CryptoCurrency>,
@ -267,30 +260,6 @@ internal class DefaultCurrenciesRepository(
}
}
override fun getMultiCurrencyWalletCurrenciesUpdatesLce(
userWalletId: UserWalletId,
): LceFlow<Throwable, List<CryptoCurrency>> = lceFlow {
val userWallet = getUserWallet(userWalletId)
catch({ ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) }) {
raise(it)
}
launch(dispatchers.io) {
combine(
getMultiCurrencyWalletCurrencies(userWallet),
isMultiCurrencyWalletCurrenciesFetching.map { it.getOrElse(userWallet.walletId) { false } },
) { currencies, isFetching ->
send(currencies, isStillLoading = isFetching)
}.collect()
}
withContext(dispatchers.io) {
catch({ fetchTokensIfCacheExpired(userWallet, refresh = false) }) {
raise(it)
}
}
}
override suspend fun getMultiCurrencyWalletCurrenciesSync(
userWalletId: UserWalletId,
refresh: Boolean,
@ -540,19 +509,7 @@ internal class DefaultCurrenciesRepository(
cacheRegistry.invokeOnExpire(
key = getTokensCacheKey(userWallet.walletId),
skipCache = refresh,
block = {
isMultiCurrencyWalletCurrenciesFetching.update {
it + (userWallet.walletId to true)
}
try {
fetchTokens(userWallet)
} finally {
isMultiCurrencyWalletCurrenciesFetching.update {
it - userWallet.walletId
}
}
},
block = { fetchTokens(userWallet) },
)
}

View file

@ -1,6 +1,5 @@
package com.tangem.data.tokens.repository
import arrow.core.raise.catch
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.utils.fromNetworkId
@ -15,8 +14,6 @@ import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.lce.lceFlow
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
@ -28,7 +25,10 @@ import com.tangem.domain.walletmanager.model.UpdateWalletManagerResult
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
@Suppress("LongParameterList")
@ -46,10 +46,6 @@ internal class DefaultNetworksRepository(
private val responseCurrenciesFactory by lazy { ResponseCryptoCurrenciesFactory() }
private val networkStatusFactory by lazy { NetworkStatusFactory() }
private val isNetworkStatusesFetching = MutableStateFlow(
value = emptyMap<UserWalletId, Boolean>(),
)
override fun getNetworkStatusesUpdates(
userWalletId: UserWalletId,
networks: Set<Network>,
@ -63,24 +59,6 @@ internal class DefaultNetworksRepository(
}
}
override fun getNetworkStatusesUpdatesLce(
userWalletId: UserWalletId,
networks: Set<Network>,
): LceFlow<Throwable, Set<NetworkStatus>> = lceFlow {
combine(
networksStatusesStore.get(userWalletId),
isNetworkStatusesFetching.map { it.getOrElse(userWalletId) { false } },
) { statuses, isFetching ->
send(statuses, isStillLoading = isFetching || networks.size != statuses.size)
}.launchIn(scope = this + dispatchers.io)
withContext(dispatchers.io) {
catch({ fetchNetworksStatusesIfCacheExpired(userWalletId, networks, refresh = false) }) {
raise(it)
}
}
}
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
val currencies = getCurrencies(userWalletId, networks)
withContext(dispatchers.io) {
@ -123,67 +101,13 @@ internal class DefaultNetworksRepository(
}
}
override suspend fun getNetworkAddress(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): CryptoCurrencyAddress = withContext(dispatchers.io) {
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = walletManagersFacade.getAddresses(userWalletId, currency.network)
.firstOrNull { it.type == AddressType.Default }
?.value.orEmpty(),
)
}
override fun getNetworkAddressFlow(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): Flow<CryptoCurrencyAddress> = channelFlow {
launch(dispatchers.io) {
send(getNetworkAddress(userWalletId, currency))
}
}
override suspend fun getNetworkAddresses(userWalletId: UserWalletId): List<CryptoCurrencyAddress> =
withContext(dispatchers.io) {
// Get list of currencies matching [network]
val currencies = getCurrencies(userWalletId)
// There is no currencies matching given [networks] in [userWalletId]
if (currencies.toList().isEmpty()) return@withContext emptyList()
currencies.toList().map { currency ->
CryptoCurrencyAddress(
cryptoCurrency = currency,
address = walletManagersFacade.getAddresses(userWalletId, currency.network)
.firstOrNull { it.type == AddressType.Default }
?.value.orEmpty(),
)
}
}
override fun getNetworkAddressesFlow(
userWalletId: UserWalletId,
network: Network,
): Flow<List<CryptoCurrencyAddress>> = channelFlow {
launch(dispatchers.io) {
send(getNetworkAddresses(userWalletId, network))
}
}
override fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow<List<CryptoCurrencyAddress>> = channelFlow {
launch(dispatchers.io) {
send(getNetworkAddresses(userWalletId))
}
}
private suspend fun fetchNetworksStatusesIfCacheExpired(
userWalletId: UserWalletId,
networks: Set<Network>,
refresh: Boolean,
) = coroutineScope {
if (refresh) {
val statusesToRefresh = networks.map { NetworkStatus(it, NetworkStatus.Loading) }
val statusesToRefresh = networks.map { NetworkStatus(it, NetworkStatus.Refreshing) }
networksStatusesStore.storeAll(userWalletId, statusesToRefresh)
}
@ -230,8 +154,6 @@ internal class DefaultNetworksRepository(
network: Network,
currencies: Sequence<CryptoCurrency>,
) {
networksStatusesStore.store(userWalletId, NetworkStatus(network, NetworkStatus.Loading))
val result = walletManagersFacade.update(
userWalletId = userWalletId,
network = network,

View file

@ -49,12 +49,7 @@ interface StakingRepository {
refresh: Boolean = false,
)
fun getMultiYieldBalanceFlow(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList>
fun getMultiYieldBalance(
fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList>

View file

@ -186,7 +186,7 @@ internal class CurrenciesStatusesLceOperations(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): EitherFlow<TokenListError, YieldBalanceList> {
return stakingRepository.getMultiYieldBalance(userWalletId, cryptoCurrencies)
return stakingRepository.getMultiYieldBalanceUpdates(userWalletId, cryptoCurrencies)
.map<YieldBalanceList, Either<TokenListError, YieldBalanceList>> { it.right() }
.catch { emit(TokenListError.DataError(it).left()) }
.distinctUntilChanged()

View file

@ -1,7 +1,6 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.core.error.DataError
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.FeePaidCurrency
@ -130,17 +129,6 @@ interface CurrenciesRepository {
*/
fun getMultiCurrencyWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow<List<CryptoCurrency>>
/**
* Retrieves updates of the list of cryptocurrencies within a multi-currency wallet.
*
* Loads remote cryptocurrencies if they have expired.
*
* @param userWalletId The unique identifier of the user wallet.
* @return A [LceFlow] emitting the set of cryptocurrencies associated with the user wallet. May emit an
* [DataError.UserWalletError.WrongUserWallet] if single-currency user wallet ID provided.
*/
fun getMultiCurrencyWalletCurrenciesUpdatesLce(userWalletId: UserWalletId): LceFlow<Throwable, List<CryptoCurrency>>
/**
* Retrieves the list of cryptocurrencies within a multi-currency wallet.
*

View file

@ -1,7 +1,5 @@
package com.tangem.domain.tokens.repository
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
@ -23,20 +21,6 @@ interface NetworksRepository {
*/
fun getNetworkStatusesUpdates(userWalletId: UserWalletId, networks: Set<Network>): Flow<Set<NetworkStatus>>
/**
* Retrieves updates of network statuses of specified blockchain networks for a specific user wallet.
*
* Loads remote network statuses if they have expired.
*
* @param userWalletId The unique identifier of the user wallet.
* @param networks A set of network which statuses are to be retrieved.
* @return A [LceFlow] emitting a set of [NetworkStatus] objects corresponding to the specified networks.
*/
fun getNetworkStatusesUpdatesLce(
userWalletId: UserWalletId,
networks: Set<Network>,
): LceFlow<Throwable, Set<NetworkStatus>>
/**
* Fetches pending transactions for given network
*
@ -63,33 +47,8 @@ interface NetworksRepository {
fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean
/**
* Returns list of addresses and crypto currency info of added currencies of [network] in selected wallet [userWalletId]
*/
fun getNetworkAddressesFlow(userWalletId: UserWalletId, network: Network): Flow<List<CryptoCurrencyAddress>>
/**
* Returns list of addresses and crypto currency info of added currencies of [network] in selected wallet [userWalletId]
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List<CryptoCurrencyAddress>
/**
* Returns address of [cryptoCurrency] in selected wallet [userWalletId]
*/
suspend fun getNetworkAddress(userWalletId: UserWalletId, currency: CryptoCurrency): CryptoCurrencyAddress
/**
* Returns address of [cryptoCurrency] in selected wallet [userWalletId]
*/
fun getNetworkAddressFlow(userWalletId: UserWalletId, currency: CryptoCurrency): Flow<CryptoCurrencyAddress>
/**
* Returns list of addresses and crypto currency info in selected wallet [userWalletId]
*/
fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow<List<CryptoCurrencyAddress>>
/**
* Returns list of addresses and crypto currency info in selected wallet [userWalletId]
*/
suspend fun getNetworkAddresses(userWalletId: UserWalletId): List<CryptoCurrencyAddress>
}

View file

@ -3,8 +3,6 @@ package com.tangem.domain.tokens.repository
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.FeePaidCurrency
@ -57,7 +55,7 @@ internal class MockCurrenciesRepository(
override suspend fun removeCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) = Unit
override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): LceFlow<Throwable, List<CryptoCurrency>> {
override fun getWalletCurrenciesUpdates(userWalletId: UserWalletId): Flow<List<CryptoCurrency>> {
return emptyFlow()
}
@ -91,12 +89,6 @@ internal class MockCurrenciesRepository(
return tokens.map { it.getOrElse { e -> throw e } }
}
override fun getMultiCurrencyWalletCurrenciesUpdatesLce(
userWalletId: UserWalletId,
): LceFlow<Throwable, List<CryptoCurrency>> {
return tokens.map { it.toLce() }
}
override suspend fun getMultiCurrencyWalletCurrency(
userWalletId: UserWalletId,
id: CryptoCurrency.ID,

View file

@ -3,15 +3,11 @@ package com.tangem.domain.tokens.repository
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.core.error.DataError
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
@ -26,13 +22,6 @@ internal class MockNetworksRepository(
return statuses.map { it.getOrElse { e -> throw e } }
}
override fun getNetworkStatusesUpdatesLce(
userWalletId: UserWalletId,
networks: Set<Network>,
): LceFlow<Throwable, Set<NetworkStatus>> {
return statuses.map { it.toLce() }
}
override suspend fun fetchNetworkPendingTransactions(userWalletId: UserWalletId, networks: Set<Network>) {
// no-op
}
@ -47,37 +36,10 @@ internal class MockNetworksRepository(
override fun isNeedToCreateAccountWithoutReserve(network: Network) = false
override fun getNetworkAddressesFlow(
userWalletId: UserWalletId,
network: Network,
): Flow<List<CryptoCurrencyAddress>> = channelFlow {
send(emptyList())
}
override fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow<List<CryptoCurrencyAddress>> = channelFlow {
send(emptyList())
}
override suspend fun getNetworkAddresses(
userWalletId: UserWalletId,
network: Network,
): List<CryptoCurrencyAddress> {
return emptyList()
}
override suspend fun getNetworkAddresses(userWalletId: UserWalletId): List<CryptoCurrencyAddress> {
return emptyList()
}
override suspend fun getNetworkAddress(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): CryptoCurrencyAddress = CryptoCurrencyAddress(currency, "")
override fun getNetworkAddressFlow(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): Flow<CryptoCurrencyAddress> = channelFlow {
send(CryptoCurrencyAddress(currency, ""))
}
}

View file

@ -145,18 +145,7 @@ class MockStakingRepository : StakingRepository {
/* no-op */
}
override fun getMultiYieldBalanceFlow(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> = channelFlow {
send(
YieldBalanceList.Data(
balances = listOf(YieldBalance.Error),
),
)
}
override fun getMultiYieldBalance(
override fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> = flowOf()