Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-23 13:15:47 +03:00
commit 1a1bf8a43b
10 changed files with 81 additions and 51 deletions

View file

@ -21,7 +21,7 @@ class GetSupportedNetworksUseCase(
ensureNotNull(networks.takeIf { it.isNotEmpty() }) {
SupportedBlockchainException.EmptyList
}
}.sortedBy(Network::name)
}
}
}

View file

@ -1,20 +1,20 @@
package com.tangem.domain.staking.model.stakekit.action
enum class StakingActionType {
STAKE,
UNSTAKE,
CLAIM_REWARDS,
RESTAKE_REWARDS,
WITHDRAW,
RESTAKE,
CLAIM_UNSTAKED,
UNLOCK_LOCKED,
STAKE_LOCKED,
VOTE,
REVOKE,
VOTE_LOCKED,
REVOTE,
REBOND,
MIGRATE,
UNKNOWN,
enum class StakingActionType(val type: String) {
STAKE("Stake"),
UNSTAKE("Unstake"),
CLAIM_REWARDS("Claim Rewards"),
RESTAKE_REWARDS("Restake Rewards"),
WITHDRAW("Withdraw"),
RESTAKE("Restake"),
CLAIM_UNSTAKED("Claim Unstaked"),
UNLOCK_LOCKED("Unlock Locked"),
STAKE_LOCKED("Stake Locked"),
VOTE("Vote"),
REVOKE("Revoke"),
VOTE_LOCKED("Vote Locked"),
REVOTE("Revote"),
REBOND("Rebond"),
MIGRATE("Migrate"),
UNKNOWN("Unknown"),
}

View file

@ -151,13 +151,12 @@ internal class CurrenciesStatusesOperations(
val currenciesFlow = combine(
getQuotes(currenciesIds),
getNetworksStatuses(networks),
getYieldBalances(nonEmptyCurrencies),
) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances ->
) { maybeQuotes, maybeNetworksStatuses ->
createCurrenciesStatuses(
currencies = nonEmptyCurrencies,
maybeQuotes = maybeQuotes,
maybeNetworkStatuses = maybeNetworksStatuses,
maybeYieldBalances = maybeYieldBalances,
maybeYieldBalances = null,
)
}
@ -403,15 +402,6 @@ 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> {

View file

@ -47,18 +47,20 @@ internal sealed class StakingAnalyticsEvents(
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"Validator" to validator,
"Action" to action.name,
"Action" to action.type,
),
)
data class StakeInProgressScreenOpened(
val validator: String,
val token: String,
val action: StakingActionType,
) : StakingAnalyticsEvents(
event = "Stake In Progress Screen Opened",
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"Validator" to validator,
"Action" to action.type,
),
)
@ -136,9 +138,13 @@ internal sealed class StakingAnalyticsEvents(
data class StakingError(
val token: String,
val errorType: String,
) : StakingAnalyticsEvents(
event = "Errors",
params = mapOf(AnalyticsParam.TOKEN_PARAM to token),
params = mapOf(
AnalyticsParam.TOKEN_PARAM to token,
"ErrorType" to errorType,
),
)
data class TransactionError(

View file

@ -104,6 +104,7 @@ internal class StakingAnalyticSender(
StakingAnalyticsEvents.StakeInProgressScreenOpened(
validator = validatorName,
token = value.cryptoCurrencySymbol,
action = getStakingActionType(value),
),
)
}

View file

@ -49,9 +49,10 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
suspend fun getFee(
pendingAction: PendingAction?,
pendingActions: ImmutableList<PendingAction>?,
onFeeError: (GetFeeError) -> Unit,
onStakingFee: (Fee) -> Unit,
onStakingFeeError: (StakingError) -> Unit,
onApprovalFee: (TransactionFee) -> Unit,
onFeeError: (GetFeeError) -> Unit,
) {
val state = stateController.value
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
@ -85,7 +86,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
pendingActions = pendingActions,
amount = amount,
validatorAddress = validatorAddress,
onFeeError = onFeeError,
onStakingFeeError = onStakingFeeError,
onStakingFee = onStakingFee,
)
}
@ -95,7 +96,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
pendingActions = pendingActions,
amount = amount,
validatorAddress = validatorAddress,
onFeeError = onFeeError,
onStakingFeeError = onStakingFeeError,
onStakingFee = onStakingFee,
)
}
@ -106,7 +107,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
pendingActions: ImmutableList<PendingAction>?,
amount: BigDecimal,
validatorAddress: String,
onFeeError: (GetFeeError) -> Unit,
onStakingFeeError: (StakingError) -> Unit,
onStakingFee: (Fee) -> Unit,
) {
val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
@ -125,7 +126,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
action = action,
)
}.getOrElse {
onFeeError(GetFeeError.DataError(Throwable(it.toString())))
onStakingFeeError(it)
null
}
}
@ -133,7 +134,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
}
if (result.isNullOrEmpty()) {
onFeeError(GetFeeError.UnknownError)
onStakingFeeError(StakingError.UnknownError)
return
}
@ -151,7 +152,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
validatorAddress = validatorAddress,
action = pendingAction,
).getOrElse {
onFeeError(GetFeeError.DataError(Throwable(it.toString())))
onStakingFeeError(it)
return
}
}

