From fb34b46df66d9573160eb7aafe3db70721953f22 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Jul 2024 13:21:26 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 33 ++- .../models/request/YieldBalanceRequestBody.kt | 2 +- .../tangem/utils/extensions/BigDecimalExt.kt | 10 + data/staking/build.gradle.kts | 2 + .../data/staking/DefaultStakingRepository.kt | 257 +++++++++++++----- .../data/staking/di/StakingDataModule.kt | 3 + .../repository/DefaultNetworksRepository.kt | 54 ++++ domain/staking/build.gradle.kts | 2 + .../domain/staking/model/YieldBalance.kt | 16 +- .../domain/staking/model/YieldBalanceList.kt | 6 +- .../staking/FetchStakingTokensUseCase.kt | 4 +- .../FetchStakingYieldBalanceUseCase.kt | 5 +- .../staking/GetStakingYieldBalanceUseCase.kt | 5 +- .../staking/repositories/StakingRepository.kt | 26 +- domain/tokens/build.gradle.kts | 3 + domain/tokens/models/build.gradle.kts | 12 +- .../tokens/model/CryptoCurrencyStatus.kt | 7 + .../tokens/FetchCardTokenListUseCase.kt | 19 +- .../domain/tokens/GetCardTokensListUseCase.kt | 3 + .../tokens/GetCryptoCurrencyActionsUseCase.kt | 1 + .../GetCryptoCurrencyStatusSyncUseCase.kt | 4 + .../GetCryptoCurrencyStatusesSyncUseCase.kt | 3 + .../tokens/GetCurrencyStatusUpdatesUseCase.kt | 3 + .../tokens/GetCurrencyWarningsUseCase.kt | 3 + ...tFeePaidCryptoCurrencyStatusSyncUseCase.kt | 3 + .../tokens/GetNetworkCoinStatusUseCase.kt | 4 + .../GetPrimaryCurrencyStatusUpdatesUseCase.kt | 3 + .../domain/tokens/GetTokenListUseCase.kt | 4 + .../tokens/GetWalletTotalBalanceUseCase.kt | 3 + .../mapper/CurrencyStatusErrorMappers.kt | 1 + .../error/mapper/TokenListErrorMappers.kt | 1 + .../CurrenciesStatusesLceOperations.kt | 44 ++- .../CurrenciesStatusesOperations.kt | 132 +++++++-- .../operations/CurrencyStatusOperations.kt | 9 +- .../TokenListFiatBalanceOperations.kt | 16 +- .../tokens/repository/NetworksRepository.kt | 26 ++ ...PrimaryCurrencyStatusUpdatesUseCaseTest.kt | 4 + .../domain/tokens/GetTokenListUseCaseTest.kt | 2 + .../domain/tokens/mock/MockTokenLists.kt | 9 +- .../domain/tokens/mock/MockTokensStates.kt | 3 + .../repository/MockNetworksRepository.kt | 32 ++- .../repository/MockStakingRepository.kt | 203 ++++++++++++++ features/swap/domain/build.gradle.kts | 1 + .../swap/domain/di/SwapDomainModule.kt | 5 + features/swap/presentation/build.gradle.kts | 1 + .../feature/swap/di/SwapPresentationModule.kt | 4 +- .../tokendetails/TokenDetailsPreviewData.kt | 1 + .../state/TokenDetailsBalanceBlockState.kt | 1 + .../TokenDetailsLoadedBalanceConverter.kt | 51 ++-- .../state/factory/TokenDetailsStateFactory.kt | 9 +- .../tokendetails/ui/TokenDetailsScreen.kt | 1 - .../ui/components/TokenDetailsBalanceBlock.kt | 7 +- .../viewmodels/TokenDetailsViewModel.kt | 45 +-- .../converter/TokenItemStateConverter.kt | 9 +- 54 files changed, 909 insertions(+), 208 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index ca10e45669..92b7c76800 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -55,8 +55,14 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, ): GetTokenListUseCase { - return GetTokenListUseCase(currenciesRepository, quotesRepository, networksRepository) + return GetTokenListUseCase( + currenciesRepository, + quotesRepository, + networksRepository, + stakingRepository, + ) } @Provides @@ -65,8 +71,9 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, ): GetCardTokensListUseCase { - return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository) + return GetCardTokensListUseCase(currenciesRepository, quotesRepository, networksRepository, stakingRepository) } @Provides @@ -84,9 +91,16 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, ): GetCurrencyStatusUpdatesUseCase { - return GetCurrencyStatusUpdatesUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers) + return GetCurrencyStatusUpdatesUseCase( + currenciesRepository, + quotesRepository, + networksRepository, + stakingRepository, + dispatchers, + ) } @Provides @@ -101,6 +115,7 @@ internal object TokensDomainModule { currencyChecksRepository: CurrencyChecksRepository, showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, promoRepository: PromoRepository, + stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, ): GetCurrencyWarningsUseCase { return GetCurrencyWarningsUseCase( @@ -113,6 +128,7 @@ internal object TokensDomainModule { swapRepository = swapRepository, showSwapPromoTokenUseCase = showSwapPromoTokenUseCase, promoRepository = promoRepository, + stakingRepository = stakingRepository, dispatchers = dispatchers, ) } @@ -123,12 +139,14 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, ): GetPrimaryCurrencyStatusUpdatesUseCase { return GetPrimaryCurrencyStatusUpdatesUseCase( currenciesRepository, quotesRepository, networksRepository, + stakingRepository, dispatchers, ) } @@ -149,8 +167,9 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, ): FetchCardTokenListUseCase { - return FetchCardTokenListUseCase(currenciesRepository, networksRepository, quotesRepository) + return FetchCardTokenListUseCase(currenciesRepository, networksRepository, quotesRepository, stakingRepository) } @Provides @@ -212,12 +231,14 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, ): GetNetworkCoinStatusUseCase { return GetNetworkCoinStatusUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, dispatchers = dispatchers, ) } @@ -228,12 +249,14 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, ): GetFeePaidCryptoCurrencyStatusSyncUseCase { return GetFeePaidCryptoCurrencyStatusSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, dispatchers = dispatchers, ) } @@ -366,11 +389,13 @@ internal object TokensDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, ): GetWalletTotalBalanceUseCase { return GetWalletTotalBalanceUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/YieldBalanceRequestBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/YieldBalanceRequestBody.kt index bbfe66a6af..b950b05390 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/YieldBalanceRequestBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/request/YieldBalanceRequestBody.kt @@ -5,7 +5,7 @@ import com.squareup.moshi.Json data class YieldBalanceRequestBody( @Json(name = "addresses") val addresses: Address, @Json(name = "args") val args: YieldBalanceRequestArgs, - @Json(name = "integrationId") val integrationId: String? = null, + @Json(name = "integrationId") val integrationId: String, ) { data class YieldBalanceRequestArgs( diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt b/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt new file mode 100644 index 0000000000..d09f3f6a6d --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/extensions/BigDecimalExt.kt @@ -0,0 +1,10 @@ +package com.tangem.utils.extensions + +import java.math.BigDecimal + +/** + * Converts `BigDecimal?` to `BigDecimal` + * + * If `BigDecimal?` is `null`, returns `BigDecimal.ZERO` + */ +fun BigDecimal?.orZero(): BigDecimal = this ?: BigDecimal.ZERO \ No newline at end of file diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts index e0b3458636..009048e07e 100644 --- a/data/staking/build.gradle.kts +++ b/data/staking/build.gradle.kts @@ -24,6 +24,8 @@ dependencies { implementation(projects.domain.staking) implementation(projects.domain.wallets.models) + /** Feature Api modules */ + implementation(projects.features.staking.api) // region DI implementation(deps.hilt.android) diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 7f1ad03485..52f0aef09c 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -1,5 +1,6 @@ package com.tangem.data.staking +import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toCoinId import com.tangem.data.staking.converters.StakingNetworkTypeConverter @@ -29,27 +30,33 @@ import com.tangem.data.staking.converters.* import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.datasource.local.token.StakingBalanceStore +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.lce.lceFlow import com.tangem.domain.staking.model.* import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.toFormattedString import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.cancellable import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal +@Suppress("LargeClass") internal class DefaultStakingRepository( private val stakeKitApi: StakeKitApi, private val stakingYieldsStore: StakingYieldsStore, private val stakingBalanceStore: StakingBalanceStore, private val cacheRegistry: CacheRegistry, private val dispatchers: CoroutineDispatcherProvider, + private val stakingFeatureToggle: StakingFeatureToggles, ) : StakingRepository { private val stakingNetworkTypeConverter = StakingNetworkTypeConverter() @@ -83,15 +90,24 @@ internal class DefaultStakingRepository( private val yieldBalanceListConverter = YieldBalanceListConverter() + private val isYieldBalanceFetching = MutableStateFlow( + value = emptyMap(), + ) + override fun isStakingSupported(currencyId: String): Boolean { - return integrationIds.contains(currencyId) + return integrationIdMap.containsKey(currencyId) } - override suspend fun fetchEnabledYields() { + override suspend fun fetchEnabledYields(refresh: Boolean) { withContext(dispatchers.io) { - val stakingTokensWithYields = stakeKitApi.getMultipleYields().getOrThrow() - - stakingYieldsStore.store(stakingTokensWithYields.data) + cacheRegistry.invokeOnExpire( + key = YIELDS_STORE_KEY, + skipCache = refresh, + block = { + val stakingTokensWithYields = stakeKitApi.getMultipleYields().getOrThrow() + stakingYieldsStore.store(stakingTokensWithYields.data) + }, + ) } } @@ -182,24 +198,32 @@ internal class DefaultStakingRepository( override suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, - address: String, - integrationId: String, + address: CryptoCurrencyAddress, refresh: Boolean, ) = withContext(dispatchers.io) { + if (!stakingFeatureToggle.isStakingEnabled) return@withContext + + val cryptoCurrency = address.cryptoCurrency + val rawCurrencyId = + cryptoCurrency.id.rawCurrencyId ?: error("Staking custom tokens is not available") + + val integrationId = integrationIdMap[rawCurrencyId] ?: return@withContext + cacheRegistry.invokeOnExpire( key = getYieldBalancesKey(userWalletId), skipCache = refresh, block = { + val requestBody = getBalanceRequestData(address.address, integrationId) val result = stakeKitApi.getSingleYieldBalance( - integrationId = integrationId, - body = getBalanceRequestData(address, integrationId), + integrationId = requestBody.integrationId, + body = requestBody, ).getOrThrow() stakingBalanceStore.store( - integrationId, + requestBody.integrationId, YieldBalanceWrapperDTO( balances = result, - integrationId = integrationId, + integrationId = requestBody.integrationId, ), ) }, @@ -208,70 +232,157 @@ internal class DefaultStakingRepository( override fun getSingleYieldBalanceFlow( userWalletId: UserWalletId, - address: String, - integrationId: String, + address: CryptoCurrencyAddress, ): Flow = channelFlow { - launch(dispatchers.io) { - stakingBalanceStore.get(integrationId) - .collectLatest { - send( - yieldBalanceConverter.convert( - YieldBalanceConverter.Data( - balance = it, - integrationId = integrationId, + if (!stakingFeatureToggle.isStakingEnabled) { + send(YieldBalance.Empty) + } else { + launch(dispatchers.io) { + val integrationId = integrationIdMap[address.cryptoCurrency.id.rawCurrencyId] + ?: error("Could not get integrationId") + stakingBalanceStore.get(integrationId) + .collectLatest { + send( + yieldBalanceConverter.convert( + YieldBalanceConverter.Data( + balance = it, + integrationId = integrationId, + ), ), - ), - ) - } - } + ) + } + } - withContext(dispatchers.io) { - fetchSingleYieldBalance( - userWalletId, - address, - integrationId, - ) + withContext(dispatchers.io) { + fetchSingleYieldBalance( + userWalletId, + address, + ) + } } }.cancellable() + override suspend fun getSingleYieldBalanceSync( + userWalletId: UserWalletId, + address: CryptoCurrencyAddress, + ): YieldBalance = withContext(dispatchers.io) { + if (!stakingFeatureToggle.isStakingEnabled) { + YieldBalance.Empty + } else { + fetchSingleYieldBalance(userWalletId, address) + + val integrationId = integrationIdMap[address.cryptoCurrency.id.rawCurrencyId] + ?: error("Could not get integrationId") + val result = stakingBalanceStore.getSyncOrNull(integrationId) ?: return@withContext YieldBalance.Error + yieldBalanceConverter.convert( + YieldBalanceConverter.Data( + balance = result, + integrationId = integrationId, + ), + ) + } + } + override suspend fun fetchMultiYieldBalance( userWalletId: UserWalletId, addresses: List, - integrationId: String, refresh: Boolean, ) = withContext(dispatchers.io) { - cacheRegistry.invokeOnExpire( - key = getYieldBalancesKey(userWalletId), - skipCache = refresh, - block = { - val result = stakeKitApi.getMultipleYieldBalances( - addresses.map { getBalanceRequestData(it.address, integrationId) }, - ).getOrThrow() + if (!stakingFeatureToggle.isStakingEnabled) return@withContext + try { + isYieldBalanceFetching.update { + it + (userWalletId to true) + } + cacheRegistry.invokeOnExpire( + key = getYieldBalancesKey(userWalletId), + skipCache = refresh, + block = { + val result = stakeKitApi.getMultipleYieldBalances( + addresses + .mapNotNull { networkAddress -> + val cryptoCurrency = networkAddress.cryptoCurrency + val rawCurrencyId = cryptoCurrency.id.rawCurrencyId ?: error("Currency raw id is null") + val integrationId = integrationIdMap[rawCurrencyId] - stakingBalanceStore.store(result) - }, - ) + if (integrationId != null) { + networkAddress.address to integrationId + } else { + null + } + } + .distinct() + .map { getBalanceRequestData(it.first, it.second) }, + ).getOrThrow() + + stakingBalanceStore.store(result) + }, + ) + } finally { + isYieldBalanceFetching.update { + it - userWalletId + } + } } override fun getMultiYieldBalanceFlow( userWalletId: UserWalletId, addresses: List, - integrationId: String, ): Flow = channelFlow { - launch(dispatchers.io) { - stakingBalanceStore.get() - .collectLatest { send(yieldBalanceListConverter.convert(it)) } - } + if (!stakingFeatureToggle.isStakingEnabled) { + send(YieldBalanceList.Empty) + } else { + launch(dispatchers.io) { + stakingBalanceStore.get() + .collectLatest { send(yieldBalanceListConverter.convert(it)) } + } - withContext(dispatchers.io) { - fetchMultiYieldBalance( - userWalletId, - addresses, - integrationId, - ) + withContext(dispatchers.io) { + fetchMultiYieldBalance( + userWalletId, + addresses, + ) + } } }.cancellable() + override fun getMultiYieldBalanceLce( + userWalletId: UserWalletId, + addresses: List, + ): LceFlow = lceFlow { + if (!stakingFeatureToggle.isStakingEnabled) { + send(YieldBalanceList.Empty) + } else { + launch(dispatchers.io) { + combine( + stakingBalanceStore.get(), + isYieldBalanceFetching.map { it.getOrElse(userWalletId) { false } }, + ) { result, isFetching -> + val balances = yieldBalanceListConverter.convert(result) + send(balances, isStillLoading = isFetching) + }.collect() + } + withContext(dispatchers.io) { + catch( + block = { fetchMultiYieldBalance(userWalletId, addresses, refresh = false) }, + catch = { raise(it) }, + ) + } + } + } + + override suspend fun getMultiYieldBalanceSync( + userWalletId: UserWalletId, + addresses: List, + ): YieldBalanceList = withContext(dispatchers.io) { + if (!stakingFeatureToggle.isStakingEnabled) { + YieldBalanceList.Empty + } else { + fetchMultiYieldBalance(userWalletId, addresses) + val result = stakingBalanceStore.getSyncOrNull() ?: return@withContext YieldBalanceList.Error + yieldBalanceListConverter.convert(result) + } + } + private fun findPrefetchedYield(yields: List, currencyId: String, symbol: String): Yield? { return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol } } @@ -297,19 +408,33 @@ internal class DefaultStakingRepository( private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}" - companion object { - private val integrationIds = setOf( - Blockchain.Solana.toCoinId(), - Blockchain.Cosmos.toCoinId(), - Blockchain.Polkadot.toCoinId(), - Blockchain.Polygon.toCoinId(), - Blockchain.Avalanche.toCoinId(), - Blockchain.Tron.toCoinId(), - Blockchain.Cronos.toCoinId(), - Blockchain.Binance.toCoinId(), - Blockchain.Kava.toCoinId(), - Blockchain.Near.toCoinId(), - Blockchain.Tezos.toCoinId(), + private companion object { + const val YIELDS_STORE_KEY = "yields" + + const val SOLANA_INTEGRATION_ID = "solana-sol-native-multivalidator-staking" + const val COSMOS_INTEGRATION_ID = "cosmos-atom-native-staking" + const val POLKADOT_INTEGRATION_ID = "polkadot-dot-validator-staking" + const val ETHEREUM_INTEGRATION_ID = "ethereum-matic-native-staking" + const val AVALANCHE_INTEGRATION_ID = "avalanche-avax-native-staking" + const val TRON_INTEGRATION_ID = "tron-trx-native-staking" + const val CRONOS_INTEGRATION_ID = "cronos-cro-native-staking" + const val BINANCE_INTEGRATION_ID = "binance-bnb-native-staking" + const val KAVA_INTEGRATION_ID = "kava-kava-native-staking" + const val NEAR_INTEGRATION_ID = "near-near-native-staking" + const val TEZOS_INTEGRATION_ID = "tezos-xtz-native-staking" + + val integrationIdMap = mapOf( + Blockchain.Solana.toCoinId() to SOLANA_INTEGRATION_ID, + Blockchain.Cosmos.toCoinId() to COSMOS_INTEGRATION_ID, + Blockchain.Polkadot.toCoinId() to POLKADOT_INTEGRATION_ID, + Blockchain.Polygon.toCoinId() to ETHEREUM_INTEGRATION_ID, + Blockchain.Avalanche.toCoinId() to AVALANCHE_INTEGRATION_ID, + Blockchain.Tron.toCoinId() to TRON_INTEGRATION_ID, + Blockchain.Cronos.toCoinId() to CRONOS_INTEGRATION_ID, + Blockchain.Binance.toCoinId() to BINANCE_INTEGRATION_ID, + Blockchain.Kava.toCoinId() to KAVA_INTEGRATION_ID, + Blockchain.Near.toCoinId() to NEAR_INTEGRATION_ID, + Blockchain.Tezos.toCoinId() to TEZOS_INTEGRATION_ID, ) } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index 16c1bed364..b855fe3386 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -24,6 +25,7 @@ internal object StakingDataModule { stakingTokenStore: StakingYieldsStore, stakingBalanceStore: StakingBalanceStore, dispatchers: CoroutineDispatcherProvider, + stakingFeatureToggle: StakingFeatureToggles, cacheRegistry: CacheRegistry, ): StakingRepository { return DefaultStakingRepository( @@ -32,6 +34,7 @@ internal object StakingDataModule { stakingBalanceStore = stakingBalanceStore, dispatchers = dispatchers, cacheRegistry = cacheRegistry, + stakingFeatureToggle = stakingFeatureToggle, ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index c4a12be730..ce8d705b9f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -124,6 +124,60 @@ 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 = channelFlow { + launch(dispatchers.io) { + send(getNetworkAddress(userWalletId, currency)) + } + } + + override suspend fun getNetworkAddresses(userWalletId: UserWalletId): List = + 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> = channelFlow { + launch(dispatchers.io) { + send(getNetworkAddresses(userWalletId, network)) + } + } + + override fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow> = channelFlow { + launch(dispatchers.io) { + send(getNetworkAddresses(userWalletId)) + } + } + private suspend fun fetchNetworksStatusesIfCacheExpired( userWalletId: UserWalletId, networks: Set, diff --git a/domain/staking/build.gradle.kts b/domain/staking/build.gradle.kts index 38ec70430c..e92bc23b4c 100644 --- a/domain/staking/build.gradle.kts +++ b/domain/staking/build.gradle.kts @@ -18,4 +18,6 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + + implementation(projects.features.staking.api) } \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt index 1b22df4bed..e8d227b3fc 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalance.kt @@ -6,9 +6,23 @@ sealed class YieldBalance { data class Data( val balance: YieldBalanceItem, - ) : YieldBalance() + ) : YieldBalance() { + fun getTotalStakingBalance(): BigDecimal { + return balance.items + .filterNot { it.type == BalanceType.REWARDS } + .sumOf { it.amount * it.pricePerShare } + } + + fun getRewardStakingBalance(): BigDecimal { + return balance.items + .filter { it.type == BalanceType.REWARDS } + .sumOf { it.amount * it.pricePerShare } + } + } data object Empty : YieldBalance() + + data object Error : YieldBalance() } data class YieldBalanceItem( diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt index 22db0dfdf1..c75d5c9f10 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/YieldBalanceList.kt @@ -5,13 +5,15 @@ sealed class YieldBalanceList { data class Data( val balances: List, ) : YieldBalanceList() { - fun getBalance(rawCurrencyId: String?): YieldBalance? { + fun getBalance(rawCurrencyId: String?): YieldBalance { return balances.firstOrNull { yield -> (yield as? YieldBalance.Data)?.balance?.items ?.any { it.rawCurrencyId == rawCurrencyId } == true - } + } ?: YieldBalance.Error } } data object Empty : YieldBalanceList() + + data object Error : YieldBalanceList() } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt index b8609b2d1d..47e9960974 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingTokensUseCase.kt @@ -12,10 +12,10 @@ import com.tangem.domain.staking.repositories.StakingRepository class FetchStakingTokensUseCase( private val stakingRepository: StakingRepository, ) { - suspend operator fun invoke(): Either { + suspend operator fun invoke(isRefresh: Boolean = false): Either { return either { catch( - block = { stakingRepository.fetchEnabledYields() }, + block = { stakingRepository.fetchEnabledYields(isRefresh) }, catch = { StakingTokensError.DataError(it) }, ) } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt index bdbb1521d7..525fd569cb 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchStakingYieldBalanceUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.staking import arrow.core.Either import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.wallets.models.UserWalletId class FetchStakingYieldBalanceUseCase( @@ -10,14 +11,12 @@ class FetchStakingYieldBalanceUseCase( suspend operator fun invoke( userWalletId: UserWalletId, - address: String, - integrationId: String, + address: CryptoCurrencyAddress, refresh: Boolean = false, ): Either = Either.catch { stakingRepository.fetchSingleYieldBalance( userWalletId = userWalletId, address = address, - integrationId = integrationId, refresh = refresh, ) } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt index 5e12367173..f2e7c1440a 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingYieldBalanceUseCase.kt @@ -6,6 +6,7 @@ import arrow.core.right import com.tangem.domain.core.utils.EitherFlow import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map @@ -16,13 +17,11 @@ class GetStakingYieldBalanceUseCase( operator fun invoke( userWalletId: UserWalletId, - address: String, - integrationId: String, + address: CryptoCurrencyAddress, ): EitherFlow { return stakingRepository.getSingleYieldBalanceFlow( userWalletId = userWalletId, address = address, - integrationId = integrationId, ).map> { it.right() } .catch { emit(it.left()) } } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index c48ed07b5c..854aa92a93 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -7,6 +7,7 @@ import com.tangem.domain.staking.model.Yield import com.tangem.domain.staking.model.action.EnterAction import com.tangem.domain.staking.model.transaction.StakingTransaction import java.math.BigDecimal +import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.staking.model.* import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress @@ -17,7 +18,7 @@ interface StakingRepository { fun isStakingSupported(currencyId: String): Boolean - suspend fun fetchEnabledYields() + suspend fun fetchEnabledYields(refresh: Boolean) suspend fun getEntryInfo(integrationId: String): StakingEntryInfo @@ -30,30 +31,35 @@ interface StakingRepository { suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, - address: String, - integrationId: String, + address: CryptoCurrencyAddress, refresh: Boolean = false, ) - fun getSingleYieldBalanceFlow( - userWalletId: UserWalletId, - address: String, - integrationId: String, - ): Flow + fun getSingleYieldBalanceFlow(userWalletId: UserWalletId, address: CryptoCurrencyAddress): Flow + + suspend fun getSingleYieldBalanceSync(userWalletId: UserWalletId, address: CryptoCurrencyAddress): YieldBalance suspend fun fetchMultiYieldBalance( userWalletId: UserWalletId, addresses: List, - integrationId: String, refresh: Boolean = false, ) fun getMultiYieldBalanceFlow( userWalletId: UserWalletId, addresses: List, - integrationId: String, ): Flow + fun getMultiYieldBalanceLce( + userWalletId: UserWalletId, + addresses: List, + ): LceFlow + + suspend fun getMultiYieldBalanceSync( + userWalletId: UserWalletId, + addresses: List, + ): YieldBalanceList + suspend fun createEnterAction( integrationId: String, amount: BigDecimal, diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index 56b3e441ab..989a7e9c5d 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -24,7 +24,10 @@ dependencies { implementation(projects.domain.settings) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) + + /** Project - Api */ implementation(projects.features.send.api) + implementation(projects.features.staking.api) /** Project - Other */ implementation(projects.core.utils) diff --git a/domain/tokens/models/build.gradle.kts b/domain/tokens/models/build.gradle.kts index a243ada498..c51f155acf 100644 --- a/domain/tokens/models/build.gradle.kts +++ b/domain/tokens/models/build.gradle.kts @@ -10,12 +10,20 @@ android { } dependencies { - implementation(deps.kotlin.serialization) - implementation(projects.domain.txhistory.models) + /** Project - Core */ implementation(projects.core.analytics.models) + + /** Project - Domain */ + implementation(projects.domain.txhistory.models) + implementation(projects.domain.staking.models) + + /** SDK dependencies */ implementation(deps.tangem.blockchain) { exclude(module = "joda-time") } + + /** Other dependencies */ + implementation(deps.kotlin.serialization) implementation(deps.jodatime) implementation(deps.timber) } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index 6b098d5be3..cda5592ee6 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.model +import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.txhistory.models.TxHistoryItem import java.math.BigDecimal @@ -45,6 +46,9 @@ data class CryptoCurrencyStatus( /** The network address */ open val networkAddress: NetworkAddress? = null + + /** Staking yield balance */ + open val yieldBalance: YieldBalance? = null } /** Represents the Loading state of a cryptocurrency, typically while fetching its details. */ @@ -107,6 +111,7 @@ data class CryptoCurrencyStatus( override val fiatAmount: BigDecimal, override val fiatRate: BigDecimal, override val priceChange: BigDecimal, + override val yieldBalance: YieldBalance?, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, override val networkAddress: NetworkAddress, @@ -128,6 +133,7 @@ data class CryptoCurrencyStatus( override val fiatAmount: BigDecimal?, override val fiatRate: BigDecimal?, override val priceChange: BigDecimal?, + override val yieldBalance: YieldBalance?, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, override val networkAddress: NetworkAddress, @@ -143,6 +149,7 @@ data class CryptoCurrencyStatus( */ data class NoQuote( override val amount: BigDecimal, + override val yieldBalance: YieldBalance?, override val hasCurrentNetworkTransactions: Boolean, override val pendingTransactions: Set, override val networkAddress: NetworkAddress, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt index 7448714472..ca9266b2d1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCardTokenListUseCase.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.raise.Raise import arrow.core.raise.catch import arrow.core.raise.either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network @@ -19,6 +20,7 @@ class FetchCardTokenListUseCase( private val currenciesRepository: CurrenciesRepository, private val networksRepository: NetworksRepository, private val quotesRepository: QuotesRepository, + private val stakingRepository: StakingRepository, ) { suspend operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = false): Either { @@ -39,8 +41,13 @@ class FetchCardTokenListUseCase( refresh = refresh, ) } - - awaitAll(fetchStatuses, fetchQuotes) + val yieldBalances = async { + fetchYieldBalances( + userWalletId = userWalletId, + refresh = refresh, + ) + } + awaitAll(fetchStatuses, fetchQuotes, yieldBalances) } } } @@ -69,4 +76,12 @@ class FetchCardTokenListUseCase( catch = { /* Ignore error */ }, ) } + + private suspend fun fetchYieldBalances(userWalletId: UserWalletId, refresh: Boolean) { + val networkAddresses = networksRepository.getNetworkAddresses(userWalletId) + catch( + block = { stakingRepository.fetchMultiYieldBalance(userWalletId, networkAddresses, refresh) }, + catch = { /* Ignore error */ }, + ) + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt index 31b6ccc631..698c3be4f5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCardTokensListUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.tokens import arrow.core.left import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -21,6 +22,7 @@ class GetCardTokensListUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, ) { @OptIn(ExperimentalCoroutinesApi::class) @@ -43,6 +45,7 @@ class GetCardTokensListUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getCardCurrenciesStatusesFlow() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 871067c3ec..8cb5dbfc99 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -43,6 +43,7 @@ class GetCryptoCurrencyActionsUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, userWalletId = userWallet.walletId, ) val networkId = cryptoCurrencyStatus.currency.network.id diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt index 8150cfc734..9c87e00297 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusSyncUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrency @@ -16,6 +17,7 @@ class GetCryptoCurrencyStatusSyncUseCase( internal val currenciesRepository: CurrenciesRepository, internal val quotesRepository: QuotesRepository, internal val networksRepository: NetworksRepository, + internal val stakingRepository: StakingRepository, internal val dispatchers: CoroutineDispatcherProvider, ) { @@ -29,6 +31,7 @@ class GetCryptoCurrencyStatusSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getCurrencyStatusSync(cryptoCurrencyId, isSingleWalletWithTokens) @@ -41,6 +44,7 @@ class GetCryptoCurrencyStatusSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getPrimaryCurrencyStatusSync() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt index cdf58b3080..4c3e6b1b71 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyStatusesSyncUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -15,6 +16,7 @@ class GetCryptoCurrencyStatusesSyncUseCase( internal val currenciesRepository: CurrenciesRepository, internal val quotesRepository: QuotesRepository, internal val networksRepository: NetworksRepository, + internal val stakingRepository: StakingRepository, internal val dispatchers: CoroutineDispatcherProvider, ) { @@ -24,6 +26,7 @@ class GetCryptoCurrencyStatusesSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getCurrenciesStatusesSync() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt index 92e660af07..dceb8551cf 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyStatusUpdatesUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrency @@ -24,6 +25,7 @@ class GetCurrencyStatusUpdatesUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, private val dispatchers: CoroutineDispatcherProvider, ) { @@ -60,6 +62,7 @@ class GetCurrencyStatusUpdatesUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, userWalletId = userWalletId, ) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index eb7296acea..1e64511bf5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.domain.tokens.model.warnings.HederaWarnings @@ -26,6 +27,7 @@ class GetCurrencyWarningsUseCase( private val networksRepository: NetworksRepository, private val swapRepository: SwapRepository, private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository, + private val stakingRepository: StakingRepository, private val promoRepository: PromoRepository, private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase, private val dispatchers: CoroutineDispatcherProvider, @@ -43,6 +45,7 @@ class GetCurrencyWarningsUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, userWalletId = userWalletId, ) return combine( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt index d38fa6a951..473b7c10b9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetFeePaidCryptoCurrencyStatusSyncUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.tokens import arrow.core.Either import arrow.core.raise.either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.FeePaidCurrency @@ -16,6 +17,7 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase( internal val currenciesRepository: CurrenciesRepository, internal val quotesRepository: QuotesRepository, internal val networksRepository: NetworksRepository, + internal val stakingRepository: StakingRepository, internal val dispatchers: CoroutineDispatcherProvider, ) { @@ -30,6 +32,7 @@ class GetFeePaidCryptoCurrencyStatusSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return either { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt index 8b03aef79d..bd77e54e3b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -17,6 +18,7 @@ class GetNetworkCoinStatusUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, private val dispatchers: CoroutineDispatcherProvider, ) { @@ -49,6 +51,7 @@ class GetNetworkCoinStatusUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, userWalletId = userWalletId, ) val maybeCurrency = if (isSingleWalletWithTokens) { @@ -69,6 +72,7 @@ class GetNetworkCoinStatusUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, userWalletId = userWalletId, ) val networkFlow = if (isSingleWalletWithTokens) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt index f1216be10f..fdc6dfaf8a 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -24,6 +25,7 @@ class GetPrimaryCurrencyStatusUpdatesUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, private val dispatchers: CoroutineDispatcherProvider, ) { @@ -46,6 +48,7 @@ class GetPrimaryCurrencyStatusUpdatesUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, userWalletId = userWalletId, ) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index afb4450d1c..ef0b96e10b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -6,6 +6,7 @@ import com.tangem.domain.core.utils.EitherFlow import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.core.utils.toLce +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -26,6 +27,7 @@ class GetTokenListUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, ) { @OptIn(ExperimentalCoroutinesApi::class) @@ -35,6 +37,7 @@ class GetTokenListUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getCurrenciesStatusesFlow().transformLatest { maybeTokens -> @@ -55,6 +58,7 @@ class GetTokenListUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getCurrenciesStatuses(userWalletId).transformLatest { maybeCurrencies -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index 36da5cae5c..ce783509f9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -6,6 +6,7 @@ import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.lce.lce import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TotalFiatBalance @@ -23,6 +24,7 @@ class GetWalletTotalBalanceUseCase( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, ) { suspend operator fun invoke( @@ -72,6 +74,7 @@ class GetWalletTotalBalanceUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) return operations.getCurrenciesStatuses( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt index 6b5e9429d8..000588f24b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/CurrencyStatusErrorMappers.kt @@ -6,6 +6,7 @@ import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations internal fun CurrenciesStatusesOperations.Error.mapToCurrencyError(): CurrencyStatusError { return when (this) { is CurrenciesStatusesOperations.Error.DataError -> CurrencyStatusError.DataError(this.cause) + is CurrenciesStatusesOperations.Error.EmptyYieldBalances, is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses, is CurrenciesStatusesOperations.Error.EmptyQuotes, is CurrenciesStatusesOperations.Error.EmptyCurrencies, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt index 27b4e3956f..4f918d6a79 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt @@ -11,6 +11,7 @@ internal fun CurrenciesStatusesOperations.Error.mapToTokenListError(): TokenList is CurrenciesStatusesOperations.Error.EmptyQuotes, is CurrenciesStatusesOperations.Error.EmptyCurrencies, is CurrenciesStatusesOperations.Error.UnableToCreateCurrencyStatus, + is CurrenciesStatusesOperations.Error.EmptyYieldBalances, -> TokenListError.EmptyTokens } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt index 3dd2b0582b..a4265dd621 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -7,6 +7,9 @@ import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.lce.lce import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading +import com.tangem.domain.staking.model.YieldBalance +import com.tangem.domain.staking.model.YieldBalanceList +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -20,6 +23,7 @@ internal class CurrenciesStatusesLceOperations( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, ) { fun getCurrenciesStatuses( @@ -65,11 +69,18 @@ internal class CurrenciesStatusesLceOperations( val (networks, currenciesIds) = getIds(nonEmptyCurrencies) + val addresses = networksRepository.getNetworkAddresses(userWalletId) combine( getQuotes(currenciesIds), getNetworksStatuses(userWalletId, networks), - ) { maybeQuotes, maybeNetworksStatuses -> - val statuses = createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses) + getYieldBalances(userWalletId, addresses), + ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances -> + val statuses = createCurrenciesStatuses( + currencies = nonEmptyCurrencies, + maybeQuotes = maybeQuotes, + maybeNetworkStatuses = maybeNetworksStatuses, + maybeYieldBalances = maybeYieldBalances, + ) emit(statuses) }.collect() } @@ -84,9 +95,10 @@ internal class CurrenciesStatusesLceOperations( lceLoading() } else { createCurrenciesStatuses( - nonEmptyCurrencies, + currencies = nonEmptyCurrencies, maybeNetworkStatuses = null, maybeQuotes = null, + maybeYieldBalances = null, ) } @@ -113,6 +125,7 @@ internal class CurrenciesStatusesLceOperations( currencies: NonEmptyList, maybeQuotes: Either>?, maybeNetworkStatuses: Lce>?, + maybeYieldBalances: Lce?, ): Lce> = lce { isLoading.set(maybeNetworkStatuses == null) @@ -127,11 +140,20 @@ internal class CurrenciesStatusesLceOperations( null } + val yieldBalances = maybeYieldBalances?.getOrNull() + currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } + val yieldBalance = (yieldBalances as? YieldBalanceList.Data)?.getBalance(currency.id.rawCurrencyId) - createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed) + createCurrencyStatus( + currency = currency, + quote = quote, + networkStatus = networkStatus, + yieldBalance = yieldBalance, + ignoreQuote = quotesRetrievingFailed, + ) } } @@ -139,12 +161,14 @@ internal class CurrenciesStatusesLceOperations( currency: CryptoCurrency, quote: Quote?, networkStatus: NetworkStatus?, + yieldBalance: YieldBalance?, ignoreQuote: Boolean, ): CryptoCurrencyStatus { val currencyStatusOperations = CurrencyStatusOperations( currency = currency, quote = quote, networkStatus = networkStatus, + yieldBalance = yieldBalance, ignoreQuote = ignoreQuote, ) @@ -167,6 +191,18 @@ internal class CurrenciesStatusesLceOperations( } } + private fun getYieldBalances( + userWalletId: UserWalletId, + addresses: List, + ): LceFlow { + return stakingRepository.getMultiYieldBalanceLce( + userWalletId = userWalletId, + addresses = addresses, + ).map { maybeBalances -> + maybeBalances.mapError { TokenListError.DataError(it) } + } + } + private fun getIds(currencies: List): Pair, NonEmptySet> { val currencyIdToNetworkId = currencies.associate { currency -> currency.id to currency.network diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index de1768de08..24bb880d94 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -3,6 +3,9 @@ package com.tangem.domain.tokens.operations import arrow.core.* import arrow.core.raise.* import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.staking.model.YieldBalance +import com.tangem.domain.staking.model.YieldBalanceList +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository @@ -17,6 +20,7 @@ internal class CurrenciesStatusesOperations( private val currenciesRepository: CurrenciesRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, + private val stakingRepository: StakingRepository, private val userWalletId: UserWalletId, ) { @@ -42,6 +46,7 @@ internal class CurrenciesStatusesOperations( currencies = nonEmptyCurrencies, maybeNetworkStatuses = null, maybeQuotes = null, + maybeYieldBalances = null, ) emit(maybeLoadingCurrenciesStatuses) @@ -51,8 +56,14 @@ internal class CurrenciesStatusesOperations( val currenciesFlow = combine( getQuotes(currenciesIds), getNetworksStatuses(networks), - ) { maybeQuotes, maybeNetworksStatuses -> - createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses) + getYieldBalances(), + ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances -> + createCurrenciesStatuses( + currencies = nonEmptyCurrencies, + maybeQuotes = maybeQuotes, + maybeNetworkStatuses = maybeNetworksStatuses, + maybeYieldBalances = maybeYieldBalances, + ) } emitAll(currenciesFlow) @@ -70,7 +81,9 @@ internal class CurrenciesStatusesOperations( val quotes = quotesRepository.getQuotesSync(currenciesIds, false).right() val networkStatuses = networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right() - return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses) + val yieldBalances = getYieldBalancesSync() + + return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses, yieldBalances) }, catch = { raise(Error.DataError(it)) }, ) @@ -98,7 +111,9 @@ internal class CurrenciesStatusesOperations( ).firstOrNull { it.network == currency.network }.right() - return createCurrencyStatus(currency, quotes, networkStatuses) + val yieldBalances = getYieldBalanceSync(currency) + + return createCurrencyStatus(currency, quotes, networkStatuses, yieldBalances) }, catch = { raise(Error.DataError(it)) }, ) @@ -142,8 +157,9 @@ internal class CurrenciesStatusesOperations( }, catch = { Error.DataError(it).left() }, ) + val yieldBalances = getYieldBalanceSync(currency) - return createCurrencyStatus(currency, quotes, networkStatus) + return createCurrencyStatus(currency, quotes, networkStatus, yieldBalances) } fun getCardCurrenciesStatusesFlow(): Flow>> { @@ -167,6 +183,7 @@ internal class CurrenciesStatusesOperations( currencies = nonEmptyCurrencies, maybeNetworkStatuses = null, maybeQuotes = null, + maybeYieldBalances = null, ) emit(maybeLoadingCurrenciesStatuses) @@ -176,8 +193,9 @@ internal class CurrenciesStatusesOperations( val currenciesFlow = combine( getQuotes(currenciesIds), getNetworksStatuses(networks), - ) { maybeQuotes, maybeNetworksStatuses -> - createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses) + getYieldBalances(), + ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances -> + createCurrenciesStatuses(nonEmptyCurrencies, maybeQuotes, maybeNetworksStatuses, maybeYieldBalances) } emitAll(currenciesFlow) @@ -255,8 +273,10 @@ internal class CurrenciesStatusesOperations( } } - return combine(quoteFlow, statusFlow) { maybeQuote, maybeNetworkStatus -> - createCurrencyStatus(currency, maybeQuote, maybeNetworkStatus) + val yieldBalanceFlow = getYieldBalance(currency) + + return combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance -> + createCurrencyStatus(currency, maybeQuote, maybeNetworkStatus, maybeYieldBalance) } } @@ -264,6 +284,7 @@ internal class CurrenciesStatusesOperations( currencies: NonEmptyList, maybeQuotes: Either>?, maybeNetworkStatuses: Either>?, + maybeYieldBalances: Either?, ): Either> = either { var quotesRetrievingFailed = false @@ -281,11 +302,19 @@ internal class CurrenciesStatusesOperations( }, ) + val yieldBalances = maybeYieldBalances?.getOrNull() + currencies.map { currency -> val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId } val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network } - - createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quotesRetrievingFailed) + val yieldBalance = (yieldBalances as? YieldBalanceList.Data)?.getBalance(currency.id.rawCurrencyId) + createCurrencyStatus( + currency = currency, + quote = quote, + networkStatus = networkStatus, + ignoreQuote = quotesRetrievingFailed, + yieldBalance = yieldBalance, + ) } } @@ -293,6 +322,7 @@ internal class CurrenciesStatusesOperations( currency: CryptoCurrency, maybeQuote: Either, maybeNetworkStatus: Either, + maybeYieldBalance: Either?, ): Either = either { var quoteRetrievingFailed = false @@ -301,8 +331,15 @@ internal class CurrenciesStatusesOperations( quoteRetrievingFailed = true null } + val yieldBalance = maybeYieldBalance?.getOrNull() - createCurrencyStatus(currency, quote, networkStatus, ignoreQuote = quoteRetrievingFailed) + createCurrencyStatus( + currency = currency, + quote = quote, + networkStatus = networkStatus, + ignoreQuote = quoteRetrievingFailed, + yieldBalance = yieldBalance, + ) } private fun createCurrencyStatus( @@ -310,12 +347,14 @@ internal class CurrenciesStatusesOperations( quote: Quote?, networkStatus: NetworkStatus?, ignoreQuote: Boolean, + yieldBalance: YieldBalance?, ): CryptoCurrencyStatus { val currencyStatusOperations = CurrencyStatusOperations( currency = currency, quote = quote, networkStatus = networkStatus, ignoreQuote = ignoreQuote, + yieldBalance = yieldBalance, ) return currencyStatusOperations.createTokenStatus() @@ -396,6 +435,65 @@ internal class CurrenciesStatusesOperations( .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } } + @OptIn(ExperimentalCoroutinesApi::class) + private fun getYieldBalances(): EitherFlow { + return networksRepository.getNetworkAddressesFlow(userWalletId).flatMapLatest { addresses -> + stakingRepository.getMultiYieldBalanceFlow( + userWalletId = userWalletId, + addresses = addresses, + ).map> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(Error.EmptyYieldBalances.left()) } + } + } + + private suspend fun getYieldBalancesSync(): Either { + return catch( + block = { + val networkAddresses = networksRepository.getNetworkAddresses(userWalletId) + stakingRepository.getMultiYieldBalanceSync( + userWalletId, + networkAddresses, + ).right() + }, + catch = { + Error.EmptyYieldBalances.left() + }, + ) + } + + private suspend fun getYieldBalanceSync( + cryptoCurrency: CryptoCurrency, + ): Either { + return catch( + block = { + val address = networksRepository.getNetworkAddress(userWalletId, cryptoCurrency) + stakingRepository.getSingleYieldBalanceSync( + userWalletId, + address, + ).right() + }, + catch = { + Error.EmptyYieldBalances.left() + }, + ) + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun getYieldBalance(cryptoCurrency: CryptoCurrency): EitherFlow { + return networksRepository.getNetworkAddressFlow( + userWalletId, + cryptoCurrency, + ).flatMapLatest { address -> + stakingRepository.getSingleYieldBalanceFlow( + userWalletId = userWalletId, + address = address, + ).map> { it.right() } + .catch { emit(Error.DataError(it).left()) } + .onEmpty { emit(Error.EmptyYieldBalances.left()) } + } + } + private fun getIds( currencies: NonEmptyList, ): Pair, NonEmptySet> { @@ -413,14 +511,16 @@ internal class CurrenciesStatusesOperations( sealed class Error { - object EmptyCurrencies : Error() + data object EmptyCurrencies : Error() - object EmptyQuotes : Error() + data object EmptyQuotes : Error() - object EmptyNetworksStatuses : Error() + data object EmptyNetworksStatuses : Error() - object UnableToCreateCurrencyStatus : Error() + data object UnableToCreateCurrencyStatus : Error() data class DataError(val cause: Throwable) : Error() + + data object EmptyYieldBalances : Error() } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index 8284d1667e..ee8fa082a7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.operations +import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.tokens.model.* import java.math.BigDecimal @@ -7,6 +8,7 @@ internal class CurrencyStatusOperations( private val currency: CryptoCurrency, private val quote: Quote?, private val networkStatus: NetworkStatus?, + private val yieldBalance: YieldBalance?, private val ignoreQuote: Boolean, ) { @@ -18,7 +20,7 @@ internal class CurrencyStatusOperations( is NetworkStatus.MissedDerivation -> createMissedDerivationStatus() is NetworkStatus.Unreachable -> createUnreachableStatus(status) is NetworkStatus.NoAccount -> createNoAccountStatus(status) - is NetworkStatus.Verified -> createStatus(status) + is NetworkStatus.Verified -> createStatus(status, yieldBalance) } } @@ -42,7 +44,7 @@ internal class CurrencyStatusOperations( networkAddress = status.address, ) - private fun createStatus(status: NetworkStatus.Verified): CryptoCurrencyStatus.Value { + private fun createStatus(status: NetworkStatus.Verified, yieldBalance: YieldBalance?): CryptoCurrencyStatus.Value { val amount = when (val amount = status.amounts[currency.id]) { null -> { return CryptoCurrencyStatus.Loading @@ -62,6 +64,7 @@ internal class CurrencyStatusOperations( hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, networkAddress = status.address, + yieldBalance = yieldBalance, ) currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( amount = amount, @@ -71,6 +74,7 @@ internal class CurrencyStatusOperations( hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, networkAddress = status.address, + yieldBalance = yieldBalance, ) quote == null -> CryptoCurrencyStatus.Loading else -> CryptoCurrencyStatus.Loaded( @@ -81,6 +85,7 @@ internal class CurrencyStatusOperations( hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, networkAddress = status.address, + yieldBalance = yieldBalance, ) } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt index 2ff01885c9..8f28b74ecc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt @@ -1,8 +1,10 @@ package com.tangem.domain.tokens.operations import arrow.core.NonEmptyList +import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TotalFiatBalance +import com.tangem.utils.extensions.orZero import java.math.BigDecimal internal class TokenListFiatBalanceOperations( @@ -56,10 +58,13 @@ internal class TokenListFiatBalanceOperations( currentBalance: TotalFiatBalance, ): TotalFiatBalance { return with(currentBalance) { + val stakingBalance = (status.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero() + val fiatStakingBalance = status.fiatRate.times(stakingBalance) + (this as? TotalFiatBalance.Loaded)?.copy( - amount = this.amount + status.fiatAmount, + amount = this.amount + status.fiatAmount + fiatStakingBalance, ) ?: TotalFiatBalance.Loaded( - amount = status.fiatAmount, + amount = status.fiatAmount + fiatStakingBalance, isAllAmountsSummarized = true, ) } @@ -71,12 +76,13 @@ internal class TokenListFiatBalanceOperations( ): TotalFiatBalance { return with(currentBalance) { val isTokenAmountCanBeSummarized = status.fiatAmount != null - + val yieldBalance = (status.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero() + val fiatYieldBalance = status.fiatRate?.times(yieldBalance).orZero() (this as? TotalFiatBalance.Loaded)?.copy( - amount = this.amount + (status.fiatAmount ?: BigDecimal.ZERO), + amount = this.amount + status.fiatAmount.orZero() + fiatYieldBalance, isAllAmountsSummarized = isTokenAmountCanBeSummarized, ) ?: TotalFiatBalance.Loaded( - amount = status.fiatAmount ?: BigDecimal.ZERO, + amount = status.fiatAmount.orZero() + fiatYieldBalance, isAllAmountsSummarized = isTokenAmountCanBeSummarized, ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index a2033bb707..a2a99ddea2 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -1,6 +1,7 @@ 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 @@ -62,8 +63,33 @@ 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> + /** * 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 + + /** + * 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 + + /** + * Returns list of addresses and crypto currency info in selected wallet [userWalletId] + */ + fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow> + + /** + * Returns list of addresses and crypto currency info in selected wallet [userWalletId] + */ + suspend fun getNetworkAddresses(userWalletId: UserWalletId): List } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt index b5c027ac68..4f52744205 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetPrimaryCurrencyStatusUpdatesUseCaseTest.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.left import arrow.core.right import com.tangem.domain.core.error.DataError +import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.mock.MockNetworks import com.tangem.domain.tokens.mock.MockQuotes @@ -13,6 +14,7 @@ import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.repository.MockCurrenciesRepository import com.tangem.domain.tokens.repository.MockNetworksRepository import com.tangem.domain.tokens.repository.MockQuotesRepository +import com.tangem.domain.tokens.repository.MockStakingRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import junit.framework.TestCase.assertEquals @@ -121,6 +123,7 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest { networkAddress = NetworkAddress.Single( defaultAddress = NetworkAddress.Address(value = "mock", NetworkAddress.Address.Type.Primary), ), + yieldBalance = YieldBalance.Error, ), ) } @@ -171,5 +174,6 @@ internal class GetPrimaryCurrencyStatusUpdatesUseCaseTest { ), quotesRepository = MockQuotesRepository(quotes), networksRepository = MockNetworksRepository(statuses), + stakingRepository = MockStakingRepository(), ) } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index 613246a85c..b5cd747c50 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -16,6 +16,7 @@ import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.repository.MockCurrenciesRepository import com.tangem.domain.tokens.repository.MockNetworksRepository import com.tangem.domain.tokens.repository.MockQuotesRepository +import com.tangem.domain.tokens.repository.MockStakingRepository import com.tangem.domain.wallets.models.UserWalletId import junit.framework.TestCase.assertEquals import kotlinx.coroutines.delay @@ -316,5 +317,6 @@ internal class GetTokenListUseCaseTest { ), quotesRepository = MockQuotesRepository(quotes), networksRepository = MockNetworksRepository(statuses), + stakingRepository = MockStakingRepository(), ) } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt index e08a5348dd..3f15752baf 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -49,7 +49,8 @@ internal object MockTokenLists { val loadingUngroupedTokenList = with(failedUngroupedTokenList) { copy( - currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) }.toNonEmptyListOrNull() ?: emptyList(), + currencies = currencies.map { it.copy(value = CryptoCurrencyStatus.Loading) }.toNonEmptyListOrNull() + ?: emptyList(), totalFiatBalance = TotalFiatBalance.Loading, ) } @@ -60,7 +61,9 @@ internal object MockTokenLists { groups = groups.map { group -> group.copy( currencies = group.currencies - .map { it.copy(value = CryptoCurrencyStatus.Loading) }, + .map { it.copy(value = CryptoCurrencyStatus.Loading) } + .toNonEmptyListOrNull() + ?: emptyList(), ) }.toNonEmptyListOrNull()!!, ) @@ -109,7 +112,7 @@ internal object MockTokenLists { val sortedGroupedTokenList: TokenList.GroupedByNetwork get() { - val groups = sortedNetworksGroups + val groups = sortedNetworksGroups.toNonEmptyList() return unsortedGroupedTokenList.copy( groups = groups, diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt index 7490827a6c..e3f88d9b52 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -1,6 +1,7 @@ package com.tangem.domain.tokens.mock import arrow.core.nonEmptyListOf +import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress @@ -151,6 +152,7 @@ internal object MockTokensStates { pendingTransactions = emptySet(), hasCurrentNetworkTransactions = false, networkAddress = requireNotNull(networkStatus.value as? NetworkStatus.Verified).address, + yieldBalance = YieldBalance.Error, ), ) } @@ -166,6 +168,7 @@ internal object MockTokensStates { .first { it.network == status.currency.network } .value as? NetworkStatus.Verified, ).address, + yieldBalance = YieldBalance.Error, ), ) } diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index 78e06e7087..5b5019dfd6 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -3,13 +3,15 @@ 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.tokens.model.CryptoCurrencyAddress 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 @@ -44,10 +46,38 @@ internal class MockNetworksRepository( } override fun isNeedToCreateAccountWithoutReserve(network: Network) = false + + override fun getNetworkAddressesFlow( + userWalletId: UserWalletId, + network: Network, + ): Flow> = channelFlow { + send(emptyList()) + } + + override fun getNetworkAddressesFlow(userWalletId: UserWalletId): Flow> = channelFlow { + send(emptyList()) + } + override suspend fun getNetworkAddresses( userWalletId: UserWalletId, network: Network, ): List { return emptyList() } + + override suspend fun getNetworkAddresses(userWalletId: UserWalletId): List { + return emptyList() + } + + override suspend fun getNetworkAddress( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): CryptoCurrencyAddress = CryptoCurrencyAddress(currency, "") + + override fun getNetworkAddressFlow( + userWalletId: UserWalletId, + currency: CryptoCurrency, + ): Flow = channelFlow { + send(CryptoCurrencyAddress(currency, "")) + } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt new file mode 100644 index 0000000000..e6436b5f62 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -0,0 +1,203 @@ +package com.tangem.domain.tokens.repository + +import com.tangem.domain.core.lce.LceFlow +import com.tangem.domain.core.lce.lceFlow +import com.tangem.domain.staking.model.* +import com.tangem.domain.staking.model.action.EnterAction +import com.tangem.domain.staking.model.action.StakingActionStatus +import com.tangem.domain.staking.model.action.StakingActionType +import com.tangem.domain.staking.model.transaction.StakingTransaction +import com.tangem.domain.staking.model.transaction.StakingTransactionStatus +import com.tangem.domain.staking.model.transaction.StakingTransactionType +import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyAddress +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import org.joda.time.DateTime +import java.math.BigDecimal + +class MockStakingRepository : StakingRepository { + override fun isStakingSupported(currencyId: String): Boolean = true + + override suspend fun fetchEnabledYields(refresh: Boolean) { /* no-op */ + } + + override suspend fun getEntryInfo(integrationId: String): StakingEntryInfo = StakingEntryInfo( + interestRate = 1.toBigDecimal(), + periodInDays = 2, + tokenSymbol = "SOL", + ) + + override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield = Yield( + id = "1", + token = Token( + name = "Solana", + network = NetworkType.SOLANA, + symbol = "SOL", + decimals = 18, + address = null, + coinGeckoId = "solana", + logoURI = null, + isPoints = null, + ), + tokens = listOf(), + args = Yield.Args( + enter = Yield.Args.Enter( + addresses = Yield.Args.Enter.Addresses( + address = AddressArgument( + required = false, + network = null, + minimum = null, + maximum = null, + ), + additionalAddresses = mapOf(), + ), + args = mapOf(), + ), + exit = null, + ), + status = Yield.Status(enter = false, exit = null), + apy = 1.toBigDecimal(), + rewardRate = 2.3, + rewardType = Yield.RewardType.APR, + metadata = Yield.Metadata( + name = "Yield", + logoUri = "", + description = "", + documentation = null, + gasFeeToken = Token( + name = "Solana", + network = NetworkType.SOLANA, + symbol = "SOL", + decimals = 18, + address = null, + coinGeckoId = null, + logoURI = null, + isPoints = null, + ), + token = Token( + name = "Solana", + network = NetworkType.SOLANA, + symbol = "SOL", + decimals = 18, + address = null, + coinGeckoId = null, + logoURI = null, + isPoints = null, + ), + tokens = listOf(), + type = "auto", + rewardSchedule = "1", + cooldownPeriod = Yield.Metadata.Period(days = 1), + warmupPeriod = Yield.Metadata.Period(days = 1), + rewardClaiming = "1", + defaultValidator = null, + minimumStake = null, + supportsMultipleValidators = false, + revshare = Yield.Metadata.Enabled(enabled = false), + fee = Yield.Metadata.Enabled(enabled = false), + ), + validators = listOf(), + isAvailable = false, + ) + + override suspend fun getStakingAvailabilityForActions( + cryptoCurrencyId: CryptoCurrency.ID, + symbol: String, + ): StakingAvailability = StakingAvailability.Unavailable + + override suspend fun fetchSingleYieldBalance( + userWalletId: UserWalletId, + address: CryptoCurrencyAddress, + refresh: Boolean, + ) { + /* no-op */ + } + + override fun getSingleYieldBalanceFlow( + userWalletId: UserWalletId, + address: CryptoCurrencyAddress, + ): Flow = channelFlow { + send(YieldBalance.Error) + } + + override suspend fun getSingleYieldBalanceSync( + userWalletId: UserWalletId, + address: CryptoCurrencyAddress, + ): YieldBalance = YieldBalance.Error + + override suspend fun fetchMultiYieldBalance( + userWalletId: UserWalletId, + addresses: List, + refresh: Boolean, + ) { + /* no-op */ + } + + override fun getMultiYieldBalanceFlow( + userWalletId: UserWalletId, + addresses: List, + ): Flow = channelFlow { + send( + YieldBalanceList.Data( + balances = listOf(YieldBalance.Error), + ), + ) + } + + override fun getMultiYieldBalanceLce( + userWalletId: UserWalletId, + addresses: List, + ): LceFlow = lceFlow { + send( + YieldBalanceList.Data( + balances = listOf(YieldBalance.Error), + ), + ) + } + + override suspend fun getMultiYieldBalanceSync( + userWalletId: UserWalletId, + addresses: List, + ): YieldBalanceList = YieldBalanceList.Data( + balances = listOf(YieldBalance.Error), + ) + + override suspend fun createEnterAction( + integrationId: String, + amount: BigDecimal, + address: String, + validatorAddress: String, + token: Token, + ): EnterAction = EnterAction( + id = "quis", + integrationId = "persequeris", + status = StakingActionStatus.PROCESSING, + type = StakingActionType.CLAIM_REWARDS, + currentStepIndex = 8701, + amount = BigDecimal.ZERO, + validatorAddress = null, + validatorAddresses = listOf(), + transactions = listOf(), + createdAt = DateTime.now(), + ) + + override suspend fun constructTransaction(transactionId: String): StakingTransaction = StakingTransaction( + id = "id", + network = NetworkType.SOLANA, + status = StakingTransactionStatus.SIGNED, + type = StakingTransactionType.FREEZE_ENERGY, + hash = null, + signedTransaction = null, + unsignedTransaction = null, + stepIndex = 9368, + error = null, + gasEstimate = null, + stakeId = null, + explorerUrl = null, + ledgerHwAppId = null, + isMessage = false, + ) +} \ No newline at end of file diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index 73348d6d0b..c40cbbcc83 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.domain.txhistory.models) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) + implementation(projects.domain.staking) /** Core modules */ implementation(projects.core.utils) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index daa4bcc969..76f5998502 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.demo.DemoConfig import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.GetCardTokensListUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -95,12 +96,14 @@ class SwapDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, dispatchers: CoroutineDispatcherProvider, ): GetCryptoCurrencyStatusesSyncUseCase { return GetCryptoCurrencyStatusesSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, dispatchers = dispatchers, ) } @@ -112,11 +115,13 @@ class SwapDomainModule { currenciesRepository: CurrenciesRepository, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, ): GetCardTokensListUseCase { return GetCardTokensListUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, ) } diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index aa70954160..f82d33b863 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.features.swap.domain) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) + implementation(projects.domain.staking) /** AndroidX */ implementation(deps.androidx.activity.compose) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt index 441abf4a2e..db8287c844 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/di/SwapPresentationModule.kt @@ -1,10 +1,10 @@ package com.tangem.feature.swap.di +import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository -import com.tangem.feature.swap.domain.* import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -23,11 +23,13 @@ class SwapPresentationModule { dispatcherProvider: CoroutineDispatcherProvider, quotesRepository: QuotesRepository, networksRepository: NetworksRepository, + stakingRepository: StakingRepository, ): GetCryptoCurrencyStatusSyncUseCase { return GetCryptoCurrencyStatusSyncUseCase( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, networksRepository = networksRepository, + stakingRepository = stakingRepository, dispatchers = dispatcherProvider, ) } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index 132a42865d..d685def542 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -129,6 +129,7 @@ internal object TokenDetailsPreviewData { onBalanceSelect = {}, displayCryptoBalance = "966,96 XLM", displayFiatBalance = "91,50$", + isBalanceSelectorEnabled = false, ) val balanceError = TokenDetailsBalanceBlockState.Error( actionButtons = actionButtons, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt index 825f45f6c5..3828253254 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -25,6 +25,7 @@ internal sealed class TokenDetailsBalanceBlockState { val onBalanceSelect: (TokenBalanceSegmentedButtonConfig) -> Unit, val displayCryptoBalance: String, val displayFiatBalance: String, + val isBalanceSelectorEnabled: Boolean, ) : TokenDetailsBalanceBlockState() data class Error( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 81e8ef5fc0..5a1524feaa 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -19,9 +19,11 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsTxHistoryTransactionStateConverter import com.tangem.feature.tokendetails.presentation.tokendetails.state.utils.getBalance import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.isNullOrZero import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal @@ -32,21 +34,17 @@ internal class TokenDetailsLoadedBalanceConverter( private val symbol: String, private val decimals: Int, private val clickIntents: TokenDetailsClickIntents, -) : Converter { + private val stakingFeatureToggles: StakingFeatureToggles, +) : Converter, TokenDetailsState> { private val txHistoryItemConverter by lazy { TokenDetailsTxHistoryTransactionStateConverter(symbol, decimals, clickIntents) } - data class Data( - val maybeCryptoCurrencyStatus: Either, - val maybeYieldBalance: Either?, - ) - - override fun convert(value: Data): TokenDetailsState { - return value.maybeCryptoCurrencyStatus.fold( + override fun convert(value: Either): TokenDetailsState { + return value.fold( ifLeft = { convertError() }, - ifRight = { convert(it, value.maybeYieldBalance?.getOrNull() ?: YieldBalance.Empty) }, + ifRight = { convert(it) }, ) } @@ -64,23 +62,17 @@ internal class TokenDetailsLoadedBalanceConverter( ) } - private fun convert(status: CryptoCurrencyStatus, yieldBalance: YieldBalance): TokenDetailsState { + private fun convert(status: CryptoCurrencyStatus): TokenDetailsState { val state = currentStateProvider() val currencyName = state.marketPriceBlockState.currencySymbol val pendingTxs = status.value.pendingTransactions.map(txHistoryItemConverter::convert).toPersistentList() - val stakingCryptoAmount = (yieldBalance as? YieldBalance.Data)?.let { - yieldBalance.balance.items.sumOf { it.amount } - } - val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) } return state.copy( tokenBalanceBlockState = getBalanceState( currentState = state.tokenBalanceBlockState, status = status, - stakingCryptoAmount = stakingCryptoAmount, - stakingFiatAmount = stakingFiatAmount, ), - stakingBlocksState = getYieldBalance(status, yieldBalance, state), + stakingBlocksState = getYieldBalance(status, state), marketPriceBlockState = getMarketPriceState(status = status.value, currencySymbol = currencyName), pendingTxs = pendingTxs, txHistoryState = if (state.txHistoryState is TxHistoryState.NotSupported) { @@ -94,9 +86,10 @@ internal class TokenDetailsLoadedBalanceConverter( private fun getBalanceState( currentState: TokenDetailsBalanceBlockState, status: CryptoCurrencyStatus, - stakingCryptoAmount: BigDecimal?, - stakingFiatAmount: BigDecimal?, ): TokenDetailsBalanceBlockState { + val stakingCryptoAmount = (status.value.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance() + val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) } + val isBalanceSelectorEnabled = stakingFeatureToggles.isStakingEnabled && !stakingCryptoAmount.isNullOrZero() return when (status.value) { is CryptoCurrencyStatus.NoQuote, is CryptoCurrencyStatus.Loaded, @@ -120,6 +113,7 @@ internal class TokenDetailsLoadedBalanceConverter( balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig, onBalanceSelect = clickIntents::onBalanceSelect, selectedBalanceType = currentState.selectedBalanceType, + isBalanceSelectorEnabled = isBalanceSelectorEnabled, ) is CryptoCurrencyStatus.Loading -> TokenDetailsBalanceBlockState.Loading( actionButtons = currentState.actionButtons, @@ -137,19 +131,14 @@ internal class TokenDetailsLoadedBalanceConverter( } } - private fun getYieldBalance( - status: CryptoCurrencyStatus, - yieldBalance: YieldBalance, - state: TokenDetailsState, - ): StakingBlocksState { - val stakingCryptoAmount = (yieldBalance as? YieldBalance.Data)?.let { - yieldBalance.balance.items.sumOf { it.amount } - } + private fun getYieldBalance(status: CryptoCurrencyStatus, state: TokenDetailsState): StakingBlocksState { + val yieldBalance = status.value.yieldBalance as? YieldBalance.Data + + val stakingCryptoAmount = yieldBalance?.getTotalStakingBalance() + val stakingRewardAmount = yieldBalance?.getRewardStakingBalance() val stakingFiatAmount = stakingCryptoAmount?.let { status.value.fiatRate?.multiply(it) } - val stakingRewardAmount = (yieldBalance as? YieldBalance.Data)?.let { - yieldBalance.balance.items.sumOf { it.amount.multiply(it.pricePerShare) } - } - val stakingBalance = if (stakingCryptoAmount == null) { + + val stakingBalance = if (stakingCryptoAmount.isNullOrZero()) { StakingBalance.Empty } else { StakingBalance.Content( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 7e860743ac..952b0d2dc6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -18,7 +18,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -32,6 +31,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.t import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents +import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider @@ -46,6 +46,7 @@ internal class TokenDetailsStateFactory( private val cryptoCurrencyStatusProvider: Provider, private val clickIntents: TokenDetailsClickIntents, private val featureToggles: TokenDetailsFeatureToggles, + stakingFeatureToggles: StakingFeatureToggles, symbol: String, decimals: Int, ) { @@ -68,6 +69,7 @@ internal class TokenDetailsStateFactory( symbol = symbol, decimals = decimals, clickIntents = clickIntents, + stakingFeatureToggles = stakingFeatureToggles, ) } @@ -121,11 +123,8 @@ internal class TokenDetailsStateFactory( fun getCurrencyLoadedBalanceState( cryptoCurrencyEither: Either, - yieldBalanceEither: Either?, ): TokenDetailsState { - return tokenDetailsLoadedBalanceConverter.convert( - TokenDetailsLoadedBalanceConverter.Data(cryptoCurrencyEither, yieldBalanceEither), - ) + return tokenDetailsLoadedBalanceConverter.convert(cryptoCurrencyEither) } fun getManageButtonsState(actions: List): TokenDetailsState { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 638e43d98d..2c71568659 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -108,7 +108,6 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { modifier = itemModifier, isBalanceHidden = state.isBalanceHidden, state = state.tokenBalanceBlockState, - isStakingAvailable = state.isStakingBlockShown, ) } items( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt index ac4738e499..394e52f37b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -29,7 +29,6 @@ import kotlinx.collections.immutable.toImmutableList internal fun TokenDetailsBalanceBlock( state: TokenDetailsBalanceBlockState, isBalanceHidden: Boolean, - isStakingAvailable: Boolean, modifier: Modifier = Modifier, ) { Surface( @@ -54,7 +53,7 @@ internal fun TokenDetailsBalanceBlock( .weight(1f) .padding(top = TangemTheme.dimens.spacing12), ) - if (isStakingAvailable) BalanceButtons(state) + BalanceButtons(state) } FiatBalance( state = state, @@ -138,7 +137,7 @@ private fun CryptoBalance( @Composable private fun BalanceButtons(state: TokenDetailsBalanceBlockState) { - if (state !is TokenDetailsBalanceBlockState.Content) return + if (state !is TokenDetailsBalanceBlockState.Content || !state.isBalanceSelectorEnabled) return SegmentedButtons( config = state.balanceSegmentedButtonConfig, @@ -172,7 +171,7 @@ private fun Preview_TokenDetailsBalanceBlock( @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, ) { TangemThemePreview { - TokenDetailsBalanceBlock(state = state, isBalanceHidden = false, isStakingAvailable = true) + TokenDetailsBalanceBlock(state = state, isBalanceHidden = false) } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 848518dbf1..9553f54131 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -30,7 +30,6 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase import com.tangem.domain.staking.GetStakingAvailabilityUseCase import com.tangem.domain.staking.GetStakingEntryInfoUseCase -import com.tangem.domain.staking.GetStakingYieldBalanceUseCase import com.tangem.domain.staking.GetYieldUseCase import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.tokens.* @@ -108,7 +107,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val stakingFeatureToggles: StakingFeatureToggles, private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase, private val getYieldUseCase: GetYieldUseCase, - private val getStakingYieldBalanceUseCase: GetStakingYieldBalanceUseCase, private val swapRepository: SwapRepository, private val swapTransactionRepository: SwapTransactionRepository, private val quotesRepository: QuotesRepository, @@ -135,7 +133,6 @@ internal class TokenDetailsViewModel @Inject constructor( ?: error("This screen can't open without `CryptoCurrency`") private val userWallet: UserWallet - private var stakingIntegrationId: String? = null lateinit var router: InnerTokenDetailsRouter @@ -157,6 +154,7 @@ internal class TokenDetailsViewModel @Inject constructor( symbol = cryptoCurrency.symbol, decimals = cryptoCurrency.decimals, featureToggles = tokenDetailsFeatureToggles, + stakingFeatureToggles = stakingFeatureToggles, ) private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) { @@ -220,6 +218,7 @@ internal class TokenDetailsViewModel @Inject constructor( } private fun updateContent() { + subscribeOnCurrencyStatusUpdates() subscribeOnExchangeTransactionsUpdates() updateTxHistory(refresh = false, showItemsLoading = true) @@ -281,36 +280,12 @@ internal class TokenDetailsViewModel @Inject constructor( ) .distinctUntilChanged() .onEach { maybeCurrencyStatus -> - maybeCurrencyStatus.fold( - ifLeft = { - internalUiState.value = stateFactory.getCurrencyLoadedBalanceState( - maybeCurrencyStatus, - null, - ) - }, - ifRight = { status -> - cryptoCurrencyStatus = status - - if (stakingIntegrationId != null) { - getStakingYieldBalanceUseCase( - userWalletId = userWalletId, - address = status.value.networkAddress?.defaultAddress?.value.orEmpty(), - integrationId = stakingIntegrationId.orEmpty(), - ).onEach { maybeYieldBalance -> - internalUiState.value = stateFactory.getCurrencyLoadedBalanceState( - maybeCurrencyStatus, - maybeYieldBalance, - ) - }.launchIn(viewModelScope) - } else { - internalUiState.value = - stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus, null) - } - - updateButtons(currencyStatus = status) - updateWarnings(status) - }, - ) + internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus) + maybeCurrencyStatus.onRight { status -> + cryptoCurrencyStatus = status + updateButtons(currencyStatus = status) + updateWarnings(status) + } currencyStatusAnalyticsSender.send(maybeCurrencyStatus) } .flowOn(dispatchers.main) @@ -411,12 +386,8 @@ internal class TokenDetailsViewModel @Inject constructor( ) internalUiState.value = stateFactory.getStateWithUpdatedStakingAvailability(stakingAvailability) if (stakingAvailability is StakingAvailability.Available) { - stakingIntegrationId = stakingAvailability.integrationId val stakingInfo = getStakingEntryInfoUseCase(stakingAvailability.integrationId) - subscribeOnCurrencyStatusUpdates() internalUiState.value = stateFactory.getStateWithStaking(stakingInfo) - } else { - subscribeOnCurrencyStatusUpdates() } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt index f41d4d4ee7..73198feee8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenItemStateConverter.kt @@ -5,11 +5,13 @@ import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.staking.model.YieldBalance import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.utils.Provider import com.tangem.utils.converter.Converter +import com.tangem.utils.extensions.orZero import java.math.BigDecimal internal class TokenItemStateConverter( @@ -61,13 +63,16 @@ internal class TokenItemStateConverter( } private fun CryptoCurrencyStatus.getFormattedAmount(): String { - val amount = value.amount ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN + val yieldBalance = (value.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero() + val amount = value.amount?.plus(yieldBalance) ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals) } private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String { - val fiatAmount = value.fiatAmount ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN + val yieldBalance = (value.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero() + val fiatYieldBalance = value.fiatRate?.times(yieldBalance).orZero() + val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return TokenItemState.UNKNOWN_AMOUNT_SIGN val appCurrency = appCurrencyProvider() return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)