Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-16 19:14:30 +03:00
parent f39e22c82e
commit bb32493ab0
13 changed files with 100 additions and 75 deletions

View file

@ -74,18 +74,6 @@ internal object StakingDomainModule {
)
}
@Provides
@Singleton
fun provideGetStakingYieldBalanceUseCase(
stakingRepository: StakingRepository,
stakingErrorResolver: StakingErrorResolver,
): GetStakingYieldBalanceUseCase {
return GetStakingYieldBalanceUseCase(
stakingRepository = stakingRepository,
stakingErrorResolver = stakingErrorResolver,
)
}
@Provides
@Singleton
fun provideInitializeStakingProcessUseCase(

View file

@ -29,22 +29,31 @@ internal class DefaultStakingBalanceStore(
}
}
override fun get(userWalletId: UserWalletId, integrationId: String): Flow<YieldBalanceWrapperDTO> {
override fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalanceWrapperDTO> {
return dataStore.get(userWalletId.stringValue)
.mapNotNull { balances ->
balances.firstOrNull { it.integrationId == integrationId }
balances.firstOrNull { it.integrationId == integrationId && it.addresses.address == address }
}
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId, integrationId: String): YieldBalanceWrapperDTO? {
override suspend fun getSyncOrNull(
userWalletId: UserWalletId,
address: String,
integrationId: String,
): YieldBalanceWrapperDTO? {
return dataStore.getSyncOrNull(userWalletId.stringValue)
?.firstOrNull { it.integrationId == integrationId }
?.firstOrNull { it.integrationId == integrationId && it.addresses.address == address }
}
override suspend fun store(userWalletId: UserWalletId, integrationId: String, item: YieldBalanceWrapperDTO) {
override suspend fun store(
userWalletId: UserWalletId,
integrationId: String,
address: String,
item: YieldBalanceWrapperDTO,
) {
mutex.withLock {
val balances = dataStore.getSyncOrNull(userWalletId.stringValue)
?.addOrReplace(item) { it.integrationId == integrationId }
?.addOrReplace(item) { it.integrationId == integrationId && it.addresses.address == address }
?: setOf(item)
dataStore.store(userWalletId.stringValue, balances)

View file

@ -12,9 +12,13 @@ interface StakingBalanceStore {
suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>)
fun get(userWalletId: UserWalletId, integrationId: String): Flow<YieldBalanceWrapperDTO>
fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalanceWrapperDTO>
suspend fun getSyncOrNull(userWalletId: UserWalletId, integrationId: String): YieldBalanceWrapperDTO?
suspend fun getSyncOrNull(
userWalletId: UserWalletId,
address: String,
integrationId: String,
): YieldBalanceWrapperDTO?
suspend fun store(userWalletId: UserWalletId, integrationId: String, item: YieldBalanceWrapperDTO)
suspend fun store(userWalletId: UserWalletId, integrationId: String, address: String, item: YieldBalanceWrapperDTO)
}

View file

@ -37,7 +37,6 @@ import com.tangem.domain.staking.model.stakekit.NetworkType
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
@ -310,6 +309,7 @@ internal class DefaultStakingRepository(
stakingBalanceStore.store(
userWalletId,
requestBody.integrationId,
address,
YieldBalanceWrapperDTO(
balances = result,
integrationId = requestBody.integrationId,
@ -328,9 +328,10 @@ internal class DefaultStakingRepository(
send(YieldBalance.Empty)
} else {
launch(dispatchers.io) {
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
stakingBalanceStore.get(userWalletId, integrationId)
stakingBalanceStore.get(userWalletId, address, integrationId)
.collectLatest {
send(yieldBalanceConverter.convert(it))
}
@ -354,9 +355,12 @@ internal class DefaultStakingRepository(
} else {
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
val result = stakingBalanceStore.getSyncOrNull(userWalletId, integrationId)
val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
?: return@withContext YieldBalance.Error
yieldBalanceConverter.convert(result)
@ -379,17 +383,19 @@ internal class DefaultStakingRepository(
block = {
val availableCurrencies = cryptoCurrencies
.mapNotNull { currency ->
val address = walletManagersFacade.getDefaultAddress(userWalletId, currency.network)
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
if (integrationId != null && address != null) {
address to integrationId
if (integrationId != null) {
addresses to integrationId
} else {
null
}
}
.distinctBy { it.second }
.map { getBalanceRequestData(it.first, it.second) }
.flatMap { (addresses, integrationId) ->
addresses.map { address -> address to integrationId }
}
.map { getBalanceRequestData(it.first.value, it.second) }
.ifEmpty { return@invokeOnExpire }
val result = stakeKitApi.getMultipleYieldBalances(availableCurrencies).getOrThrow()

View file

@ -5,10 +5,13 @@ sealed class YieldBalanceList {
data class Data(
val balances: List<YieldBalance>,
) : YieldBalanceList() {
fun getBalance(rawCurrencyId: String?): YieldBalance {
return balances.firstOrNull { yield ->
(yield as? YieldBalance.Data)?.balance?.items
?.any { rawCurrencyId == it.rawCurrencyId } == true
fun getBalance(address: String?, rawCurrencyId: String?): YieldBalance {
return balances.firstOrNull { yieldBalance ->
val data = yieldBalance as? YieldBalance.Data
data?.balance?.items?.any {
address == data.address && rawCurrencyId == it.rawCurrencyId
} == true
} ?: YieldBalance.Error
}
}

View file

@ -1,31 +0,0 @@
package com.tangem.domain.staking
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.core.utils.EitherFlow
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
class GetStakingYieldBalanceUseCase(
private val stakingRepository: StakingRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): EitherFlow<StakingError, YieldBalance> {
return stakingRepository.getSingleYieldBalanceFlow(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
).map<YieldBalance, Either<StakingError, YieldBalance>> { it.right() }
.catch { emit(stakingErrorResolver.resolve(it).left()) }
}
}

View file

@ -44,7 +44,7 @@ class GetCurrencyStatusUpdatesUseCase(
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return flow {
emitAll(
getCurrency(
getCurrencyStatus(
userWalletId = userWalletId,
currencyId = currencyId,
isSingleWalletWithTokens = isSingleWalletWithTokens,
@ -53,7 +53,7 @@ class GetCurrencyStatusUpdatesUseCase(
}.flowOn(dispatchers.io)
}
private suspend fun getCurrency(
private suspend fun getCurrencyStatus(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean,

View file

@ -56,7 +56,7 @@ class GetNodlTokenListUseCase(
stakingRepository = stakingRepository,
)
return operations.getNodlCurrencyStatusesFlow()
return operations.getCardCurrenciesStatusesFlow()
.map { maybeCurrenciesStatuses ->
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
}

View file

@ -4,7 +4,7 @@ import com.tangem.domain.tokens.model.TokenList
sealed class TokenListError {
object EmptyTokens : TokenListError()
data object EmptyTokens : TokenListError()
data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : TokenListError()

View file

@ -10,6 +10,7 @@ internal fun CurrenciesStatusesOperations.Error.mapToCurrencyError(): CurrencySt
is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses,
is CurrenciesStatusesOperations.Error.EmptyQuotes,
is CurrenciesStatusesOperations.Error.EmptyCurrencies,
is CurrenciesStatusesOperations.Error.EmptyAddresses,
is CurrenciesStatusesOperations.Error.UnableToCreateCurrencyStatus,
-> CurrencyStatusError.UnableToCreateCurrency
}

View file

@ -10,6 +10,7 @@ internal fun CurrenciesStatusesOperations.Error.mapToTokenListError(): TokenList
is CurrenciesStatusesOperations.Error.EmptyNetworksStatuses,
is CurrenciesStatusesOperations.Error.EmptyQuotes,
is CurrenciesStatusesOperations.Error.EmptyCurrencies,
is CurrenciesStatusesOperations.Error.EmptyAddresses,
is CurrenciesStatusesOperations.Error.UnableToCreateCurrencyStatus,
is CurrenciesStatusesOperations.Error.EmptyYieldBalances,
-> TokenListError.EmptyTokens

View file

@ -73,6 +73,7 @@ internal class CurrenciesStatusesLceOperations(
getQuotes(currenciesIds),
getNetworksStatuses(userWalletId, networks),
getYieldBalances(userWalletId, nonEmptyCurrencies),
) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances ->
val statuses = createCurrenciesStatuses(
currencies = nonEmptyCurrencies,
@ -144,11 +145,15 @@ internal class CurrenciesStatusesLceOperations(
currencies.map { currency ->
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
val address = extractAddress(networkStatus)
val isStakingSupported = stakingRepository.isStakingSupported(
stakingRepository.getIntegrationKey(currency.id),
)
val yieldBalance = if (isStakingSupported) {
(yieldBalances as? YieldBalanceList.Data)?.getBalance(currency.id.rawCurrencyId)
(yieldBalances as? YieldBalanceList.Data)?.getBalance(
address = address,
rawCurrencyId = currency.id.rawCurrencyId,
)
} else {
null
}
@ -221,4 +226,13 @@ internal class CurrenciesStatusesLceOperations(
return networks to currenciesIds
}
private fun extractAddress(networkStatus: NetworkStatus?): String? {
return when (val value = networkStatus?.value) {
is NetworkStatus.NoAccount -> value.address.defaultAddress.value
is NetworkStatus.Unreachable -> value.address?.defaultAddress?.value
is NetworkStatus.Verified -> value.address.defaultAddress.value
else -> null
}
}
}

View file

@ -36,7 +36,12 @@ internal class CurrenciesStatusesOperations(
networksRepository.getNetworkStatusesSync(userWalletId, networks, false).right()
val yieldBalances = getYieldBalancesSync(nonEmptyCurrencies)
return createCurrenciesStatuses(nonEmptyCurrencies, quotes, networkStatuses, yieldBalances)
return createCurrenciesStatuses(
nonEmptyCurrencies,
quotes,
networkStatuses,
yieldBalances,
)
},
catch = { raise(Error.DataError(it)) },
)
@ -115,8 +120,7 @@ internal class CurrenciesStatusesOperations(
return createCurrencyStatus(currency, quotes, networkStatus, yieldBalances)
}
/** Get flow of currency statuses for NODL card */
fun getNodlCurrencyStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
fun getCardCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
return flow {
val nonEmptyCurrencies = recover(
block = { getCurrenciesFromCard(userWalletId) },
@ -147,12 +151,13 @@ internal class CurrenciesStatusesOperations(
val currenciesFlow = combine(
getQuotes(currenciesIds),
getNetworksStatuses(networks),
) { maybeQuotes, maybeNetworksStatuses ->
getYieldBalances(nonEmptyCurrencies),
) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances ->
createCurrenciesStatuses(
currencies = nonEmptyCurrencies,
maybeQuotes = maybeQuotes,
maybeNetworkStatuses = maybeNetworksStatuses,
maybeYieldBalances = null,
maybeYieldBalances = maybeYieldBalances,
)
}
@ -265,11 +270,16 @@ internal class CurrenciesStatusesOperations(
currencies.map { currency ->
val quote = quotes?.firstOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }
val networkStatus = networksStatuses?.firstOrNull { it.network == currency.network }
val address = extractAddress(networkStatus)
val isStakingSupported = stakingRepository.isStakingSupported(
stakingRepository.getIntegrationKey(currency.id),
)
val yieldBalance = if (isStakingSupported) {
(yieldBalances as? YieldBalanceList.Data)?.getBalance(currency.id.rawCurrencyId)
(yieldBalances as? YieldBalanceList.Data)?.getBalance(
address = address,
rawCurrencyId = currency.id.rawCurrencyId,
)
} else {
null
}
@ -393,6 +403,15 @@ internal class CurrenciesStatusesOperations(
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
}
private fun getYieldBalances(cryptoCurrencies: List<CryptoCurrency>): Flow<Either<Error, YieldBalanceList>> {
return stakingRepository.getMultiYieldBalanceFlow(
userWalletId = userWalletId,
cryptoCurrencies = cryptoCurrencies,
).map<YieldBalanceList, Either<Error, YieldBalanceList>> { it.right() }
.catch { emit(Error.DataError(it).left()) }
.onEmpty { emit(Error.EmptyYieldBalances.left()) }
}
private suspend fun getYieldBalancesSync(
cryptoCurrencies: List<CryptoCurrency>,
): Either<Error.EmptyYieldBalances, YieldBalanceList> {
@ -449,6 +468,15 @@ internal class CurrenciesStatusesOperations(
return networks to currenciesIds
}
private fun extractAddress(networkStatus: NetworkStatus?): String? {
return when (val value = networkStatus?.value) {
is NetworkStatus.NoAccount -> value.address.defaultAddress.value
is NetworkStatus.Unreachable -> value.address?.defaultAddress?.value
is NetworkStatus.Verified -> value.address.defaultAddress.value
else -> null
}
}
sealed class Error {
data object EmptyCurrencies : Error()
@ -457,6 +485,8 @@ internal class CurrenciesStatusesOperations(
data object EmptyNetworksStatuses : Error()
data object EmptyAddresses : Error()
data object UnableToCreateCurrencyStatus : Error()
data class DataError(val cause: Throwable) : Error()