Updated on 2026-08-14
This commit is contained in:
parent
6d758e0608
commit
6474fa0a8b
19 changed files with 842 additions and 433 deletions
|
|
@ -42,13 +42,7 @@ internal class StakingAnalyticSender(
|
|||
StakingAnalyticsEvents.ConfirmationScreenOpened(
|
||||
token = value.cryptoCurrencySymbol,
|
||||
validator = validatorName,
|
||||
action = when (value.actionType) {
|
||||
StakingActionCommonType.ENTER -> StakingActionType.STAKE
|
||||
StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
-> confirmationState.pendingAction?.type ?: StakingActionType.UNKNOWN
|
||||
},
|
||||
action = getStakingActionType(value),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -83,12 +77,24 @@ internal class StakingAnalyticSender(
|
|||
)
|
||||
}
|
||||
|
||||
fun sendTransactionStakingAnalytics(cryptoCurrency: CryptoCurrency, validator: String) {
|
||||
fun sendTransactionStakingAnalytics(value: StakingUiState) {
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
val validatorState = confirmationState?.validatorState as? ValidatorState.Content
|
||||
val validatorName = validatorState?.chosenValidator?.name ?: return
|
||||
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvents.ButtonAction(
|
||||
action = getStakingActionType(value).name,
|
||||
token = value.cryptoCurrencyName,
|
||||
validator = validatorName,
|
||||
),
|
||||
)
|
||||
|
||||
analyticsEventHandler.send(
|
||||
Basic.TransactionSent(
|
||||
sentFrom = AnalyticsParam.TxSentFrom.Staking(
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
token = cryptoCurrency.symbol,
|
||||
blockchain = value.cryptoCurrencyName,
|
||||
token = value.cryptoCurrencySymbol,
|
||||
feeType = AnalyticsParam.FeeType.Normal,
|
||||
),
|
||||
memoType = Basic.TransactionSent.MemoType.Null,
|
||||
|
|
@ -96,9 +102,21 @@ internal class StakingAnalyticSender(
|
|||
)
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvents.StakeInProgressScreenOpened(
|
||||
validator = validator,
|
||||
token = cryptoCurrency.symbol,
|
||||
validator = validatorName,
|
||||
token = value.cryptoCurrencySymbol,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStakingActionType(value: StakingUiState): StakingActionType {
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
|
||||
return when (value.actionType) {
|
||||
StakingActionCommonType.ENTER -> StakingActionType.STAKE
|
||||
StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
-> confirmationState?.pendingAction?.type ?: StakingActionType.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +92,7 @@ internal sealed class StakingStates {
|
|||
val feeState: FeeState,
|
||||
val validatorState: ValidatorState,
|
||||
val pendingAction: PendingAction?,
|
||||
val pendingActions: ImmutableList<PendingAction>?,
|
||||
val notifications: ImmutableList<NotificationUM>,
|
||||
val footerText: TextReference,
|
||||
val transactionDoneState: TransactionDoneState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.helpers
|
||||
|
||||
import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase
|
||||
import com.tangem.domain.tokens.FetchPendingTransactionsUseCase
|
||||
import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.coroutines.DelayedWork
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class StakingBalanceUpdater @AssistedInject constructor(
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val updateDelayedNetworkStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) {
|
||||
fun scheduleUpdates() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
|
||||
},
|
||||
// we should update tx history and network for new balances
|
||||
async {
|
||||
updateStakeBalance()
|
||||
},
|
||||
async {
|
||||
updateTxHistory()
|
||||
},
|
||||
async {
|
||||
updateNetworkStatuses()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun instantUpdate() {
|
||||
coroutineScope {
|
||||
listOf(
|
||||
async {
|
||||
updateStakeBalance()
|
||||
},
|
||||
async {
|
||||
updateNetworkStatuses(delay = 0)
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateNetworkStatuses(delay: Long = BALANCE_UPDATE_DELAY) {
|
||||
updateDelayedNetworkStatusUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
delayMillis = delay,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateStakeBalance() {
|
||||
stakingYieldBalanceUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateTxHistory() {
|
||||
delay(BALANCE_UPDATE_DELAY)
|
||||
val txHistoryItemsCountEither = getTxHistoryItemsCountUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
)
|
||||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
getTxHistoryItemsUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val BALANCE_UPDATE_DELAY = 11_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.helpers
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.domain.staking.EstimateGasUseCase
|
||||
import com.tangem.domain.staking.model.StakingApproval
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.ActionParams
|
||||
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStateController
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.ValidatorState
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class StakingFeeTransactionLoader @AssistedInject constructor(
|
||||
private val stateController: StakingStateController,
|
||||
private val getAllowanceUseCase: GetAllowanceUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val estimateGasUseCase: EstimateGasUseCase,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val yield: Yield,
|
||||
@Assisted private val stakingApproval: StakingApproval,
|
||||
) {
|
||||
|
||||
suspend fun getFee(
|
||||
pendingAction: PendingAction?,
|
||||
pendingActions: ImmutableList<PendingAction>?,
|
||||
onFeeError: (GetFeeError) -> Unit,
|
||||
onStakingFee: (Fee) -> Unit,
|
||||
onApprovalFee: (TransactionFee) -> Unit,
|
||||
) {
|
||||
val state = stateController.value
|
||||
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
|
||||
val amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided")
|
||||
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
|
||||
val approval = stakingApproval as? StakingApproval.Needed
|
||||
if (approval != null) {
|
||||
val allowance = getAllowanceUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
spenderAddress = approval.spenderAddress,
|
||||
).getOrElse { BigDecimal.ZERO }
|
||||
|
||||
if (allowance < amount) {
|
||||
getApproveFee(
|
||||
amount = amount,
|
||||
validatorAddress = validatorAddress,
|
||||
onApprovalFee = onApprovalFee,
|
||||
onApprovalFeeError = onFeeError,
|
||||
)
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
pendingActions = pendingActions,
|
||||
amount = amount,
|
||||
validatorAddress = validatorAddress,
|
||||
onFeeError = onFeeError,
|
||||
onStakingFee = onStakingFee,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
pendingActions = pendingActions,
|
||||
amount = amount,
|
||||
validatorAddress = validatorAddress,
|
||||
onFeeError = onFeeError,
|
||||
onStakingFee = onStakingFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun estimateGas(
|
||||
pendingAction: PendingAction?,
|
||||
pendingActions: ImmutableList<PendingAction>?,
|
||||
amount: BigDecimal,
|
||||
validatorAddress: String,
|
||||
onFeeError: (GetFeeError) -> Unit,
|
||||
onStakingFee: (Fee) -> Unit,
|
||||
) {
|
||||
val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
|
||||
val gasEstimate = if (isSolanaWithdraw(cryptoCurrencyStatus.currency.network.id.value, pendingActions)) {
|
||||
val result = coroutineScope {
|
||||
pendingActions?.map { action ->
|
||||
async {
|
||||
// Simultaneous or quick api calls can sometimes return ZERO fee
|
||||
estimateFeeRetry {
|
||||
estimateFee(
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
action = action,
|
||||
)
|
||||
}.getOrElse {
|
||||
onFeeError(GetFeeError.DataError(Throwable(it.toString())))
|
||||
null
|
||||
}
|
||||
}
|
||||
}?.awaitAll()?.filterNotNull()
|
||||
}
|
||||
|
||||
if (result.isNullOrEmpty()) {
|
||||
onFeeError(GetFeeError.UnknownError)
|
||||
return
|
||||
}
|
||||
|
||||
val totalAmount = result.sumOf { it.amount }
|
||||
val totalGasLimit = result.sumOf { it.gasLimit?.toBigDecimalOrNull().orZero() }
|
||||
StakingGasEstimate(
|
||||
amount = totalAmount,
|
||||
token = result.first().token,
|
||||
gasLimit = totalGasLimit.toPlainString().orEmpty(),
|
||||
)
|
||||
} else {
|
||||
estimateFee(
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
action = pendingAction,
|
||||
).getOrElse {
|
||||
onFeeError(GetFeeError.DataError(Throwable(it.toString())))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onStakingFee(
|
||||
Fee.Common(
|
||||
Amount(
|
||||
currencySymbol = gasEstimate.token.symbol,
|
||||
value = gasEstimate.amount,
|
||||
decimals = gasEstimate.token.decimals,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun estimateFee(
|
||||
amount: BigDecimal,
|
||||
sourceAddress: String,
|
||||
validatorAddress: String,
|
||||
action: PendingAction?,
|
||||
) = estimateGasUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
params = ActionParams(
|
||||
actionCommonType = stateController.value.actionType,
|
||||
integrationId = yield.id,
|
||||
amount = amount,
|
||||
address = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
token = yield.token,
|
||||
passthrough = action?.passthrough,
|
||||
type = action?.type,
|
||||
),
|
||||
)
|
||||
|
||||
private suspend fun getApproveFee(
|
||||
amount: BigDecimal,
|
||||
validatorAddress: String,
|
||||
onApprovalFee: (TransactionFee) -> Unit,
|
||||
onApprovalFeeError: (GetFeeError) -> Unit,
|
||||
) {
|
||||
getFeeUseCase(
|
||||
amount = amount,
|
||||
destination = validatorAddress,
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
).fold(
|
||||
ifRight = { fee ->
|
||||
onApprovalFee(fee)
|
||||
},
|
||||
ifLeft = { error ->
|
||||
onApprovalFeeError(error)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun estimateFeeRetry(
|
||||
times: Int = 3,
|
||||
delay: Long = 1000,
|
||||
block: suspend () -> Either<StakingError, StakingGasEstimate>,
|
||||
): Either<StakingError, StakingGasEstimate> {
|
||||
repeat(times - 1) {
|
||||
val feeResult = block()
|
||||
feeResult.fold(
|
||||
ifLeft = { return feeResult },
|
||||
ifRight = {
|
||||
if (!it.amount.isZero()) return feeResult
|
||||
},
|
||||
)
|
||||
delay(delay)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
userWallet: UserWallet,
|
||||
yield: Yield,
|
||||
stakingApproval: StakingApproval,
|
||||
): StakingFeeTransactionLoader
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.helpers
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
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.GetConstructedStakingTransactionUseCase
|
||||
import com.tangem.domain.staking.GetStakingTransactionUseCase
|
||||
import com.tangem.domain.staking.SaveUnsubmittedHashUseCase
|
||||
import com.tangem.domain.staking.SubmitHashUseCase
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.StakingError
|
||||
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.StakingTransactionType
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.domain.transaction.usecase.SendMultipleTransactionUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class StakingTransactionSender @AssistedInject constructor(
|
||||
private val stateController: StakingStateController,
|
||||
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
|
||||
private val getStakingTransactionUseCase: GetStakingTransactionUseCase,
|
||||
private val getConstructedStakingTransactionUseCase: GetConstructedStakingTransactionUseCase,
|
||||
private val sendMultipleTransactionUseCase: SendMultipleTransactionUseCase,
|
||||
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
|
||||
private val submitHashUseCase: SubmitHashUseCase,
|
||||
private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
@Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
@Assisted private val yield: Yield,
|
||||
@Assisted private val isAmountSubtractAvailable: Boolean,
|
||||
) {
|
||||
|
||||
private val balanceUpdater: StakingBalanceUpdater
|
||||
get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet)
|
||||
|
||||
suspend fun constructAndSendTransactions(
|
||||
onConstructSuccess: (List<StakingTransaction>) -> Unit,
|
||||
onConstructError: (StakingError) -> Unit,
|
||||
onSendSuccess: (String) -> Unit,
|
||||
onSendError: (SendTransactionError?) -> Unit,
|
||||
) {
|
||||
val state = stateController.value
|
||||
|
||||
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val fee = (confirmationState.feeState as? FeeState.Content)?.fee
|
||||
?: error("No fee provided")
|
||||
|
||||
val stakingTransactions = getStakingTransactions(
|
||||
state = state,
|
||||
confirmationState = confirmationState,
|
||||
onConstructError = onConstructError,
|
||||
)
|
||||
|
||||
val fullTransactionsData = getConstructedTransactions(
|
||||
stakingTransactions = stakingTransactions,
|
||||
fee = fee,
|
||||
onConstructError = onConstructError,
|
||||
)
|
||||
|
||||
if (fullTransactionsData.isNullOrEmpty()) {
|
||||
onConstructError(StakingError.UnknownError)
|
||||
return
|
||||
}
|
||||
|
||||
onConstructSuccess(fullTransactionsData.map { it.stakeKitTransaction })
|
||||
|
||||
sendStakingTransaction(
|
||||
fullTransactionsData = fullTransactionsData,
|
||||
onSendSuccess = onSendSuccess,
|
||||
onSendError = onSendError,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getStakingTransactions(
|
||||
state: StakingUiState,
|
||||
confirmationState: StakingStates.ConfirmationState.Data,
|
||||
onConstructError: (StakingError) -> Unit,
|
||||
) = coroutineScope {
|
||||
val isAllWithdrawAction = isSolanaWithdraw(
|
||||
cryptoCurrencyStatus.currency.network.id.value,
|
||||
confirmationState.pendingActions,
|
||||
)
|
||||
if (isAllWithdrawAction) {
|
||||
confirmationState.pendingActions?.map { action ->
|
||||
async {
|
||||
getStakingTransaction(
|
||||
state = state,
|
||||
action = action,
|
||||
confirmationState = confirmationState,
|
||||
onConstructError = onConstructError,
|
||||
)
|
||||
}
|
||||
}?.awaitAll()?.flatten()
|
||||
} else {
|
||||
getStakingTransaction(
|
||||
state = state,
|
||||
confirmationState = confirmationState,
|
||||
onConstructError = onConstructError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getConstructedTransactions(
|
||||
stakingTransactions: List<StakingTransaction>?,
|
||||
fee: Fee,
|
||||
onConstructError: (StakingError) -> Unit,
|
||||
) = coroutineScope {
|
||||
stakingTransactions?.filterNot { it.type == StakingTransactionType.APPROVAL }
|
||||
?.map { transaction ->
|
||||
async {
|
||||
getConstructedStakingTransactionUseCase(
|
||||
networkId = cryptoCurrencyStatus.currency.network.id.value,
|
||||
fee = fee,
|
||||
transactionId = transaction.id,
|
||||
).fold(
|
||||
ifRight = { (constructedTransaction, transactionData) ->
|
||||
FullTransactionData(
|
||||
stakeKitTransaction = constructedTransaction,
|
||||
tangemTransaction = transactionData,
|
||||
)
|
||||
},
|
||||
ifLeft = {
|
||||
onConstructError(it)
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
}?.awaitAll()?.filterNotNull()
|
||||
}
|
||||
|
||||
private suspend fun getStakingTransaction(
|
||||
state: StakingUiState,
|
||||
confirmationState: StakingStates.ConfirmationState.Data,
|
||||
action: PendingAction? = confirmationState.pendingAction,
|
||||
onConstructError: (StakingError) -> Unit,
|
||||
): List<StakingTransaction> {
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
val fee = (confirmationState.feeState as? FeeState.Content)?.fee
|
||||
?: error("No fee provided")
|
||||
val defaultAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
val amountState = state.amountState as? AmountState.Data
|
||||
?: error("No amount provided")
|
||||
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
val amount = getAmount(amountState, fee, confirmationState.reduceAmountBy)
|
||||
|
||||
return getStakingTransactionUseCase(
|
||||
userWalletId = userWallet.walletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
params = ActionParams(
|
||||
actionCommonType = state.actionType,
|
||||
integrationId = yield.id,
|
||||
amount = amount,
|
||||
address = defaultAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
token = yield.token,
|
||||
passthrough = action?.passthrough,
|
||||
type = action?.type,
|
||||
),
|
||||
).getOrElse {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(state.cryptoCurrencyName))
|
||||
onConstructError(it)
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendStakingTransaction(
|
||||
fullTransactionsData: List<FullTransactionData>,
|
||||
onSendSuccess: (txUrl: String) -> Unit,
|
||||
onSendError: (SendTransactionError?) -> Unit,
|
||||
) {
|
||||
sendMultipleTransactionUseCase(
|
||||
txsData = fullTransactionsData.map { it.tangemTransaction },
|
||||
userWallet = userWallet,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
onSendError(error)
|
||||
},
|
||||
ifRight = { transactionHashes ->
|
||||
submitHash(
|
||||
transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id },
|
||||
transactionHashes = transactionHashes,
|
||||
)
|
||||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = transactionHashes.last(),
|
||||
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||
).getOrElse { "" }
|
||||
|
||||
balanceUpdater.scheduleUpdates()
|
||||
onSendSuccess(txUrl)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun submitHash(transactionIds: List<String>, transactionHashes: List<String>) {
|
||||
transactionIds
|
||||
.zip(transactionHashes)
|
||||
.forEach { (transactionId, transactionHash) ->
|
||||
submitHashUseCase.submitHash(
|
||||
transactionId = transactionId,
|
||||
transactionHash = transactionHash,
|
||||
)
|
||||
.onLeft {
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvents.StakingError(stateController.value.cryptoCurrencyName),
|
||||
)
|
||||
saveUnsubmittedHashUseCase.invoke(
|
||||
transactionId = transactionId,
|
||||
transactionHash = transactionHash,
|
||||
)
|
||||
}.onRight {
|
||||
Timber.d("Successful hash submission")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal {
|
||||
val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value")
|
||||
val feeValue = fee.amount.value ?: error("No fee value")
|
||||
val isEnterAction = stateController.value.actionType == StakingActionCommonType.ENTER
|
||||
|
||||
return checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy.orZero(),
|
||||
)
|
||||
}
|
||||
|
||||
private data class FullTransactionData(
|
||||
val stakeKitTransaction: StakingTransaction,
|
||||
val tangemTransaction: TransactionData.Compiled,
|
||||
)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
userWallet: UserWallet,
|
||||
yield: Yield,
|
||||
isAmountSubtractAvailable: Boolean,
|
||||
): StakingTransactionSender
|
||||
}
|
||||
}
|
||||
|
|
@ -92,5 +92,6 @@ internal object ConfirmationStatePreviewData {
|
|||
pendingAction = null,
|
||||
isApprovalNeeded = false,
|
||||
reduceAmountBy = null,
|
||||
pendingActions = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
@ -15,7 +16,12 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun onBackClick() {}
|
||||
|
||||
override fun onNextClick(actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?) {}
|
||||
override fun onNextClick(
|
||||
actionTypeToOverwrite: StakingActionCommonType?,
|
||||
pendingAction: PendingAction?,
|
||||
pendingActions: ImmutableList<PendingAction>?,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onActionClick() {}
|
||||
|
||||
|
|
@ -55,7 +61,8 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun onActiveStake(activeStake: BalanceState) {}
|
||||
|
||||
override fun getFee(pendingAction: PendingAction?) {}
|
||||
override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?) {
|
||||
}
|
||||
|
||||
override fun onAmountReduceByClick(
|
||||
reduceAmountBy: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -91,7 +91,12 @@ internal class AddStakingNotificationsTransformer(
|
|||
prevState = prevState,
|
||||
feeError = feeError,
|
||||
sendingAmount = sendingAmount,
|
||||
onReload = { prevState.clickIntents.getFee(confirmationState.pendingAction) },
|
||||
onReload = {
|
||||
prevState.clickIntents.getFee(
|
||||
confirmationState.pendingAction,
|
||||
confirmationState.pendingActions,
|
||||
)
|
||||
},
|
||||
feeValue = feeValue,
|
||||
)
|
||||
// warnings
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.staking.impl.presentation.state.transformers
|
|||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class DismissBottomSheetStateTransformer : Transformer<StakingUiState> {
|
||||
internal object DismissBottomSheetStateTransformer : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(bottomSheetConfig = null)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStates
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal class SetActionToExecuteTransformer(
|
||||
private val actionTypeToOverwrite: StakingActionCommonType,
|
||||
private val pendingAction: PendingAction?,
|
||||
private val pendingActions: ImmutableList<PendingAction>?,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
return prevState.copy(
|
||||
actionType = actionTypeToOverwrite,
|
||||
confirmationState = confirmationState?.copy(
|
||||
pendingAction = pendingAction,
|
||||
pendingActions = pendingActions,
|
||||
) ?: StakingStates.ConfirmationState.Empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,14 @@ import com.tangem.features.staking.impl.presentation.state.StakingStates
|
|||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal class SetConfirmationStateAssentTransformer(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val fee: Fee,
|
||||
private val action: PendingAction?,
|
||||
private val actions: ImmutableList<PendingAction>?,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
|
|
@ -38,6 +40,7 @@ internal class SetConfirmationStateAssentTransformer(
|
|||
),
|
||||
validatorState = validatorState.copySealed(isClickable = true),
|
||||
pendingAction = action,
|
||||
pendingActions = actions,
|
||||
isPrimaryButtonEnabled = true,
|
||||
isApprovalNeeded = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,43 +1,26 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers
|
||||
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class SetConfirmationStateCompletedTransformer(
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val fee: Fee,
|
||||
private val txUrl: String,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
return prevState.copy(
|
||||
confirmationState = prevState.confirmationState.copyWrapped(fee),
|
||||
confirmationState = prevState.confirmationState.copyWrapped(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun StakingStates.ConfirmationState.copyWrapped(fee: Fee): StakingStates.ConfirmationState {
|
||||
private fun StakingStates.ConfirmationState.copyWrapped(): 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,
|
||||
rate = feeCryptoCurrencyStatus?.value?.fiatRate,
|
||||
isFeeConvertibleToFiat = isFeeConvertibleToFiat,
|
||||
appCurrency = appCurrencyProvider(),
|
||||
isFeeApproximate = false,
|
||||
),
|
||||
validatorState = validatorState.copySealed(
|
||||
isClickable = false,
|
||||
),
|
||||
validatorState = validatorState.copySealed(isClickable = false),
|
||||
footerText = TextReference.EMPTY,
|
||||
notifications = persistentListOf(),
|
||||
transactionDoneState = TransactionDoneState.Content(
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ internal class SetConfirmationStateLoadingTransformer(
|
|||
footerText = getFooter(prevState),
|
||||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingAction = possibleConfirmationState?.pendingAction,
|
||||
pendingActions = possibleConfirmationState?.pendingActions,
|
||||
isApprovalNeeded = false,
|
||||
reduceAmountBy = null,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ internal class SetInitialDataStateTransformer(
|
|||
footerText = TextReference.EMPTY,
|
||||
transactionDoneState = TransactionDoneState.Empty,
|
||||
pendingAction = null,
|
||||
pendingActions = null,
|
||||
isApprovalNeeded = isApprovalNeeded,
|
||||
reduceAmountBy = null,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ package com.tangem.features.staking.impl.presentation.state.utils
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.staking.model.stakekit.PendingAction
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (this) {
|
||||
|
|
@ -24,4 +27,10 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
|
|||
StakingActionType.UNSTAKE -> resourceReference(R.string.common_unstake)
|
||||
StakingActionType.UNKNOWN -> TextReference.EMPTY
|
||||
null -> TextReference.EMPTY
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -30,17 +30,16 @@ import java.math.BigDecimal
|
|||
|
||||
@Composable
|
||||
internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) {
|
||||
val backgroundColor = if (isTransactionSent) {
|
||||
TangemTheme.colors.background.action
|
||||
} else {
|
||||
TangemTheme.colors.button.disabled
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(
|
||||
if (isTransactionSent) {
|
||||
TangemTheme.colors.background.action
|
||||
} else {
|
||||
TangemTheme.colors.button.disabled
|
||||
},
|
||||
)
|
||||
.background(backgroundColor)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
|
|
@ -49,9 +48,7 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) {
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Box(modifier = Modifier.padding(top = TangemTheme.dimens.spacing8)) {
|
||||
when (feeState) {
|
||||
is FeeState.Content -> {
|
||||
val feeAmount = feeState.fee?.amount
|
||||
|
|
@ -82,6 +79,7 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) {
|
|||
SelectorRowItem(
|
||||
titleRes = R.string.common_fee_selector_option_market,
|
||||
iconRes = R.drawable.ic_bird_24,
|
||||
isSelected = true,
|
||||
paddingValues = PaddingValues(),
|
||||
showDivider = false,
|
||||
)
|
||||
|
|
@ -91,6 +89,7 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) {
|
|||
SelectorRowItem(
|
||||
titleRes = R.string.common_fee_selector_option_market,
|
||||
iconRes = R.drawable.ic_bird_24,
|
||||
isSelected = true,
|
||||
paddingValues = PaddingValues(),
|
||||
)
|
||||
FeeError(feeState)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.features.staking.impl.presentation.state.BalanceState
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
@ -15,7 +16,11 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onBackClick()
|
||||
|
||||
fun onNextClick(actionTypeToOverwrite: StakingActionCommonType? = null, pendingAction: PendingAction? = null)
|
||||
fun onNextClick(
|
||||
actionTypeToOverwrite: StakingActionCommonType? = null,
|
||||
pendingAction: PendingAction? = null,
|
||||
pendingActions: ImmutableList<PendingAction>? = null,
|
||||
)
|
||||
|
||||
fun onActionClick()
|
||||
|
||||
|
|
@ -27,7 +32,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun onInfoClick(infoType: InfoType)
|
||||
|
||||
fun getFee(pendingAction: PendingAction?)
|
||||
fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?)
|
||||
|
||||
override fun onAmountNext() = onNextClick(actionTypeToOverwrite = null)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ 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
|
||||
|
|
@ -26,24 +23,22 @@ import com.tangem.domain.feedback.GetCardInfoUseCase
|
|||
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.IsAnyTokenStakedUseCase
|
||||
import com.tangem.domain.staking.IsApproveNeededUseCase
|
||||
import com.tangem.domain.staking.model.StakingApproval
|
||||
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.StakingActionCommonType
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
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.StakingTransactionType
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
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
|
||||
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -55,6 +50,9 @@ import com.tangem.features.staking.impl.navigation.InnerStakingRouter
|
|||
import com.tangem.features.staking.impl.presentation.state.*
|
||||
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
|
||||
import com.tangem.features.staking.impl.presentation.state.events.StakingEventFactory
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingBalanceUpdater
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingFeeTransactionLoader
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingTransactionSender
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.*
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.amount.*
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetInProgressTransformer
|
||||
|
|
@ -62,14 +60,14 @@ import com.tangem.features.staking.impl.presentation.state.transformers.approval
|
|||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetConfirmationStateAssentApprovalTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.ShowApprovalBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
|
||||
import com.tangem.features.staking.impl.presentation.state.utils.isSolanaWithdraw
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.*
|
||||
import com.tangem.utils.extensions.isSingleItem
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
|
@ -86,22 +84,9 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getStakingTransactionUseCase: GetStakingTransactionUseCase,
|
||||
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,
|
||||
private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase,
|
||||
private val updateDelayedNetworkStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
|
||||
private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase,
|
||||
private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase,
|
||||
private val getAllowanceUseCase: GetAllowanceUseCase,
|
||||
private val getFeeUseCase: GetFeeUseCase,
|
||||
private val isApproveNeededUseCase: IsApproveNeededUseCase,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
private val feedbackManager: FeedbackManager,
|
||||
|
|
@ -112,8 +97,10 @@ internal class StakingViewModel @Inject constructor(
|
|||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
|
||||
private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase,
|
||||
private val stakingTransactionLoader: StakingTransactionSender.Factory,
|
||||
private val stakingFeeTransactionLoader: StakingFeeTransactionLoader.Factory,
|
||||
private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
|
||||
|
||||
|
|
@ -144,6 +131,31 @@ internal class StakingViewModel @Inject constructor(
|
|||
|
||||
private var isInitialInfoAnalyticSent: Boolean = false
|
||||
|
||||
private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) {
|
||||
stakingBalanceUpdater.create(
|
||||
cryptoCurrencyStatus,
|
||||
userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
private val feeLoader by lazy(LazyThreadSafetyMode.NONE) {
|
||||
stakingFeeTransactionLoader.create(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
userWallet = userWallet,
|
||||
yield = yield,
|
||||
stakingApproval = stakingApproval,
|
||||
)
|
||||
}
|
||||
|
||||
private val transactionSender by lazy(LazyThreadSafetyMode.NONE) {
|
||||
stakingTransactionLoader.create(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
userWallet = userWallet,
|
||||
yield = yield,
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable,
|
||||
)
|
||||
}
|
||||
|
||||
private val stakingEventFactory: StakingEventFactory
|
||||
get() = StakingEventFactory(
|
||||
stateController = stateController,
|
||||
|
|
@ -180,16 +192,13 @@ internal class StakingViewModel @Inject constructor(
|
|||
stakingStateRouter.onBackClick()
|
||||
}
|
||||
|
||||
override fun onNextClick(actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?) {
|
||||
override fun onNextClick(
|
||||
actionTypeToOverwrite: StakingActionCommonType?,
|
||||
pendingAction: PendingAction?,
|
||||
pendingActions: ImmutableList<PendingAction>?,
|
||||
) {
|
||||
if (actionTypeToOverwrite != null) {
|
||||
stateController.update { it.copy(actionType = actionTypeToOverwrite) }
|
||||
stateController.update {
|
||||
val confirmationState = it.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
it.copy(
|
||||
confirmationState = confirmationState?.copy(pendingAction = pendingAction)
|
||||
?: StakingStates.ConfirmationState.Empty(),
|
||||
)
|
||||
}
|
||||
stateController.update(SetActionToExecuteTransformer(actionTypeToOverwrite, pendingAction, pendingActions))
|
||||
}
|
||||
stakingStateRouter.onNextClick()
|
||||
when {
|
||||
|
|
@ -198,7 +207,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
onRefreshSwipe(isRefreshing = false)
|
||||
}
|
||||
isAssentState() -> {
|
||||
getFee(pendingAction)
|
||||
getFee(pendingAction, pendingActions)
|
||||
val amountState = value.amountState as? AmountState.Data
|
||||
if (amountState?.amountTextField?.isWarning == true) {
|
||||
stateController.update(
|
||||
|
|
@ -215,50 +224,40 @@ internal class StakingViewModel @Inject constructor(
|
|||
handleOnNextConfirmationClick()
|
||||
}
|
||||
|
||||
override fun getFee(pendingAction: PendingAction?) {
|
||||
override fun getFee(pendingAction: PendingAction?, pendingActions: ImmutableList<PendingAction>?) {
|
||||
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
|
||||
viewModelScope.launch {
|
||||
stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency))
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatus.value
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
|
||||
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided")
|
||||
val sourceAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
val validatorAddress = validatorState.chosenValidator.address
|
||||
|
||||
val approval = stakingApproval as? StakingApproval.Needed
|
||||
if (approval != null) {
|
||||
val allowance = getAllowanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
spenderAddress = approval.spenderAddress,
|
||||
).getOrElse { BigDecimal.ZERO }
|
||||
|
||||
if (allowance < amount) {
|
||||
getApproveFee(
|
||||
amount = amount,
|
||||
validatorAddress = validatorAddress,
|
||||
feeLoader.getFee(
|
||||
pendingAction = pendingAction,
|
||||
pendingActions = pendingActions,
|
||||
onStakingFee = { gasEstimate ->
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = gasEstimate,
|
||||
action = pendingAction,
|
||||
actions = pendingActions,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
updateNotifications()
|
||||
},
|
||||
onFeeError = { error ->
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stateController.update(AddStakingErrorTransformer())
|
||||
updateNotifications(error)
|
||||
},
|
||||
onApprovalFee = { fee ->
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
estimateGas(
|
||||
pendingAction = pendingAction,
|
||||
amount = amount,
|
||||
sourceAddress = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
)
|
||||
}
|
||||
updateNotifications()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -266,139 +265,32 @@ internal class StakingViewModel @Inject constructor(
|
|||
if (isAssentState()) {
|
||||
viewModelScope.launch {
|
||||
stateController.update(SetConfirmationStateInProgressTransformer())
|
||||
|
||||
val confirmationState =
|
||||
value.confirmationState as? StakingStates.ConfirmationState.Data ?: error("No confirmation state")
|
||||
val validatorState = confirmationState.validatorState as? ValidatorState.Content
|
||||
?: error("No validator provided")
|
||||
val amountState = value.amountState as? AmountState.Data ?: error("No amount provided")
|
||||
|
||||
val fee = (confirmationState.feeState as? FeeState.Content)?.fee ?: error("No fee provided")
|
||||
val defaultAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: error("No available address")
|
||||
val pendingAction = confirmationState.pendingAction
|
||||
|
||||
val stakingTransactions = getStakingTransactionUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
params = ActionParams(
|
||||
actionCommonType = value.actionType,
|
||||
integrationId = yield.id,
|
||||
amount = getAmount(amountState, fee, confirmationState.reduceAmountBy),
|
||||
address = defaultAddress,
|
||||
validatorAddress = validatorState.chosenValidator.address,
|
||||
token = yield.token,
|
||||
passthrough = pendingAction?.passthrough,
|
||||
type = pendingAction?.type,
|
||||
),
|
||||
).getOrElse {
|
||||
Timber.e(it.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stakingEventFactory.createStakingErrorAlert(it)
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val fullTransactionsData = stakingTransactions
|
||||
.filterNot { it.type == StakingTransactionType.APPROVAL }
|
||||
.map { transaction ->
|
||||
val (constructedTransaction, transactionData) = getConstructedStakingTransactionUseCase(
|
||||
networkId = cryptoCurrencyStatus.currency.network.id.value,
|
||||
fee = fee,
|
||||
transactionId = transaction.id,
|
||||
).getOrElse {
|
||||
Timber.e(it.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stakingEventFactory.createStakingErrorAlert(it)
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
return@launch
|
||||
}
|
||||
|
||||
FullTransactionData(
|
||||
stakeKitTransaction = constructedTransaction,
|
||||
tangemTransaction = transactionData,
|
||||
)
|
||||
}
|
||||
|
||||
transactionsInProgress.addAll(fullTransactionsData.map { it.stakeKitTransaction })
|
||||
|
||||
sendStakingTransaction(
|
||||
fullTransactionsData = fullTransactionsData,
|
||||
fee = fee,
|
||||
pendingAction = confirmationState.pendingAction,
|
||||
validator = validatorState.chosenValidator.name,
|
||||
transactionSender.constructAndSendTransactions(
|
||||
onConstructSuccess = { constructedTransactions ->
|
||||
transactionsInProgress.addAll(constructedTransactions)
|
||||
},
|
||||
onConstructError = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stakingEventFactory.createStakingErrorAlert(error)
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
},
|
||||
onSendSuccess = { txUrl ->
|
||||
stakingAnalyticSender.sendTransactionStakingAnalytics(stateController.value)
|
||||
transactionsInProgress.clear()
|
||||
stateController.update(SetConfirmationStateCompletedTransformer(txUrl))
|
||||
},
|
||||
onSendError = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stakingEventFactory.createSendTransactionErrorAlert(error)
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun estimateGas(
|
||||
pendingAction: PendingAction?,
|
||||
amount: BigDecimal,
|
||||
sourceAddress: String,
|
||||
validatorAddress: String,
|
||||
) {
|
||||
val gasEstimate = estimateGasUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
params = ActionParams(
|
||||
actionCommonType = value.actionType,
|
||||
integrationId = yield.id,
|
||||
amount = amount,
|
||||
address = sourceAddress,
|
||||
validatorAddress = validatorAddress,
|
||||
token = yield.token,
|
||||
passthrough = pendingAction?.passthrough,
|
||||
type = pendingAction?.type,
|
||||
),
|
||||
).getOrElse {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stateController.update(AddStakingErrorTransformer())
|
||||
updateNotifications(GetFeeError.UnknownError)
|
||||
return
|
||||
}
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = Fee.Common(
|
||||
Amount(
|
||||
currencySymbol = gasEstimate.token.symbol,
|
||||
value = gasEstimate.amount,
|
||||
decimals = gasEstimate.token.decimals,
|
||||
),
|
||||
),
|
||||
action = pendingAction,
|
||||
),
|
||||
)
|
||||
updateNotifications()
|
||||
}
|
||||
|
||||
private suspend fun getApproveFee(amount: BigDecimal, validatorAddress: String) {
|
||||
getFeeUseCase(
|
||||
amount = amount,
|
||||
destination = validatorAddress,
|
||||
userWallet = userWallet,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
).fold(
|
||||
ifRight = { fee ->
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
),
|
||||
)
|
||||
updateNotifications()
|
||||
},
|
||||
ifLeft = {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName))
|
||||
stateController.update(AddStakingErrorTransformer())
|
||||
updateNotifications(it)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPrevClick() {
|
||||
if (value.currentStep == StakingStep.Confirmation) {
|
||||
when ((value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState) {
|
||||
|
|
@ -422,10 +314,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
override fun onRefreshSwipe(isRefreshing: Boolean) {
|
||||
stateController.update(SetInitialLoadingStateTransformer(isRefreshing))
|
||||
viewModelScope.launch {
|
||||
val balancesUpdate = async { updateStakeBalance() }
|
||||
val networkUpdate = async { updateNetworkStatuses(0) }
|
||||
|
||||
awaitAll(balancesUpdate, networkUpdate)
|
||||
balanceUpdater.instantUpdate()
|
||||
}.saveIn(updateBalancesJobHolder)
|
||||
.invokeOnCompletion {
|
||||
stateController.update(SetInitialLoadingStateTransformer(false))
|
||||
|
|
@ -440,7 +329,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
override fun onInfoClick(infoType: InfoType) {
|
||||
stateController.update(
|
||||
ShowInfoBottomSheetStateTransformer(infoType) {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -500,11 +389,16 @@ internal class StakingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onActiveStake(activeStake: BalanceState) {
|
||||
if (activeStake.pendingActions.size > 1) {
|
||||
val isAllWithdrawActions = isSolanaWithdraw(
|
||||
cryptoCurrencyStatus.currency.network.id.value,
|
||||
activeStake.pendingActions,
|
||||
)
|
||||
val isMultiplePendingActions = activeStake.pendingActions.isSingleItem().not()
|
||||
if (isMultiplePendingActions && !isAllWithdrawActions) {
|
||||
stateController.update(
|
||||
ShowActionSelectorBottomSheetTransformer(
|
||||
pendingActions = activeStake.pendingActions,
|
||||
onActionSelect = {
|
||||
onActionSelect = { action ->
|
||||
stateController.update(ValidatorSelectChangeTransformer(activeStake.validator))
|
||||
stateController.update(
|
||||
AmountChangeStateTransformer(
|
||||
|
|
@ -513,19 +407,25 @@ internal class StakingViewModel @Inject constructor(
|
|||
yield,
|
||||
),
|
||||
)
|
||||
onNextClick(actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER, pendingAction = it)
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
},
|
||||
onDismiss = {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
onNextClick(
|
||||
actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER,
|
||||
pendingAction = action,
|
||||
)
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
onDismiss = { stateController.update(DismissBottomSheetStateTransformer) },
|
||||
),
|
||||
)
|
||||
} else {
|
||||
stateController.update(ActionTypeActiveStakeTransformer(cryptoCurrencyStatus, activeStake))
|
||||
stateController.update(ValidatorSelectChangeTransformer(activeStake.validator))
|
||||
stateController.update(AmountChangeStateTransformer(cryptoCurrencyStatus, activeStake.cryptoValue, yield))
|
||||
onNextClick(null, activeStake.pendingActions.firstOrNull())
|
||||
|
||||
onNextClick(
|
||||
actionTypeToOverwrite = null,
|
||||
pendingAction = activeStake.pendingActions.firstOrNull(),
|
||||
pendingActions = activeStake.pendingActions.takeIf { isAllWithdrawActions },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -545,7 +445,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
) {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -554,7 +454,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
stateController.update(
|
||||
SetApprovalBottomSheetInProgressTransformer {
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -614,7 +514,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
ifRight = {
|
||||
stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency)
|
||||
stateController.update(SetApprovalInProgressTransformer)
|
||||
stateController.update(DismissBottomSheetStateTransformer())
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
awaitForAllowance(confirmationState.pendingAction)
|
||||
},
|
||||
)
|
||||
|
|
@ -720,7 +620,7 @@ internal class StakingViewModel @Inject constructor(
|
|||
val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
?: error("No amount provided")
|
||||
if (allowance >= amount) {
|
||||
getFee(pendingAction)
|
||||
getFee(pendingAction = pendingAction, pendingActions = null)
|
||||
allowanceTaskScheduler.cancelTask()
|
||||
}
|
||||
},
|
||||
|
|
@ -884,146 +784,6 @@ internal class StakingViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private suspend fun sendStakingTransaction(
|
||||
fullTransactionsData: List<FullTransactionData>,
|
||||
fee: Fee,
|
||||
pendingAction: PendingAction?,
|
||||
validator: String,
|
||||
) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val action = when (value.actionType) {
|
||||
StakingActionCommonType.ENTER -> StakingActionType.STAKE
|
||||
StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE
|
||||
StakingActionCommonType.PENDING_REWARDS,
|
||||
StakingActionCommonType.PENDING_OTHER,
|
||||
-> pendingAction?.type
|
||||
}
|
||||
action?.let {
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvents.ButtonAction(
|
||||
action = it.name,
|
||||
token = value.cryptoCurrencyName,
|
||||
validator = validator,
|
||||
),
|
||||
)
|
||||
}
|
||||
sendMultipleTransactionUseCase(
|
||||
txsData = fullTransactionsData.map { it.tangemTransaction },
|
||||
userWallet = userWallet,
|
||||
network = cryptoCurrency.network,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
Timber.e(error.toString())
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
action = pendingAction,
|
||||
),
|
||||
)
|
||||
stakingEventFactory.createSendTransactionErrorAlert(error)
|
||||
stateController.update(SetConfirmationStateResetAssentTransformer)
|
||||
},
|
||||
ifRight = { transactionHashes ->
|
||||
stakingAnalyticSender.sendTransactionStakingAnalytics(cryptoCurrency, validator)
|
||||
transactionsInProgress.clear()
|
||||
submitHash(
|
||||
transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id },
|
||||
transactionHashes = transactionHashes,
|
||||
)
|
||||
scheduleUpdates()
|
||||
val txUrl = getExplorerTransactionUrlUseCase(
|
||||
txHash = transactionHashes.last(),
|
||||
networkId = cryptoCurrency.network.id,
|
||||
).getOrElse { "" }
|
||||
stateController.update(
|
||||
SetConfirmationStateCompletedTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = fee,
|
||||
txUrl = txUrl,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun submitHash(transactionIds: List<String>, transactionHashes: List<String>) {
|
||||
transactionIds
|
||||
.zip(transactionHashes)
|
||||
.forEach { (transactionId, transactionHash) ->
|
||||
submitHashUseCase.submitHash(
|
||||
transactionId = transactionId,
|
||||
transactionHash = transactionHash,
|
||||
)
|
||||
.onLeft {
|
||||
analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
|
||||
saveUnsubmittedHashUseCase.invoke(
|
||||
transactionId = transactionId,
|
||||
transactionHash = transactionHash,
|
||||
)
|
||||
}.onRight {
|
||||
Timber.d("Successful hash submission")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleUpdates() {
|
||||
coroutineScope.launch {
|
||||
listOf(
|
||||
// we should update network to find pending tx after 1 sec
|
||||
async {
|
||||
fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network))
|
||||
},
|
||||
// we should update tx history and network for new balances
|
||||
async {
|
||||
updateStakeBalance()
|
||||
},
|
||||
async {
|
||||
updateTxHistory()
|
||||
},
|
||||
async {
|
||||
updateNetworkStatuses()
|
||||
},
|
||||
).awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateNetworkStatuses(delay: Long = BALANCE_UPDATE_DELAY) {
|
||||
updateDelayedNetworkStatusUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
delayMillis = delay,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateStakeBalance() {
|
||||
stakingYieldBalanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateTxHistory() {
|
||||
delay(BALANCE_UPDATE_DELAY)
|
||||
val txHistoryItemsCountEither = getTxHistoryItemsCountUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
)
|
||||
|
||||
txHistoryItemsCountEither.onRight {
|
||||
getTxHistoryItemsUseCase(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrencyStatus.currency,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAssentState(): Boolean {
|
||||
return value.currentStep == StakingStep.Confirmation &&
|
||||
(value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState ==
|
||||
|
|
@ -1039,28 +799,8 @@ internal class StakingViewModel @Inject constructor(
|
|||
.getOrElse { false }
|
||||
}
|
||||
|
||||
private fun getAmount(amountState: AmountState.Data, fee: Fee, reduceAmountBy: BigDecimal?): BigDecimal {
|
||||
val amountValue = amountState.amountTextField.cryptoAmount.value ?: error("No amount value")
|
||||
val feeValue = fee.amount.value ?: error("No fee value")
|
||||
val isEnterAction = stateController.value.actionType == StakingActionCommonType.ENTER
|
||||
|
||||
return checkAndCalculateSubtractedAmount(
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailable && isEnterAction,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
amountValue = amountValue,
|
||||
feeValue = feeValue,
|
||||
reduceAmountBy = reduceAmountBy.orZero(),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
const val BALANCE_UPDATE_DELAY = 11_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,7 @@ markdownComposeView = "0.5.4"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-769"
|
||||
tangemBlockchainSdk = "develop-770"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-381"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue