Updated on 2026-08-14
This commit is contained in:
commit
06a915a0f4
2186 changed files with 101417 additions and 36593 deletions
|
|
@ -10,16 +10,14 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
/** Kotlin */
|
||||
api(deps.kotlin.coroutines)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
api(projects.core.decompose)
|
||||
api(projects.core.ui)
|
||||
|
||||
/** Domain models */
|
||||
api(projects.domain.models)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
api(projects.domain.staking)
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.features.staking.impl.presentation.model
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.staking.model.StakingTarget
|
||||
|
|
@ -47,10 +46,6 @@ internal interface StakingClickIntents : AmountScreenClickIntents {
|
|||
|
||||
fun showApprovalBottomSheet()
|
||||
|
||||
fun onApproveTypeChange(approveType: ApproveType)
|
||||
|
||||
fun onApprovalClick()
|
||||
|
||||
fun onAmountReduceByClick(
|
||||
reduceAmountBy: BigDecimal,
|
||||
reduceAmountByDiff: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.arkivanov.decompose.router.slot.activate
|
|||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.TangemBlogUrlBuilder
|
||||
import com.tangem.common.getValidatorsCount
|
||||
import com.tangem.common.routing.AppRouter
|
||||
|
|
@ -16,8 +15,6 @@ import com.tangem.common.routing.deeplink.toContextualRoute
|
|||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.api.ParamsInterceptorHolder
|
||||
|
|
@ -63,14 +60,15 @@ 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.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
|
||||
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.utils.convertToSdkAmount
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.approval.api.GiveApprovalComponent
|
||||
import com.tangem.features.approval.api.GiveApprovalFeatureToggles
|
||||
import com.tangem.features.marketing.api.MarketingBannerRequest
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.staking.impl.R
|
||||
|
|
@ -81,14 +79,11 @@ 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.StakingAlertUM
|
||||
import com.tangem.features.staking.impl.presentation.state.events.StakingEventFactory
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.GetEffectiveStakingFee
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingBalanceUpdater
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingFeeLoader
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingOperationsFactory
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.StakingTransactionSender
|
||||
import com.tangem.features.staking.impl.presentation.state.helpers.*
|
||||
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.*
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetConfirmationStateAssentApprovalTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.confirmation.SetUpdatedAllowanceTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.notifications.AddStakingNotificationsTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.notifications.DismissStakingNotificationsStateTransformer
|
||||
|
|
@ -130,7 +125,6 @@ internal class StakingModel @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val sendTransactionUseCase: SendTransactionUseCase,
|
||||
private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase,
|
||||
private val getAllowanceUseCase: GetAllowanceUseCase,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
|
||||
|
|
@ -161,8 +155,6 @@ internal class StakingModel @Inject constructor(
|
|||
private val coroutineScope: AppCoroutineScope,
|
||||
private val innerRouter: InnerStakingRouter,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val giveApprovalFeatureToggles: GiveApprovalFeatureToggles,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val appRouter: AppRouter,
|
||||
) : Model(), StakingClickIntents {
|
||||
|
||||
|
|
@ -326,7 +318,6 @@ internal class StakingModel @Inject constructor(
|
|||
private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()
|
||||
|
||||
private val actionsJobHolder: JobHolder = JobHolder()
|
||||
private val approvalJobHolder: JobHolder = JobHolder()
|
||||
private val feeJobHolder: JobHolder = JobHolder()
|
||||
private val sendTransactionJobHolder = JobHolder()
|
||||
private val stepChangesJobHolder = JobHolder()
|
||||
|
|
@ -350,7 +341,6 @@ internal class StakingModel @Inject constructor(
|
|||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
paramsInterceptorHolder.removeParamsInterceptor(StakingParamsInterceptor.ID)
|
||||
approvalJobHolder.cancel()
|
||||
feeJobHolder.cancel()
|
||||
sendTransactionJobHolder.cancel()
|
||||
stepChangesJobHolder.cancel()
|
||||
|
|
@ -407,8 +397,6 @@ internal class StakingModel @Inject constructor(
|
|||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
actionType = uiState.value.actionType,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles
|
||||
.isSolanaUnstakeValidationEnabled(),
|
||||
)
|
||||
|
||||
addAll(
|
||||
|
|
@ -671,7 +659,6 @@ internal class StakingModel @Inject constructor(
|
|||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
value = value,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
|
||||
),
|
||||
)
|
||||
checkSumLimitExceeded()
|
||||
|
|
@ -713,7 +700,6 @@ internal class StakingModel @Inject constructor(
|
|||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
actionType = uiState.value.actionType,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
|
||||
),
|
||||
)
|
||||
checkSumLimitExceeded()
|
||||
|
|
@ -849,112 +835,7 @@ internal class StakingModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun showApprovalBottomSheet() {
|
||||
if (giveApprovalFeatureToggles.isGaslessApprovalEnabled) {
|
||||
approvalSlotNavigation.activate(Unit)
|
||||
} else {
|
||||
stateController.update(
|
||||
ShowApprovalBottomSheetTransformer(
|
||||
userWallet = userWallet,
|
||||
appCurrencyProvider = Provider { currentAppCurrency.value },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
) {
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onApproveTypeChange(approveType: ApproveType) {
|
||||
stateController.update(SetApprovalBottomSheetTypeChangeTransformer(approveType))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override fun onApprovalClick() {
|
||||
modelScope.launch {
|
||||
stateController.update(
|
||||
SetApprovalBottomSheetInProgressTransformer {
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
},
|
||||
)
|
||||
|
||||
val tokenCryptoCurrency =
|
||||
cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: error("No token currency")
|
||||
val amountValue = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
|
||||
val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
|
||||
?: error("No confirmation state")
|
||||
val fee = (confirmationState.feeState as? FeeState.Content)?.fee ?: error("No fee provided")
|
||||
val approval = stakingApproval as? StakingApproval.Needed ?: error("No staking approve spender address")
|
||||
|
||||
val approvalBottomSheetConfig = value.bottomSheetConfig?.content as? GiveTxPermissionBottomSheetConfig
|
||||
val isLimitedApproval = approvalBottomSheetConfig?.data?.approveType == ApproveType.LIMITED
|
||||
|
||||
val approvalTransaction = createApprovalTransactionUseCase(
|
||||
amount = amountValue.takeIf { isLimitedApproval },
|
||||
contractAddress = tokenCryptoCurrency.contractAddress,
|
||||
spenderAddress = approval.spenderAddress,
|
||||
fee = fee,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
userWalletId = userWalletId,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
TangemLogger.e(error.toString())
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvent.TransactionError(
|
||||
errorCode = "CreateApprovalTxError",
|
||||
),
|
||||
)
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
appCurrencyProvider = Provider { currentAppCurrency.value },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = TransactionFee.Single(fee),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
stakingEventFactory.createGenericErrorAlert(error.message ?: error.toString())
|
||||
stateController.update(
|
||||
SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus),
|
||||
)
|
||||
return@launch
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
|
||||
sendTransactionUseCase(
|
||||
txData = approvalTransaction,
|
||||
userWallet = userWallet,
|
||||
network = tokenCryptoCurrency.network,
|
||||
).fold(
|
||||
ifLeft = { error ->
|
||||
TangemLogger.e(error.toString())
|
||||
analyticsEventHandler.send(
|
||||
StakingAnalyticsEvent.TransactionError(
|
||||
errorCode = error.getAnalyticsDescription(),
|
||||
),
|
||||
)
|
||||
stateController.update(
|
||||
SetConfirmationStateAssentApprovalTransformer(
|
||||
appCurrencyProvider = Provider { currentAppCurrency.value },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = TransactionFee.Single(fee),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
),
|
||||
)
|
||||
stakingEventFactory.createSendTransactionErrorAlert(error)
|
||||
stateController.update(
|
||||
SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus),
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency)
|
||||
stateController.update(SetApprovalInProgressTransformer)
|
||||
stateController.update(DismissBottomSheetStateTransformer)
|
||||
awaitForAllowance()
|
||||
},
|
||||
)
|
||||
}.saveIn(approvalJobHolder)
|
||||
approvalSlotNavigation.activate(Unit)
|
||||
}
|
||||
|
||||
private fun updateNotifications(feeError: GetFeeError? = null, stakingError: StakingError? = null) {
|
||||
|
|
@ -1501,7 +1382,6 @@ internal class StakingModel @Inject constructor(
|
|||
value = amountValue,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
integration = integration,
|
||||
isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.stub
|
||||
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.staking.model.StakingTarget
|
||||
|
|
@ -46,10 +45,6 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun showApprovalBottomSheet() {}
|
||||
|
||||
override fun onApproveTypeChange(approveType: ApproveType) {}
|
||||
|
||||
override fun onApprovalClick() {}
|
||||
|
||||
override fun onExploreClick() {}
|
||||
|
||||
override fun onShareClick() {}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ internal class AmountChangeStateTransformer(
|
|||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||
private val value: String,
|
||||
private val integration: StakingIntegration,
|
||||
private val isSolanaUnstakeValidationEnabled: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||
|
|
@ -44,7 +43,6 @@ internal class AmountChangeStateTransformer(
|
|||
maxAmount = maxEnterAmount,
|
||||
integration = integration,
|
||||
actionType = prevState.actionType,
|
||||
isSolanaUnstakeValidationEnabled = isSolanaUnstakeValidationEnabled,
|
||||
).transform(updatedAmountState),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ internal class AmountMaxValueStateTransformer(
|
|||
private val minimumTransactionAmount: EnterAmountBoundary?,
|
||||
private val actionType: StakingActionCommonType,
|
||||
private val integration: StakingIntegration,
|
||||
private val isSolanaUnstakeValidationEnabled: Boolean,
|
||||
) : Transformer<StakingUiState> {
|
||||
|
||||
private val maxEnterAmountConverter = MaxEnterAmountConverter()
|
||||
|
|
@ -41,7 +40,6 @@ internal class AmountMaxValueStateTransformer(
|
|||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
integration = integration,
|
||||
actionType = prevState.actionType,
|
||||
isSolanaUnstakeValidationEnabled = isSolanaUnstakeValidationEnabled,
|
||||
).transform(updatedAmountState),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ internal class AmountRequirementStateTransformer(
|
|||
private val maxAmount: EnterAmountBoundary,
|
||||
private val integration: StakingIntegration,
|
||||
private val actionType: StakingActionCommonType,
|
||||
private val isSolanaUnstakeValidationEnabled: Boolean = false,
|
||||
) : Transformer<AmountState> {
|
||||
override fun transform(prevState: AmountState): AmountState {
|
||||
return if (prevState is AmountState.Data) {
|
||||
|
|
@ -105,9 +104,7 @@ internal class AmountRequirementStateTransformer(
|
|||
)
|
||||
}
|
||||
is StakingActionCommonType.Exit -> {
|
||||
if (isSolanaUnstakeValidationEnabled &&
|
||||
isSolana(cryptoCurrencyStatus.currency.network.rawId)
|
||||
) {
|
||||
if (isSolana(cryptoCurrencyStatus.currency.network.rawId)) {
|
||||
getSolanaUnstakeError(amount = amountDecimal, staked = maxAmount.amount)
|
||||
} else {
|
||||
integration.exitArgs?.amountRequirement?.getError(
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetApprovalBottomSheetInProgressTransformer(
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val approvalBottomSheetConfig = prevState.bottomSheetConfig?.content as? GiveTxPermissionBottomSheetConfig
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevState.bottomSheetConfig?.copy(
|
||||
onDismissRequest = onDismiss,
|
||||
isShown = true,
|
||||
content = approvalBottomSheetConfig?.let { config ->
|
||||
config.copy(
|
||||
data = config.data.copy(
|
||||
approveButton = config.data.approveButton.copy(
|
||||
isEnabled = false,
|
||||
isLoading = true,
|
||||
),
|
||||
cancelButton = config.data.cancelButton.copy(
|
||||
enabled = false,
|
||||
),
|
||||
),
|
||||
onCancel = onDismiss,
|
||||
)
|
||||
} as? TangemBottomSheetConfigContent ?: return prevState,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingUiState
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
internal class SetApprovalBottomSheetTypeChangeTransformer(
|
||||
private val approveType: ApproveType,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val approvalBottomSheetConfig = prevState.bottomSheetConfig?.content as? GiveTxPermissionBottomSheetConfig
|
||||
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = prevState.bottomSheetConfig?.copy(
|
||||
content = approvalBottomSheetConfig?.copy(
|
||||
data = approvalBottomSheetConfig.data.copy(approveType = approveType),
|
||||
) as? TangemBottomSheetConfigContent ?: return prevState,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package com.tangem.features.staking.impl.presentation.state.transformers.approval
|
||||
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.features.staking.impl.R
|
||||
import com.tangem.features.staking.impl.presentation.state.FeeState
|
||||
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
|
||||
|
||||
internal class ShowApprovalBottomSheetTransformer(
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus?,
|
||||
private val onDismiss: () -> Unit,
|
||||
) : Transformer<StakingUiState> {
|
||||
override fun transform(prevState: StakingUiState): StakingUiState {
|
||||
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
|
||||
val cryptoCurrencyValue = cryptoCurrencyStatusProvider().value
|
||||
|
||||
val amountState = prevState.amountState as? AmountState.Data ?: return prevState
|
||||
val confirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
|
||||
val validatorState = prevState.validatorState as? StakingStates.ValidatorState.Data ?: return prevState
|
||||
val feeState = confirmationState.feeState as? FeeState.Content ?: return prevState
|
||||
val fee = feeState.fee ?: return prevState
|
||||
|
||||
val walletAddress = cryptoCurrencyValue.networkAddress?.defaultAddress?.value.orEmpty()
|
||||
val targetAddress = validatorState.chosenTarget.address
|
||||
val feeCryptoValue = fee.amount.value.format {
|
||||
crypto(fee.amount.currencySymbol, fee.amount.decimals)
|
||||
}
|
||||
val feeFiatValue = feeCryptoCurrencyStatus?.value?.fiatRate?.multiply(fee.amount.value).format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrencyProvider().code,
|
||||
fiatCurrencySymbol = appCurrencyProvider().symbol,
|
||||
)
|
||||
}
|
||||
return prevState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = GiveTxPermissionBottomSheetConfig(
|
||||
data = GiveTxPermissionState.ReadyForRequest(
|
||||
currency = cryptoCurrency.symbol,
|
||||
amount = amountState.amountTextField.value,
|
||||
approveType = ApproveType.UNLIMITED,
|
||||
walletAddress = walletAddress,
|
||||
spenderAddress = targetAddress,
|
||||
fee = resourceReference(
|
||||
R.string.common_crypto_fiat_format,
|
||||
wrappedList(feeCryptoValue, feeFiatValue),
|
||||
),
|
||||
approveButton = ApprovePermissionButton(
|
||||
isEnabled = true,
|
||||
isLoading = false,
|
||||
onClick = prevState.clickIntents::onApprovalClick,
|
||||
),
|
||||
cancelButton = CancelPermissionButton(
|
||||
enabled = true,
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.give_permission_staking_subtitle,
|
||||
formatArgs = wrappedList(cryptoCurrency.symbol),
|
||||
),
|
||||
dialogText = resourceReference(R.string.give_permission_staking_footer),
|
||||
footerText = resourceReference(R.string.staking_give_permission_fee_footer),
|
||||
onChangeApproveType = prevState.clickIntents::onApproveTypeChange,
|
||||
onOpenLearnMoreAboutApproveClick = {},
|
||||
isResetApproval = false,
|
||||
),
|
||||
walletInteractionIcon = walletInterationIcon(userWallet),
|
||||
onCancel = onDismiss,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,8 +12,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import com.tangem.common.ui.amountScreen.AmountScreenContent
|
||||
import com.tangem.common.ui.bottomsheet.permission.GiveTxPermissionBottomSheet
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionBottomSheetConfig
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsBlock
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButtonsState
|
||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
|
||||
|
|
@ -78,7 +76,6 @@ fun StakingBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
|
|||
if (bottomSheetConfig == null) return
|
||||
when (bottomSheetConfig.content) {
|
||||
is StakingInfoBottomSheetConfig -> StakingInfoBottomSheet(bottomSheetConfig)
|
||||
is GiveTxPermissionBottomSheetConfig -> GiveTxPermissionBottomSheet(bottomSheetConfig)
|
||||
is StakingActionSelectionBottomSheetConfig -> StakingActionSelectorBottomSheet(bottomSheetConfig)
|
||||
is TonInitializeAccountBottomSheetConfig -> TonInitializeAccountBottomSheet(bottomSheetConfig)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,11 +29,9 @@ import com.tangem.domain.staking.*
|
|||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.transaction.usecase.*
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.approval.api.GiveApprovalFeatureToggles
|
||||
import com.tangem.features.staking.api.StakingComponent
|
||||
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
|
||||
import com.tangem.features.staking.impl.presentation.state.StakingStateController
|
||||
|
|
@ -60,11 +58,12 @@ internal abstract class StakingModelTestBase {
|
|||
protected val testUserWalletId = UserWalletId("1234567890ABCDEF")
|
||||
protected val testCryptoCurrency: CryptoCurrency = mockk(relaxed = true)
|
||||
protected open val testIntegrationId: StakingIntegrationID = StakingIntegrationID.StakeKit.Coin.Solana
|
||||
private val testParams get() = StakingComponent.Params(
|
||||
userWalletId = testUserWalletId,
|
||||
cryptoCurrency = testCryptoCurrency,
|
||||
integrationId = testIntegrationId,
|
||||
)
|
||||
private val testParams
|
||||
get() = StakingComponent.Params(
|
||||
userWalletId = testUserWalletId,
|
||||
cryptoCurrency = testCryptoCurrency,
|
||||
integrationId = testIntegrationId,
|
||||
)
|
||||
protected val testYield: Yield = mockk(relaxed = true)
|
||||
protected val testUserWallet: UserWallet = mockk(relaxed = true)
|
||||
protected val initialUiState: StakingUiState = mockk(relaxed = true) {
|
||||
|
|
@ -87,7 +86,6 @@ internal abstract class StakingModelTestBase {
|
|||
protected val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase = mockk()
|
||||
protected val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase = mockk()
|
||||
protected val sendTransactionUseCase: SendTransactionUseCase = mockk()
|
||||
protected val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase = mockk()
|
||||
protected val getAllowanceUseCase: GetAllowanceUseCase = mockk()
|
||||
protected val vibratorHapticManager: VibratorHapticManager = mockk()
|
||||
protected val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase = mockk()
|
||||
|
|
@ -115,10 +113,6 @@ internal abstract class StakingModelTestBase {
|
|||
private val coroutineScope: AppCoroutineScope = mockk()
|
||||
protected val innerRouter: InnerStakingRouter = mockk()
|
||||
protected val messageSender: UiMessageSender = mockk()
|
||||
protected val giveApprovalFeatureToggles: GiveApprovalFeatureToggles = mockk()
|
||||
protected val stakingFeatureToggles: StakingFeatureToggles = mockk {
|
||||
every { isSolanaUnstakeValidationEnabled() } returns false
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
|
|
@ -180,7 +174,6 @@ internal abstract class StakingModelTestBase {
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
sendTransactionUseCase = sendTransactionUseCase,
|
||||
createApprovalTransactionUseCase = createApprovalTransactionUseCase,
|
||||
getAllowanceUseCase = getAllowanceUseCase,
|
||||
vibratorHapticManager = vibratorHapticManager,
|
||||
getWalletMetaInfoUseCase = getWalletMetaInfoUseCase,
|
||||
|
|
@ -211,8 +204,6 @@ internal abstract class StakingModelTestBase {
|
|||
coroutineScope = coroutineScope,
|
||||
innerRouter = innerRouter,
|
||||
messageSender = messageSender,
|
||||
giveApprovalFeatureToggles = giveApprovalFeatureToggles,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,6 @@ import com.tangem.features.staking.impl.presentation.state.helpers.StakingTransa
|
|||
import com.tangem.features.staking.impl.presentation.state.transformers.SetConfirmationStateInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.SetConfirmationStateLoadingTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.SetConfirmationStateResetAssentTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetInProgressTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalBottomSheetTypeChangeTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.approval.ShowApprovalBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.CompleteInitializeBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeErrorToTonInitializeBottomSheetTransformer
|
||||
import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeToTonInitializeBottomSheetTransformer
|
||||
|
|
@ -305,168 +302,6 @@ internal class StakingModelTransactionTest : StakingModelTestBase() {
|
|||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN gasless approval enabled WHEN showApprovalBottomSheet THEN approvalSlotNavigation activated`() =
|
||||
runTest {
|
||||
every { giveApprovalFeatureToggles.isGaslessApprovalEnabled } returns true
|
||||
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
||||
model.showApprovalBottomSheet()
|
||||
|
||||
verify(exactly = 0) {
|
||||
stateController.update(
|
||||
transformer = match<Transformer<StakingUiState>> { it is ShowApprovalBottomSheetTransformer }
|
||||
)
|
||||
}
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN gasless disabled WHEN showApprovalBottomSheet THEN ShowApprovalBottomSheetTransformer applied`() =
|
||||
runTest {
|
||||
every { giveApprovalFeatureToggles.isGaslessApprovalEnabled } returns false
|
||||
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
||||
model.showApprovalBottomSheet()
|
||||
|
||||
verify {
|
||||
stateController.update(
|
||||
transformer = match<Transformer<StakingUiState>> { it is ShowApprovalBottomSheetTransformer }
|
||||
)
|
||||
}
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN onApproveTypeChange THEN SetApprovalBottomSheetTypeChangeTransformer applied`() = runTest {
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
||||
model.onApproveTypeChange(ApproveType.LIMITED)
|
||||
|
||||
verify {
|
||||
stateController.update(
|
||||
transformer = match<Transformer<StakingUiState>> { it is SetApprovalBottomSheetTypeChangeTransformer },
|
||||
)
|
||||
}
|
||||
|
||||
model.onDestroy()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN approval needed WHEN onApprovalClick THEN in progress set and createApprovalTransaction called`() =
|
||||
runTest {
|
||||
val spenderAddress = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908"
|
||||
val expectedNetwork = mockk<Network> {
|
||||
every { name } returns "KEK"
|
||||
}
|
||||
val testToken: CryptoCurrency.Token = mockk(relaxed = true) {
|
||||
every { network } returns expectedNetwork
|
||||
}
|
||||
val testCryptoCurrencyStatus: CryptoCurrencyStatus = mockk(relaxed = true) {
|
||||
every { currency } returns testToken
|
||||
}
|
||||
val testAccountCurrencyStatus = mockk<AccountCryptoCurrencyStatus> {
|
||||
every { component1() } returns mockk(relaxed = true)
|
||||
every { component2() } returns testCryptoCurrencyStatus
|
||||
}
|
||||
every {
|
||||
getAccountCurrencyStatusUseCase(testUserWalletId, testCryptoCurrency)
|
||||
} returns flowOf(testAccountCurrencyStatus)
|
||||
coEvery {
|
||||
getFeePaidCryptoCurrencyStatusSyncUseCase(testUserWalletId, testCryptoCurrencyStatus)
|
||||
} returns Either.Right(mockk(relaxed = true))
|
||||
coEvery {
|
||||
getMinimumTransactionAmountSyncUseCase(testUserWalletId, testCryptoCurrencyStatus)
|
||||
} returns Either.Left(mockk())
|
||||
|
||||
// Setup stakingApproval = Needed
|
||||
mockkObject(StakingIntegrationID.Companion)
|
||||
every {
|
||||
StakingIntegrationID.create(any())
|
||||
} returns mockk {
|
||||
every { approval } returns StakingApproval.Needed(spenderAddress)
|
||||
}
|
||||
coEvery {
|
||||
getAllowanceUseCase(testUserWalletId, any(), spenderAddress)
|
||||
} returns Either.Right(BigDecimal.TEN)
|
||||
|
||||
every {
|
||||
stakingOperationsFactory.createFeeLoader(
|
||||
cryptoCurrencyStatus = any(),
|
||||
userWallet = any(),
|
||||
integration = any()
|
||||
)
|
||||
} returns mockk<StakingFeeLoader> {
|
||||
coEvery {
|
||||
getFee(
|
||||
onStakingFee = any(),
|
||||
onStakingFeeError = any(),
|
||||
onApprovalFee = any(),
|
||||
onFeeError = any()
|
||||
)
|
||||
} just Runs
|
||||
}
|
||||
val expectedApprovalTx = Either.Right<TransactionData.Uncompiled>(mockk(relaxed = true))
|
||||
coEvery {
|
||||
createApprovalTransactionUseCase.invoke(
|
||||
cryptoCurrencyStatus = any(),
|
||||
userWalletId = any(),
|
||||
amount = any(),
|
||||
fee = any(),
|
||||
contractAddress = any(),
|
||||
spenderAddress = any(),
|
||||
)
|
||||
} returns expectedApprovalTx
|
||||
coEvery {
|
||||
sendTransactionUseCase(any(), any(), any())
|
||||
} returns Either.Right("txHash")
|
||||
every { vibratorHapticManager.performOneTime(any()) } just Runs
|
||||
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Now override stateController.value with confirmation state after cryptoCurrencyStatus is initialized
|
||||
val testFee: Fee.Common = mockk(relaxed = true)
|
||||
val confirmationState = mockk<StakingStates.ConfirmationState.Data>(relaxed = true) {
|
||||
every { feeState } returns mockk<FeeState.Content>(relaxed = true) {
|
||||
every { fee } returns testFee
|
||||
}
|
||||
}
|
||||
val uiState = mockk<StakingUiState>(relaxed = true) {
|
||||
every { this@mockk.confirmationState } returns confirmationState
|
||||
every { bottomSheetConfig } returns null
|
||||
}
|
||||
every { stateController.value } returns uiState
|
||||
|
||||
model.onApprovalClick()
|
||||
advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
stateController.update(
|
||||
transformer = match<Transformer<StakingUiState>> {
|
||||
it is SetApprovalBottomSheetInProgressTransformer
|
||||
},
|
||||
)
|
||||
}
|
||||
coVerify {
|
||||
sendTransactionUseCase(
|
||||
txData = expectedApprovalTx.value,
|
||||
userWallet = testUserWallet,
|
||||
network = expectedNetwork,
|
||||
)
|
||||
}
|
||||
|
||||
model.onDestroy()
|
||||
unmockkObject(StakingIntegrationID.Companion)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN approval needed AND amountState data WHEN getApprovalParams THEN returns non-null params`() = runTest {
|
||||
val spenderAddress = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908"
|
||||
|
|
|
|||
|
|
@ -314,6 +314,7 @@ internal class StakingModelValidatorTest : StakingModelTestBase() {
|
|||
advanceUntilIdle()
|
||||
|
||||
model.onActiveStake(activeStake)
|
||||
advanceUntilIdle()
|
||||
|
||||
verify {
|
||||
stateController.update(
|
||||
|
|
|
|||
|
|
@ -85,13 +85,11 @@ internal class AmountRequirementStateTransformerTest {
|
|||
staked: BigDecimal,
|
||||
exitMin: BigDecimal?,
|
||||
enterMin: BigDecimal? = null,
|
||||
enabled: Boolean = true,
|
||||
) = AmountRequirementStateTransformer(
|
||||
cryptoCurrencyStatus = solanaCryptoStatus(),
|
||||
maxAmount = EnterAmountBoundary(amount = staked, fiatAmount = null, fiatRate = null),
|
||||
integration = solanaExitIntegration(exitMin = exitMin, enterMin = enterMin),
|
||||
actionType = StakingActionCommonType.Exit(partiallyUnstakeDisabled = false),
|
||||
isSolanaUnstakeValidationEnabled = enabled,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
@ -285,26 +283,12 @@ internal class AmountRequirementStateTransformerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN Solana validation disabled THEN partial unstake below minimum allowed`() {
|
||||
val transformer = solanaTransformer(
|
||||
staked = BigDecimal("5"),
|
||||
exitMin = BigDecimal("1"),
|
||||
enabled = false,
|
||||
)
|
||||
|
||||
val result = transformer.transform(amountState(BigDecimal("0.5"))) as AmountState.Data
|
||||
|
||||
assertThat(result.amountTextField.isError).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN validation enabled but currency is not Solana THEN Solana rule does not apply`() {
|
||||
fun `WHEN currency is not Solana THEN Solana rule does not apply`() {
|
||||
val transformer = AmountRequirementStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus, // relaxed mock: network.rawId is not "solana"
|
||||
maxAmount = EnterAmountBoundary(amount = BigDecimal("5"), fiatAmount = null, fiatRate = null),
|
||||
integration = exitIntegrationWith(minimum = null, maximum = null),
|
||||
actionType = StakingActionCommonType.Exit(partiallyUnstakeDisabled = false),
|
||||
isSolanaUnstakeValidationEnabled = true,
|
||||
)
|
||||
|
||||
val result = transformer.transform(amountState(BigDecimal("0.5"))) as AmountState.Data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue