Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-29 14:31:53 +03:00
commit 652159dbce
316 changed files with 6242 additions and 3084 deletions

View file

@ -0,0 +1,41 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.stakekit.NetworkType
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
import com.tangem.domain.staking.repositories.StakingActionRepository
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
/**
* Use case for getting pending actions list.
*/
class FetchActionsUseCase(
private val stakingRepository: StakingRepository,
private val stakingActionRepository: StakingActionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
networkType: NetworkType,
stakingActionStatus: StakingActionStatus,
): Either<StakingError, Unit> {
return Either
.catch {
val actions = stakingRepository.getActions(
userWalletId = userWalletId,
cryptoCurrency = cryptoCurrency,
networkType = networkType,
stakingActionStatus = stakingActionStatus,
)
stakingActionRepository.store(userWalletId, cryptoCurrency.id, actions)
}
.mapLeft { stakingErrorResolver.resolve(it) }
}
}

View file

@ -0,0 +1,34 @@
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.action.StakingAction
import com.tangem.domain.staking.repositories.StakingActionRepository
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
/**
* Use case for getting pending actions list.
*/
class GetActionsUseCase(
private val stakingActionRepository: StakingActionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(
userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID,
): EitherFlow<StakingError, List<StakingAction>> {
return stakingActionRepository.get(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrencyId,
).map<List<StakingAction>, Either<StakingError, List<StakingAction>>> { it.right() }
.catch { emit(stakingErrorResolver.resolve(it).left()) }
}
}

View file

@ -1,25 +0,0 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
/**
* Use case for getting saved staking pending transactions.
*/
class GetStakingPendingTransactionsUseCase(
private val stakingPendingTransactionRepository: StakingPendingTransactionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(userWalletId: UserWalletId): Either<StakingError, List<BalanceItem>> {
return Either.catch {
stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId).map { it.second }
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
}

View file

@ -1,104 +1,85 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
import org.joda.time.DateTime
import java.math.BigDecimal
import java.util.UUID
class InvalidatePendingTransactionsUseCase(
private val stakingPendingTransactionRepository: StakingPendingTransactionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(
userWalletId: UserWalletId,
balanceItems: List<BalanceItem>,
balancesId: Int,
processingActions: List<StakingAction>,
): Either<StakingError, List<BalanceItem>> {
return Either.catch {
val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions(
realData = balanceItems,
newBalancesId = balancesId,
pendingData = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId),
val balancesToDisplay = mergeBalancesAndProcessingActions(
realBalances = balanceItems,
processingActions = processingActions,
)
stakingPendingTransactionRepository.removeTransactions(userWalletId, transactionsToRemove.toSet())
balancesToDisplay
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
private fun mergeRealAndPendingTransactions(
realData: List<BalanceItem>,
newBalancesId: Int,
pendingData: List<Pair<PendingTransaction, BalanceItem>>,
): Pair<List<BalanceItem>, List<PendingTransaction>> {
val balances = realData.groupBy { BalanceIdentity(it.groupId, it.type, it.amount, it.date) }
.mapValues { it.value.toMutableList() }
.toMutableMap()
private fun mergeBalancesAndProcessingActions(
realBalances: List<BalanceItem>,
processingActions: List<StakingAction>,
): List<BalanceItem> {
val balances = realBalances.toMutableList()
val transactionsToRemove = mutableListOf<PendingTransaction>()
pendingData.forEach { (pendingTransaction, balanceItem) ->
val key = BalanceIdentity(balanceItem.groupId, balanceItem.type, balanceItem.amount, balanceItem.date)
val oldBalancesId = pendingTransaction.balancesId
when {
newBalancesId != oldBalancesId -> {
transactionsToRemove.add(pendingTransaction)
processingActions.forEach { action ->
when (action.type) {
StakingActionType.STAKE, StakingActionType.VOTE, StakingActionType.VOTE_LOCKED -> {
processEnterAction(balances, action)
}
balances.containsKey(key) -> {
val removed = balances[key]?.removeIf { !it.isPending } ?: false
if (removed) {
balances[key]?.add(balanceItem)
}
StakingActionType.WITHDRAW -> {
modifyBalancesByStatus(balances, action, BalanceType.UNSTAKED)
}
StakingActionType.UNLOCK_LOCKED -> {
modifyBalancesByStatus(balances, action, BalanceType.LOCKED)
}
StakingActionType.UNSTAKE -> {
modifyBalancesByStatus(balances, action, BalanceType.STAKED)
}
else -> {
val groupId = UUID.randomUUID().toString()
val now = DateTime.now()
balances[BalanceIdentity(groupId, BalanceType.STAKED, pendingTransaction.amount, now)] =
mutableListOf(
BalanceItem(
groupId = groupId,
type = pendingTransaction.type,
amount = pendingTransaction.amount,
rawCurrencyId = pendingTransaction.rawCurrencyId,
validatorAddress = pendingTransaction.validator?.address,
date = null,
pendingActions = emptyList(),
token = pendingTransaction.token,
isPending = true,
),
)
balances.entries.find {
it.key.type == BalanceType.STAKED &&
it.key.amount == pendingTransaction.amount &&
!it.value.any { it.isPending }
}
?.key
?.let { balances.remove(it) }
// intentionally do nothing
}
}
}
return balances.values.flatten() to transactionsToRemove
return balances
}
private data class BalanceIdentity(
val groupId: String,
val type: BalanceType,
val amount: BigDecimal,
val date: DateTime?,
)
private fun processEnterAction(balances: MutableList<BalanceItem>, action: StakingAction) {
balances.add(
BalanceItem(
groupId = UUID.randomUUID().toString(),
token = balances[0].token,
type = BalanceType.STAKED,
amount = action.amount,
rawCurrencyId = null,
validatorAddress = action.validatorAddress ?: action.validatorAddresses?.get(0) ?: "",
date = null,
pendingActions = emptyList(),
isPending = true,
),
)
}
private fun modifyBalancesByStatus(balances: MutableList<BalanceItem>, action: StakingAction, type: BalanceType) {
val index = balances.indexOfFirst {
!it.isPending && it.amount == action.amount && it.type == type
}
if (index != -1) {
balances[index] = balances[index].copy(isPending = true)
}
}
}

View file

@ -1,28 +0,0 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.repositories.StakingErrorResolver
import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository
import com.tangem.domain.wallets.models.UserWalletId
/**
* Use case for saving hash that failed to submit during staking confirmation
*/
class SavePendingTransactionUseCase(
private val stakingPendingTransactionRepository: StakingPendingTransactionRepository,
private val stakingErrorResolver: StakingErrorResolver,
) {
operator fun invoke(userWalletId: UserWalletId, transaction: PendingTransaction): Either<StakingError, Unit> {
return Either.catch {
stakingPendingTransactionRepository.saveTransaction(
userWalletId = userWalletId,
transaction = transaction,
)
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
interface StakingActionRepository {
suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, actions: List<StakingAction>)
fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow<List<StakingAction>>
}

View file

@ -1,14 +0,0 @@
package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.wallets.models.UserWalletId
interface StakingPendingTransactionRepository {
fun getTransactionsWithBalanceItems(userWalletId: UserWalletId): List<Pair<PendingTransaction, BalanceItem>>
fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction)
fun removeTransactions(userWalletId: UserWalletId, transactions: Set<PendingTransaction>)
}

View file

@ -3,14 +3,15 @@ package com.tangem.domain.staking.repositories
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
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.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
@ -34,6 +35,13 @@ interface StakingRepository {
fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability
suspend fun getActions(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
networkType: NetworkType,
stakingActionStatus: StakingActionStatus,
): List<StakingAction>
suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
@ -50,16 +58,11 @@ interface StakingRepository {
refresh: Boolean = false,
)
fun getMultiYieldBalanceFlow(
fun getMultiYieldBalanceUpdates(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList>
fun getMultiYieldBalanceLce(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): LceFlow<Throwable, YieldBalanceList>
suspend fun getMultiYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,