View file

@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionStatus
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.SendTransactionError
@ -130,7 +131,10 @@ internal class StakingTransactionSender @AssistedInject constructor(
fee: Fee,
onConstructError: (StakingError) -> Unit,
) = coroutineScope {
stakingTransactions?.filterNot { it.type == StakingTransactionType.APPROVAL }
stakingTransactions
?.filterNot {
it.type == StakingTransactionType.APPROVAL || it.status == StakingTransactionStatus.SKIPPED
}
?.map { transaction ->
async {
getConstructedStakingTransactionUseCase(
@ -150,7 +154,9 @@ internal class StakingTransactionSender @AssistedInject constructor(
},
)
}
}?.awaitAll()?.filterNotNull()
}
?.awaitAll()
?.filterNotNull()
}
private suspend fun getStakingTransaction(
@ -185,7 +191,12 @@ internal class StakingTransactionSender @AssistedInject constructor(
type = action?.type,
),
).getOrElse {
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(state.cryptoCurrencyName))
analyticsEventHandler.send(
StakingAnalyticsEvents.StakingError(
token = state.cryptoCurrencyName,
errorType = it.javaClass.simpleName,
),
)
onConstructError(it)
return emptyList()
}
@ -248,9 +259,12 @@ internal class StakingTransactionSender @AssistedInject constructor(
rawCurrencyId = rawCurrencyId,
),
)
.onLeft {
.onLeft { error ->
analyticsEventHandler.send(
StakingAnalyticsEvents.StakingError(stateController.value.cryptoCurrencyName),
StakingAnalyticsEvents.StakingError(
token = stateController.value.cryptoCurrencyName,
errorType = error.javaClass.simpleName,
),
)
saveUnsubmittedHashUseCase.invoke(
transactionId = transactionId,

View file

@ -81,11 +81,11 @@ internal class AddStakingNotificationsTransformer(
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
)
).max(minimumRequirement)
} else {
// No amount is taken from account balance on exit or pending actions
BigDecimal.ZERO
}.max(minimumRequirement)
}
val notifications = buildList {
// errors

View file

@ -32,5 +32,5 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
internal fun isSolanaWithdraw(networkId: String, pendingActions: ImmutableList<PendingAction>?): Boolean {
val isSolana = isSolana(networkId)
val isWithdraw = pendingActions?.all { it.type == StakingActionType.WITHDRAW } == true
return isSolana && isWithdraw
return isSolana && isWithdraw && !pendingActions.isNullOrEmpty()
}

View file

@ -1,7 +1,10 @@
package com.tangem.features.staking.impl.presentation.viewmodel
import android.os.Bundle
import androidx.lifecycle.*
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.routing.AppRoute
@ -256,8 +259,18 @@ internal class StakingViewModel @Inject constructor(
)
updateNotifications()
},
onStakingFeeError = { error ->
analyticsEventHandler.send(
StakingAnalyticsEvents.StakingError(
value.cryptoCurrencyName,
error.javaClass.simpleName,
),
)
stateController.update(AddStakingErrorTransformer())
updateNotifications(GetFeeError.UnknownError)
},
onFeeError = { error ->
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName))
stateController.update(AddStakingErrorTransformer())
updateNotifications(error)
},
@ -285,7 +298,12 @@ internal class StakingViewModel @Inject constructor(
},
onConstructError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
analyticsEventHandler.send(
StakingAnalyticsEvents.StakingError(
token = value.cryptoCurrencyName,
errorType = error.javaClass.simpleName,
),
)
stakingEventFactory.createStakingErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -296,7 +314,7 @@ internal class StakingViewModel @Inject constructor(
},
onSendError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName))
stakingEventFactory.createSendTransactionErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
},