Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-29 12:24:26 +03:00
parent b2189b3895
commit 4f3d1f10c7
11 changed files with 268 additions and 80 deletions

View file

@ -1,10 +1,8 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.FeeState
import com.tangem.features.staking.impl.presentation.state.InnerConfirmationStakingState
@ -17,31 +15,23 @@ import kotlinx.collections.immutable.ImmutableList
internal class SetConfirmationStateAssentTransformer(
private val appCurrencyProvider: Provider<AppCurrency>,
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
private val stakingGasEstimate: StakingGasEstimate,
private val fee: Fee,
private val pendingActionList: ImmutableList<PendingAction>,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
confirmationState = prevState.confirmationState.copyWrapped(stakingGasEstimate),
confirmationState = prevState.confirmationState.copyWrapped(fee),
)
}
private fun StakingStates.ConfirmationState.copyWrapped(
gasEstimate: StakingGasEstimate,
): StakingStates.ConfirmationState {
private fun StakingStates.ConfirmationState.copyWrapped(fee: Fee): StakingStates.ConfirmationState {
if (this is StakingStates.ConfirmationState.Data) {
val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true
return copy(
innerState = InnerConfirmationStakingState.ASSENT,
feeState = FeeState.Content(
fee = Fee.Common(
Amount(
currencySymbol = gasEstimate.token.symbol,
value = gasEstimate.amount,
decimals = gasEstimate.token.decimals,
),
),
fee = fee,
rate = feeCryptoCurrencyStatus?.value?.fiatRate,
isFeeConvertibleToFiat = isFeeConvertibleToFiat,
appCurrency = appCurrencyProvider(),

View file

@ -1,9 +1,7 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.StakingStates
@ -15,32 +13,24 @@ import com.tangem.utils.transformer.Transformer
internal class SetConfirmationStateCompletedTransformer(
private val appCurrencyProvider: Provider<AppCurrency>,
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
private val stakingGasEstimate: StakingGasEstimate,
private val fee: Fee,
private val txUrl: String,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
confirmationState = prevState.confirmationState.copyWrapped(stakingGasEstimate),
confirmationState = prevState.confirmationState.copyWrapped(fee),
)
}
private fun StakingStates.ConfirmationState.copyWrapped(
gasEstimate: StakingGasEstimate,
): StakingStates.ConfirmationState {
private fun StakingStates.ConfirmationState.copyWrapped(fee: Fee): StakingStates.ConfirmationState {
if (this is StakingStates.ConfirmationState.Data) {
val isFeeConvertibleToFiat = feeCryptoCurrencyStatus?.currency?.network?.hasFiatFeeRate == true
return copy(
isPrimaryButtonEnabled = true,
innerState = InnerConfirmationStakingState.COMPLETED,
feeState = FeeState.Content(
fee = Fee.Common(
Amount(
currencySymbol = gasEstimate.token.symbol,
value = gasEstimate.amount,
decimals = gasEstimate.token.decimals,
),
),
fee = fee,
rate = feeCryptoCurrencyStatus?.value?.fiatRate,
isFeeConvertibleToFiat = isFeeConvertibleToFiat,
appCurrency = appCurrencyProvider(),

View file

@ -6,7 +6,9 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
@ -28,7 +30,6 @@ 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
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
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
@ -37,10 +38,7 @@ import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
@ -70,6 +68,7 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import timber.log.Timber
import java.math.BigDecimal
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
import kotlin.properties.Delegates
@ -87,6 +86,7 @@ internal class StakingViewModel @Inject constructor(
private val getConstructedStakingTransactionUseCase: GetConstructedStakingTransactionUseCase,
private val estimateGasUseCase: EstimateGasUseCase,
private val sendTransactionUseCase: SendTransactionUseCase,
private val sendMultipleTransactionUseCase: SendMultipleTransactionUseCase,
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
private val submitHashUseCase: SubmitHashUseCase,
@ -143,7 +143,7 @@ internal class StakingViewModel @Inject constructor(
private var stakingApproval: StakingApproval = StakingApproval.Empty
private val allowanceTaskScheduler = SingleTaskScheduler<BigDecimal>()
private var transactionInProgress: StakingTransaction? = null
private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()
private var approvalJobHolder: JobHolder = JobHolder()
@ -187,7 +187,7 @@ internal class StakingViewModel @Inject constructor(
val defaultAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: error("No available address")
val stakingTransaction = getStakingTransactionUseCase(
val stakingTransactions = getStakingTransactionUseCase(
userWalletId = userWalletId,
network = cryptoCurrencyStatus.currency.network,
params = ActionParams(
@ -206,9 +206,9 @@ internal class StakingViewModel @Inject constructor(
return@launch
}
stakingTransaction
val fullTransactionsData = stakingTransactions
.filterNot { it.type == StakingTransactionType.APPROVAL }
.forEach { transaction ->
.map { transaction ->
val (constructedTransaction, transactionData) = getConstructedStakingTransactionUseCase(
networkId = cryptoCurrencyStatus.currency.network.id.value,
fee = fee,
@ -218,17 +218,20 @@ internal class StakingViewModel @Inject constructor(
stakingEventFactory.createStakingErrorAlert(it)
return@launch
}
val gasEstimate = constructedTransaction.gasEstimate
?: return@launch stakingEventFactory.createGenericErrorAlert("Gas estimate is null")
transactionInProgress = constructedTransaction
sendStakingTransaction(
transactionId = constructedTransaction.id,
gasEstimate = gasEstimate,
txData = transactionData,
pendingActionList = confirmationState.pendingActions,
FullTransactionData(
stakeKitTransaction = constructedTransaction,
tangemTransaction = transactionData,
)
}
transactionsInProgress.addAll(fullTransactionsData.map { it.stakeKitTransaction })
sendStakingTransaction(
fullTransactionsData = fullTransactionsData,
fee = fee,
pendingActionList = confirmationState.pendingActions,
)
}
}
}
@ -291,7 +294,7 @@ internal class StakingViewModel @Inject constructor(
validatorAddress: String,
) {
val pendingAction = pendingActions.firstOrNull()
val stakingGasEstimate = estimateGasUseCase(
val gasEstimate = estimateGasUseCase(
userWalletId = userWalletId,
network = cryptoCurrencyStatus.currency.network,
params = ActionParams(
@ -313,7 +316,13 @@ internal class StakingViewModel @Inject constructor(
SetConfirmationStateAssentTransformer(
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
stakingGasEstimate = stakingGasEstimate,
fee = Fee.Common(
Amount(
currencySymbol = gasEstimate.token.symbol,
value = gasEstimate.amount,
decimals = gasEstimate.token.decimals,
),
),
pendingActionList = pendingActions,
),
)
@ -555,8 +564,8 @@ internal class StakingViewModel @Inject constructor(
val email = FeedbackEmailType.StakingProblem(
cardInfo = cardInfo,
validatorName = validator?.name,
transactionType = transactionInProgress?.type?.name,
unsignedTransaction = transactionInProgress?.unsignedTransaction,
transactionTypes = transactionsInProgress.map { it.type.name },
unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction },
)
feedbackManager.sendEmail(email)
@ -632,13 +641,12 @@ internal class StakingViewModel @Inject constructor(
}
private suspend fun sendStakingTransaction(
transactionId: String,
gasEstimate: StakingGasEstimate,
txData: TransactionData,
fullTransactionsData: List<FullTransactionData>,
fee: Fee,
pendingActionList: ImmutableList<PendingAction>,
) {
sendTransactionUseCase(
txData = txData,
sendMultipleTransactionUseCase(
txsData = fullTransactionsData.map { it.tangemTransaction },
userWallet = userWallet,
network = cryptoCurrencyStatus.currency.network,
).fold(
@ -648,18 +656,21 @@ internal class StakingViewModel @Inject constructor(
SetConfirmationStateAssentTransformer(
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
stakingGasEstimate = gasEstimate,
fee = fee,
pendingActionList = pendingActionList,
),
)
stakingEventFactory.createSendTransactionErrorAlert(error)
},
ifRight = { txHash ->
transactionInProgress = null
submitHash(transactionId, txHash)
ifRight = { transactionHashes ->
transactionsInProgress.clear()
submitHash(
transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id },
transactionHashes = transactionHashes,
)
scheduleUpdates()
val txUrl = getExplorerTransactionUrlUseCase(
txHash = txHash,
txHash = transactionHashes.last(),
networkId = cryptoCurrencyStatus.currency.network.id,
).getOrElse { "" }
@ -667,7 +678,7 @@ internal class StakingViewModel @Inject constructor(
SetConfirmationStateCompletedTransformer(
appCurrencyProvider = Provider { appCurrency },
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
stakingGasEstimate = gasEstimate,
fee = fee,
txUrl = txUrl,
),
)
@ -675,18 +686,22 @@ internal class StakingViewModel @Inject constructor(
)
}
private suspend fun submitHash(transactionId: String, transactionHash: String) {
submitHashUseCase.submitHash(
transactionId = transactionId,
transactionHash = transactionHash,
)
.onLeft {
saveUnsubmittedHashUseCase.invoke(
private suspend fun submitHash(transactionIds: List<String>, transactionHashes: List<String>) {
transactionIds
.zip(transactionHashes)
.forEach { (transactionId, transactionHash) ->
submitHashUseCase.submitHash(
transactionId = transactionId,
transactionHash = transactionHash,
)
}.onRight {
Timber.d("Successful hash submission")
.onLeft {
saveUnsubmittedHashUseCase.invoke(
transactionId = transactionId,
transactionHash = transactionHash,
)
}.onRight {
Timber.d("Successful hash submission")
}
}
}
@ -750,6 +765,11 @@ internal class StakingViewModel @Inject constructor(
InnerConfirmationStakingState.ASSENT
}
private data class FullTransactionData(
val stakeKitTransaction: StakingTransaction,
val tangemTransaction: TransactionData.Compiled,
)
private companion object {
const val WHAT_IS_STAKING_ARTICLE_URL = "https://tangem.com/en/blog/post/how-to-stake-cryptocurrency/"
const val ALLOWANCE_UPDATE_DELAY = 10_000L