diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts index 0a1688dee1..017cc43973 100644 --- a/data/staking/build.gradle.kts +++ b/data/staking/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.staking) implementation(projects.domain.wallets.models) + implementation(projects.domain.legacy) /** Feature Api modules */ implementation(projects.features.staking.api) @@ -40,6 +41,8 @@ dependencies { implementation(projects.libs.blockchainSdk) + + implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) { exclude(module = "joda-time") } diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index bc015d17b5..1a2bc1999c 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -1,8 +1,10 @@ package com.tangem.data.staking +import android.util.Base64 import arrow.core.raise.catch import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.toCoinId +import com.tangem.common.extensions.toCompressedPublicKey import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.staking.converters.* import com.tangem.data.staking.converters.action.ActionStatusConverter @@ -23,7 +25,10 @@ import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.core.lce.lceFlow -import com.tangem.domain.staking.model.* +import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata +import com.tangem.domain.staking.model.stakekit.NetworkType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceList @@ -37,10 +42,10 @@ import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.toFormattedString import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -55,6 +60,7 @@ internal class DefaultStakingRepository( private val cacheRegistry: CacheRegistry, private val dispatchers: CoroutineDispatcherProvider, private val stakingFeatureToggle: StakingFeatureToggles, + private val walletManagersFacade: WalletManagersFacade, ) : StakingRepository { private val stakingNetworkTypeConverter = StakingNetworkTypeConverter() @@ -160,11 +166,27 @@ internal class DefaultStakingRepository( } } - override suspend fun createAction(params: ActionParams): StakingAction { + override suspend fun createAction( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): StakingAction { return withContext(dispatchers.io) { val response = when (params.actionCommonType) { - StakingActionCommonType.ENTER -> stakeKitApi.createEnterAction(createActionRequestBody(params)) - StakingActionCommonType.EXIT -> stakeKitApi.createExitAction(createActionRequestBody(params)) + StakingActionCommonType.ENTER -> stakeKitApi.createEnterAction( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) + StakingActionCommonType.EXIT -> stakeKitApi.createExitAction( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) StakingActionCommonType.PENDING -> stakeKitApi.createPendingAction( createPendingActionRequestBody(params), ) @@ -174,11 +196,27 @@ internal class DefaultStakingRepository( } } - override suspend fun estimateGas(params: ActionParams): StakingGasEstimate { + override suspend fun estimateGas( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): StakingGasEstimate { return withContext(dispatchers.io) { val gasEstimateDTO = when (params.actionCommonType) { - StakingActionCommonType.ENTER -> stakeKitApi.estimateGasOnEnter(createActionRequestBody(params)) - StakingActionCommonType.EXIT -> stakeKitApi.estimateGasOnExit(createActionRequestBody(params)) + StakingActionCommonType.ENTER -> stakeKitApi.estimateGasOnEnter( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) + StakingActionCommonType.EXIT -> stakeKitApi.estimateGasOnExit( + createActionRequestBody( + userWalletId, + network, + params, + ), + ) StakingActionCommonType.PENDING -> stakeKitApi.estimateGasOnPending( createPendingActionRequestBody(params), ) @@ -435,12 +473,19 @@ internal class DefaultStakingRepository( } } - private fun createActionRequestBody(params: ActionParams): ActionRequestBody { + private suspend fun createActionRequestBody( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): ActionRequestBody { return ActionRequestBody( integrationId = params.integrationId, - addresses = Address(params.address), + addresses = Address( + address = params.address, + additionalAddresses = createAdditionalAddresses(userWalletId, network, params), + ), args = ActionRequestBodyArgs( - amount = params.amount.toFormattedString(params.token.decimals), + amount = params.amount.toPlainString(), inputToken = tokenConverter.convertBack(params.token), validatorAddress = params.validatorAddress, ), @@ -453,12 +498,29 @@ internal class DefaultStakingRepository( type = params.type ?: StakingActionType.UNKNOWN, passthrough = params.passthrough.orEmpty(), args = ActionRequestBodyArgs( - amount = params.amount.toFormattedString(params.token.decimals), + amount = params.amount.toPlainString(), validatorAddress = params.validatorAddress, ), ) } + private suspend fun createAdditionalAddresses( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): Address.AdditionalAddresses? { + val selectedWallet = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + return when (params.token.network) { + NetworkType.COSMOS -> Address.AdditionalAddresses( + cosmosPubKey = Base64.encodeToString( + /* input = */ selectedWallet?.wallet?.publicKey?.blockchainKey?.toCompressedPublicKey(), + /* flags = */ Base64.NO_WRAP, + ), + ) + else -> null + } + } + override fun isStakeMoreAvailable(networkId: Network.ID): Boolean { val blockchain = Blockchain.fromId(networkId.value) return when (blockchain) { diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index f0562851c1..d261cdece4 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -13,6 +13,7 @@ import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -35,6 +36,7 @@ internal object StakingDataModule { dispatchers: CoroutineDispatcherProvider, stakingFeatureToggle: StakingFeatureToggles, cacheRegistry: CacheRegistry, + walletManagersFacade: WalletManagersFacade, ): StakingRepository { return DefaultStakingRepository( stakeKitApi = stakeKitApi, @@ -44,6 +46,7 @@ internal object StakingDataModule { dispatchers = dispatchers, cacheRegistry = cacheRegistry, stakingFeatureToggle = stakingFeatureToggle, + walletManagersFacade = walletManagersFacade, ) } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/transaction/ActionParams.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/transaction/ActionParams.kt index 2adfaed13b..605ed69fa5 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/transaction/ActionParams.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/transaction/ActionParams.kt @@ -12,6 +12,7 @@ data class ActionParams( val address: String, val validatorAddress: String, val token: Token, + val publicKey: String? = null, val passthrough: String? = null, val type: StakingActionType? = null, ) \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt index 381308b7a7..e5d79ff1b8 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/EstimateGasUseCase.kt @@ -6,6 +6,8 @@ import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId /** * Use case for staking gas estimation. @@ -15,9 +17,13 @@ class EstimateGasUseCase( private val stakingErrorResolver: StakingErrorResolver, ) { - suspend operator fun invoke(params: ActionParams): Either { + suspend operator fun invoke( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): Either { return Either.catch { - stakingRepository.estimateGas(params) + stakingRepository.estimateGas(userWalletId, network, params) }.mapLeft { stakingErrorResolver.resolve(it) } diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionUseCase.kt index d6d147b1c3..cfe2c9a254 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingTransactionUseCase.kt @@ -6,6 +6,8 @@ import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.delay /** @@ -16,9 +18,13 @@ class GetStakingTransactionUseCase( private val stakingErrorResolver: StakingErrorResolver, ) { - suspend operator fun invoke(params: ActionParams): Either { + suspend operator fun invoke( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): Either { return Either.catch { - val createAction = stakingRepository.createAction(params) + val createAction = stakingRepository.createAction(userWalletId, network, params) // workaround, sometimes transaction is not created immediately after actions/enter delay(PATCH_TRANSACTION_REQUEST_DELAY) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index a9ff51285e..5605958156 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -61,9 +61,9 @@ interface StakingRepository { addresses: List, ): YieldBalanceList - suspend fun createAction(params: ActionParams): StakingAction + suspend fun createAction(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingAction - suspend fun estimateGas(params: ActionParams): StakingGasEstimate + suspend fun estimateGas(userWalletId: UserWalletId, network: Network, params: ActionParams): StakingGasEstimate suspend fun constructTransaction(transactionId: String): StakingTransaction diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index ad8a88a0fd..75e83715bb 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -167,7 +167,11 @@ class MockStakingRepository : StakingRepository { balances = listOf(YieldBalance.Error), ) - override suspend fun createAction(params: ActionParams): StakingAction { + override suspend fun createAction( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): StakingAction { return StakingAction( id = "quis", integrationId = "persequeris", @@ -182,7 +186,11 @@ class MockStakingRepository : StakingRepository { ) } - override suspend fun estimateGas(params: ActionParams): StakingGasEstimate { + override suspend fun estimateGas( + userWalletId: UserWalletId, + network: Network, + params: ActionParams, + ): StakingGasEstimate { return StakingGasEstimate( amount = BigDecimal(0.0001), token = Token( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 23141275d2..739015eb13 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -113,7 +113,6 @@ internal class YieldBalancesConverter( private fun BalanceType.toGroup() = when (this) { BalanceType.PREPARING, BalanceType.STAKED, - BalanceType.REWARDS, BalanceType.AVAILABLE, BalanceType.LOCKED, -> BalanceGroupType.ACTIVE @@ -121,6 +120,7 @@ internal class YieldBalancesConverter( BalanceType.UNLOCKING, BalanceType.UNSTAKED, -> BalanceGroupType.UNSTAKED + BalanceType.REWARDS, BalanceType.UNKNOWN, -> BalanceGroupType.UNKNOWN } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt index 4efdb9141d..b87f8a0251 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountChangeStateTransformer.kt @@ -12,13 +12,11 @@ internal class AmountChangeStateTransformer( private val value: String, ) : Transformer { - private val amountRequirementStateTransformer by lazy(LazyThreadSafetyMode.NONE) { - AmountRequirementStateTransformer( - cryptoCurrencyStatus, - yield, - value, - ) - } + private val amountRequirementStateTransformer = AmountRequirementStateTransformer( + cryptoCurrencyStatus, + yield, + value, + ) override fun transform(prevState: StakingUiState): StakingUiState { val updatedAmountState = AmountFieldChangeTransformer( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt index 8270ef3971..ae53eab63e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountMaxValueStateTransformer.kt @@ -12,16 +12,13 @@ internal class AmountMaxValueStateTransformer( private val yield: Yield, ) : Transformer { - private val amountRequirementStateTransformer by lazy(LazyThreadSafetyMode.NONE) { - val value = cryptoCurrencyStatus.value.amount + private val amountRequirementStateTransformer = AmountRequirementStateTransformer( + cryptoCurrencyStatus = cryptoCurrencyStatus, + yield = yield, + value = cryptoCurrencyStatus.value.amount ?.parseBigDecimal(cryptoCurrencyStatus.currency.decimals) - .orEmpty() - AmountRequirementStateTransformer( - cryptoCurrencyStatus, - yield, - value, - ) - } + .orEmpty(), + ) override fun transform(prevState: StakingUiState): StakingUiState { val updatedAmountState = AmountFieldMaxAmountTransformer(cryptoCurrencyStatus).transform(prevState.amountState) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt index 72d7a6cb2d..f16597ae54 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/amount/AmountRequirementStateTransformer.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.staking.model.stakekit.AddressArgument import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.staking.impl.R @@ -18,38 +19,47 @@ internal class AmountRequirementStateTransformer( ) : Transformer { override fun transform(prevState: AmountState): AmountState { val amountRequirements = yield.args.enter.args[Yield.Args.ArgType.AMOUNT] - val amountDecimal = value.parseToBigDecimal(cryptoCurrencyStatus.currency.decimals) return if (prevState !is AmountState.Data || amountRequirements == null) { prevState } else { - val isAlreadyErrorState = prevState.amountTextField.isError - val isAmountRequired = amountRequirements.required - val isAmountZero = amountDecimal.isZero() - val isExceedsRequirements = - amountRequirements.maximum?.compareTo(amountDecimal) == -1 || - amountRequirements.minimum?.compareTo(amountDecimal) == 1 + updateWithError(prevState, amountRequirements) + } + } - val isRequirementError = !isAmountZero && isAmountRequired && isExceedsRequirements && !isAlreadyErrorState - if (isRequirementError) { - prevState.copy( - amountTextField = prevState.amountTextField.copy( - isError = true, - error = resourceReference( - R.string.staking_amount_requirement_error, - wrappedList( - BigDecimalFormatter.formatCryptoAmount( - amountRequirements.minimum, - cryptoCurrencyStatus.currency.symbol, - cryptoCurrencyStatus.currency.decimals, - ), + private fun updateWithError(prevState: AmountState.Data, amountRequirements: AddressArgument): AmountState { + val isRequirementError = isRequirementError(prevState, amountRequirements) + return if (isRequirementError) { + prevState.copy( + amountTextField = prevState.amountTextField.copy( + isError = true, + error = resourceReference( + R.string.staking_amount_requirement_error, + wrappedList( + BigDecimalFormatter.formatCryptoAmount( + amountRequirements.minimum, + cryptoCurrencyStatus.currency.symbol, + cryptoCurrencyStatus.currency.decimals, ), ), ), - ) - } else { - prevState - } + ), + ) + } else { + prevState } } + + private fun isRequirementError(prevState: AmountState.Data, amountRequirements: AddressArgument): Boolean { + val amountDecimal = value.parseToBigDecimal(cryptoCurrencyStatus.currency.decimals) + + val isAlreadyErrorState = prevState.amountTextField.isError + val isAmountRequired = amountRequirements.required + val isAmountZero = amountDecimal.isZero() + val isExceedsRequirements = + amountRequirements.maximum?.compareTo(amountDecimal) == -1 || + amountRequirements.minimum?.compareTo(amountDecimal) == 1 + + return !isAmountZero && isAmountRequired && isExceedsRequirements && !isAlreadyErrorState + } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 5459256c1d..c656944417 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -120,6 +120,8 @@ internal class StakingViewModel @Inject constructor( val pendingActions = confirmationState.pendingActions val stakingTransaction = getStakingTransactionUseCase( + userWalletId = userWalletId, + network = cryptoCurrencyStatus.currency.network, params = ActionParams( actionCommonType = getStakingCommonType(), integrationId = yield.id, @@ -158,6 +160,8 @@ internal class StakingViewModel @Inject constructor( val cryptoCurrencyValue = cryptoCurrencyStatus.value val stakingGasEstimate = estimateGasUseCase( + userWalletId = userWalletId, + network = cryptoCurrencyStatus.currency.network, params = ActionParams( actionCommonType = getStakingCommonType(), integrationId = yield.id,