Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-25 17:49:53 +03:00
parent dab5fb51b6
commit 967d5520a1
37 changed files with 452 additions and 408 deletions

View file

@ -9,7 +9,6 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
@ -120,7 +119,6 @@ internal sealed class StakingStates {
val transactionDoneState: TransactionDoneState,
val isApprovalNeeded: Boolean,
val reduceAmountBy: BigDecimal?,
val possiblePendingTransaction: PendingTransaction?,
) : ConfirmationState()
data class Empty(

View file

@ -1,6 +1,9 @@
package com.tangem.features.staking.impl.presentation.state.helpers
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
import com.tangem.domain.staking.FetchActionsUseCase
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -20,16 +23,21 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val fetchActionsUseCase: FetchActionsUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
@Assisted private val userWallet: UserWallet,
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
@Assisted private val yield: Yield,
) {
fun scheduleUpdates() {
coroutineScope.launch {
listOf(
// we should update network to find pending tx after 1 sec
async {
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
fetchPendingTransactionsUseCase(
userWalletId = userWallet.walletId,
networks = setOf(cryptoCurrencyStatus.currency.network),
)
},
// we should update tx history and network for new balances
async {
@ -41,6 +49,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
async {
updateNetworkStatuses()
},
async {
updateProcessingActions()
},
).awaitAll()
}
}
@ -54,6 +65,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
async {
updateNetworkStatuses(delay = 0)
},
async {
updateProcessingActions()
},
).awaitAll()
}
}
@ -91,9 +105,22 @@ internal class StakingBalanceUpdater @AssistedInject constructor(
}
}
private suspend fun updateProcessingActions() {
fetchActionsUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
networkType = yield.token.network,
stakingActionStatus = StakingActionStatus.PROCESSING,
)
}
@AssistedFactory
interface Factory {
fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater
fun create(
cryptoCurrencyStatus: CryptoCurrencyStatus,
userWallet: UserWallet,
yield: Yield,
): StakingBalanceUpdater
}
private companion object {

View file

@ -6,7 +6,6 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.SubmitHashData
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
@ -36,7 +35,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import timber.log.Timber
import java.math.BigDecimal
import java.util.UUID
@Suppress("LongParameterList")
internal class StakingTransactionSender @AssistedInject constructor(
@ -48,7 +46,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val submitHashUseCase: SubmitHashUseCase,
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
private val savePendingTransactionUseCase: SavePendingTransactionUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
@Assisted private val userWallet: UserWallet,
@ -57,7 +54,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
) {
private val balanceUpdater: StakingBalanceUpdater
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet)
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield)
suspend fun constructAndSendTransactions(
onConstructSuccess: (List<StakingTransaction>) -> Unit,
@ -95,16 +92,11 @@ internal class StakingTransactionSender @AssistedInject constructor(
sendStakingTransaction(
fullTransactionsData = fullTransactionsData,
possiblePendingTransaction = composePendingTransaction(confirmationState),
onSendSuccess = onSendSuccess,
onSendError = onSendError,
)
}
private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
return confirmationState.possiblePendingTransaction ?: composeStakeTransaction()
}
private suspend fun getStakingTransactions(
state: StakingUiState,
confirmationState: StakingStates.ConfirmationState.Data,
@ -214,7 +206,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
private suspend fun sendStakingTransaction(
fullTransactionsData: List<FullTransactionData>,
possiblePendingTransaction: PendingTransaction,
onSendSuccess: (txUrl: String) -> Unit,
onSendError: (SendTransactionError?) -> Unit,
) {
@ -230,7 +221,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
submitHash(
transactions = fullTransactionsData.map { it.stakeKitTransaction },
transactionHashes = transactionHashes,
pendingTransaction = possiblePendingTransaction,
)
val txUrl = getExplorerTransactionUrlUseCase(
txHash = transactionHashes.last(),
@ -243,11 +233,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}
private suspend fun submitHash(
transactions: List<StakingTransaction>,
transactionHashes: List<String>,
pendingTransaction: PendingTransaction,
) {
private suspend fun submitHash(transactions: List<StakingTransaction>, transactionHashes: List<String>) {
transactions
.zip(transactionHashes)
.forEach { (transaction, transactionHash) ->
@ -255,7 +241,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
SubmitHashData(
transactionId = transaction.id,
transactionHash = transactionHash,
pendingTransaction = pendingTransaction,
),
)
.onLeft { error ->
@ -271,9 +256,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}.onRight {
Timber.d("Successful hash submission")
if (transaction.type != StakingTransactionType.FREEZE_ENERGY) {
savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction)
}
}
}
}
@ -292,23 +274,6 @@ internal class StakingTransactionSender @AssistedInject constructor(
)
}
private fun composeStakeTransaction(): PendingTransaction {
val state = stateController.value
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
return PendingTransaction(
groupId = UUID.randomUUID().toString(),
token = token,
type = BalanceType.STAKED,
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
validator = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator,
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
)
}
private data class FullTransactionData(
val stakeKitTransaction: StakingTransaction,
val tangemTransaction: TransactionData.Compiled,

View file

@ -47,6 +47,5 @@ internal object ConfirmationStatePreviewData {
isApprovalNeeded = false,
reduceAmountBy = null,
pendingActions = null,
possiblePendingTransaction = null,
)
}

View file

@ -63,7 +63,6 @@ internal class SetConfirmationStateInitTransformer(
reduceAmountBy = null,
pendingAction = pendingAction,
pendingActions = pendingActions.takeIf { isComposePendingActions },
possiblePendingTransaction = null,
),
)
}

View file

@ -1,40 +0,0 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class SetPossiblePendingTransactionTransformer(
private val yield: Yield,
private val balanceState: BalanceState,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState =
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
val balancesId = yieldBalance?.getBalancesUniqueId() ?: 0
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
return prevState.copy(
confirmationState = possibleConfirmationState.copy(
possiblePendingTransaction = PendingTransaction(
groupId = balanceState.groupId,
token = token,
type = balanceState.type,
amount = balanceState.cryptoDecimal,
rawCurrencyId = balanceState.rawCurrencyId,
validator = balanceState.validator,
balancesId = balancesId,
),
),
)
}
}

View file

@ -25,14 +25,13 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase
import com.tangem.domain.staking.IsAnyTokenStakedUseCase
import com.tangem.domain.staking.IsApproveNeededUseCase
import com.tangem.domain.staking.*
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
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.transaction.StakingTransaction
import com.tangem.domain.tokens.*
@ -74,7 +73,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@ -108,6 +106,7 @@ internal class StakingViewModel @Inject constructor(
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
private val analyticsEventHandler: AnalyticsEventHandler,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val getActionsUseCase: GetActionsUseCase,
@DelayedWork private val coroutineScope: CoroutineScope,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -131,6 +130,7 @@ internal class StakingViewModel @Inject constructor(
?: error("This screen can't be opened without `Yield`")
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
private var processingActions: List<StakingAction> = emptyList()
private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var innerRouter: InnerStakingRouter by Delegates.notNull()
@ -141,9 +141,8 @@ internal class StakingViewModel @Inject constructor(
get() {
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
return invalidatePendingTransactionsUseCase(
userWalletId = userWallet.walletId,
balanceItems = yieldBalance?.balance?.items ?: emptyList(),
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
processingActions = processingActions,
).getOrElse { emptyList() }
}
@ -153,6 +152,7 @@ internal class StakingViewModel @Inject constructor(
stakingBalanceUpdater.create(
cryptoCurrencyStatus,
userWallet,
yield,
)
}
@ -215,11 +215,6 @@ internal class StakingViewModel @Inject constructor(
override fun onNextClick(balanceState: BalanceState?) {
stakingStateRouter.onNextClick()
if (balanceState != null && (isInitState() || isAssentState())) {
stateController.update(
SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus),
)
}
}
override fun getFee() {
@ -281,7 +276,6 @@ internal class StakingViewModel @Inject constructor(
transactionsInProgress.addAll(constructedTransactions)
},
onConstructError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(
StakingAnalyticsEvents.StakekitError(
token = value.cryptoCurrencySymbol,
@ -297,7 +291,6 @@ internal class StakingViewModel @Inject constructor(
stateController.update(SetConfirmationStateCompletedTransformer(txUrl))
},
onSendError = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
stakingEventFactory.createSendTransactionErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
@ -527,7 +520,6 @@ internal class StakingViewModel @Inject constructor(
userWalletId = userWalletId,
).fold(
ifLeft = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
stateController.update(
SetConfirmationStateAssentApprovalTransformer(
@ -549,7 +541,6 @@ internal class StakingViewModel @Inject constructor(
network = tokenCryptoCurrency.network,
).fold(
ifLeft = { error ->
Timber.e(error.toString())
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol))
stateController.update(
SetConfirmationStateAssentApprovalTransformer(
@ -760,7 +751,6 @@ internal class StakingViewModel @Inject constructor(
userWallet = wallet
},
ifLeft = {
Timber.e(it.toString())
stakingEventFactory.createGenericErrorAlert(it.toString())
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -790,10 +780,10 @@ internal class StakingViewModel @Inject constructor(
setupApprovalNeeded()
setupIsAnyTokenStaked()
checkIfSubtractAvailable()
subscribeOnActionsUpdates()
subscribeOnStepChanges()
},
ifLeft = { error ->
Timber.e(error.toString())
stakingEventFactory.createGenericErrorAlert(error.toString())
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@ -831,18 +821,7 @@ internal class StakingViewModel @Inject constructor(
.onEach {
when {
isInitState() -> {
stateController.updateAll(
SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = isAnyTokenStaked,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
SetConfirmationStateEmptyTransformer,
)
updateInitialData()
onRefreshSwipe(isRefreshing = false)
}
isAssentState() -> {
@ -862,6 +841,40 @@ internal class StakingViewModel @Inject constructor(
.launchIn(viewModelScope)
}
private fun subscribeOnActionsUpdates() {
getActionsUseCase(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrencyId,
)
.conflate()
.distinctUntilChanged()
.onEach { result ->
result.getOrNull()?.let { actions ->
processingActions = actions
if (isInitState()) {
updateInitialData()
}
}
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
}
private fun updateInitialData() {
stateController.updateAll(
SetInitialDataStateTransformer(
clickIntents = this@StakingViewModel,
yield = yield,
isAnyTokenStaked = isAnyTokenStaked,
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
userWalletProvider = Provider { userWallet },
appCurrencyProvider = Provider { appCurrency },
balancesToShowProvider = Provider { balancesToShow },
),
SetConfirmationStateEmptyTransformer,
)
}
private fun prepareForConfirmation(
pendingActions: ImmutableList<PendingAction> = persistentListOf(),
pendingAction: PendingAction? = pendingActions.firstOrNull(),