Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-27 20:32:12 +03:00
parent eec5a60676
commit 20dc8c9d90
19 changed files with 436 additions and 89 deletions

View file

@ -55,4 +55,10 @@ internal object StakingDomainModule {
fun provideGetStakingYieldBalanceUseCase(stakingRepository: StakingRepository): GetStakingYieldBalanceUseCase {
return GetStakingYieldBalanceUseCase(stakingRepository = stakingRepository)
}
@Provides
@Singleton
fun provideCreateEnterActionUseCase(stakingRepository: StakingRepository): InitializeStakingProcessUseCase {
return InitializeStakingProcessUseCase(stakingRepository)
}
}

View file

@ -12,5 +12,5 @@ data class StakingGasEstimateDTO(
@Json(name = "token")
val token: TokenDTO,
@Json(name = "gasLimit")
val gasLimit: BigDecimal,
val gasLimit: String?,
)

View file

@ -2,27 +2,47 @@ package com.tangem.data.staking
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.converters.*
import com.tangem.data.staking.converters.StakingNetworkTypeConverter
import com.tangem.data.staking.converters.TokenConverter
import com.tangem.data.staking.converters.YieldConverter
import com.tangem.data.staking.converters.action.ActionStatusConverter
import com.tangem.data.staking.converters.action.EnterActionResponseConverter
import com.tangem.data.staking.converters.action.StakingActionTypeConverter
import com.tangem.data.staking.converters.transaction.GasEstimateConverter
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter
import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.request.Address
import com.tangem.datasource.api.stakekit.models.request.ConstructTransactionRequestBody
import com.tangem.datasource.api.stakekit.models.request.EnterActionRequestBody
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.Token
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.model.action.EnterAction
import com.tangem.domain.staking.model.transaction.StakingTransaction
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.converters.*
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.model.*
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.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.toFormattedString
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
internal class DefaultStakingRepository(
private val stakeKitApi: StakeKitApi,
@ -33,13 +53,31 @@ internal class DefaultStakingRepository(
) : StakingRepository {
private val stakingNetworkTypeConverter = StakingNetworkTypeConverter()
private val networkTypeConverter = StakingNetworkTypeConverter()
private val transactionStatusConverter = StakingTransactionStatusConverter()
private val transactionTypeConverter = StakingTransactionTypeConverter()
private val actionStatusConverter = ActionStatusConverter()
private val stakingActionTypeConverter = StakingActionTypeConverter()
private val tokenConverter = TokenConverter(
stakingNetworkTypeConverter = stakingNetworkTypeConverter,
)
private val yieldConverter = YieldConverter(
tokenConverter = tokenConverter,
)
private val gasEstimateConverter = GasEstimateConverter(
tokenConverter = tokenConverter,
)
private val transactionConverter = StakingTransactionConverter(
networkTypeConverter = networkTypeConverter,
transactionStatusConverter = transactionStatusConverter,
transactionTypeConverter = transactionTypeConverter,
gasEstimateConverter = gasEstimateConverter,
)
private val enterActionResponseConverter = EnterActionResponseConverter(
actionStatusConverter = actionStatusConverter,
stakingActionTypeConverter = stakingActionTypeConverter,
transactionConverter = transactionConverter,
)
private val yieldBalanceConverter = YieldBalanceConverter()
@ -108,6 +146,40 @@ internal class DefaultStakingRepository(
}
}
override suspend fun createEnterAction(
integrationId: String,
amount: BigDecimal,
address: String,
validatorAddress: String,
token: Token,
): EnterAction {
return withContext(dispatchers.io) {
val body = EnterActionRequestBody(
integrationId = integrationId,
addresses = Address(address),
args = EnterActionRequestBody.EnterActionRequestBodyArgs(
amount = amount.toFormattedString(token.decimals),
inputToken = tokenConverter.convertBack(token),
validatorAddress = validatorAddress,
),
)
val response = stakeKitApi.createEnterAction(body)
enterActionResponseConverter.convert(response.getOrThrow())
}
}
override suspend fun constructTransaction(transactionId: String): StakingTransaction {
return withContext(dispatchers.io) {
val transactionResponse = stakeKitApi.constructTransaction(
transactionId = transactionId,
body = ConstructTransactionRequestBody(),
)
transactionConverter.convert(transactionResponse.getOrThrow())
}
}
override suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
address: String,

View file

@ -2,10 +2,10 @@ package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
import com.tangem.domain.staking.model.NetworkType
import com.tangem.utils.converter.Converter
import com.tangem.utils.converter.TwoWayConverter
@Suppress("CyclomaticComplexMethod", "LongMethod")
class StakingNetworkTypeConverter : Converter<NetworkTypeDTO, NetworkType> {
class StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType> {
override fun convert(value: NetworkTypeDTO): NetworkType {
return when (value) {
@ -78,4 +78,76 @@ class StakingNetworkTypeConverter : Converter<NetworkTypeDTO, NetworkType> {
else -> NetworkType.UNKNOWN
}
}
override fun convertBack(value: NetworkType): NetworkTypeDTO {
return when (value) {
NetworkType.AVALANCHE_C -> NetworkTypeDTO.AVALANCHE_C
NetworkType.AVALANCHE_ATOMIC -> NetworkTypeDTO.AVALANCHE_ATOMIC
NetworkType.AVALANCHE_P -> NetworkTypeDTO.AVALANCHE_P
NetworkType.ARBITRUM -> NetworkTypeDTO.ARBITRUM
NetworkType.BINANCE -> NetworkTypeDTO.BINANCE
NetworkType.CELO -> NetworkTypeDTO.CELO
NetworkType.ETHEREUM -> NetworkTypeDTO.ETHEREUM
NetworkType.ETHEREUM_GOERLI -> NetworkTypeDTO.ETHEREUM_GOERLI
NetworkType.ETHEREUM_HOLESKY -> NetworkTypeDTO.ETHEREUM_HOLESKY
NetworkType.FANTOM -> NetworkTypeDTO.FANTOM
NetworkType.HARMONY -> NetworkTypeDTO.HARMONY
NetworkType.OPTIMISM -> NetworkTypeDTO.OPTIMISM
NetworkType.POLYGON -> NetworkTypeDTO.POLYGON
NetworkType.GNOSIS -> NetworkTypeDTO.GNOSIS
NetworkType.MOONRIVER -> NetworkTypeDTO.MOONRIVER
NetworkType.OKC -> NetworkTypeDTO.OKC
NetworkType.ZKSYNC -> NetworkTypeDTO.ZKSYNC
NetworkType.VICTION -> NetworkTypeDTO.VICTION
NetworkType.AGORIC -> NetworkTypeDTO.AGORIC
NetworkType.AKASH -> NetworkTypeDTO.AKASH
NetworkType.AXELAR -> NetworkTypeDTO.AXELAR
NetworkType.BAND_PROTOCOL -> NetworkTypeDTO.BAND_PROTOCOL
NetworkType.BITSONG -> NetworkTypeDTO.BITSONG
NetworkType.CANTO -> NetworkTypeDTO.CANTO
NetworkType.CHIHUAHUA -> NetworkTypeDTO.CHIHUAHUA
NetworkType.COMDEX -> NetworkTypeDTO.COMDEX
NetworkType.COREUM -> NetworkTypeDTO.COREUM
NetworkType.COSMOS -> NetworkTypeDTO.COSMOS
NetworkType.CRESCENT -> NetworkTypeDTO.CRESCENT
NetworkType.CRONOS -> NetworkTypeDTO.CRONOS
NetworkType.CUDOS -> NetworkTypeDTO.CUDOS
NetworkType.DESMOS -> NetworkTypeDTO.DESMOS
NetworkType.DYDX -> NetworkTypeDTO.DYDX
NetworkType.EVMOS -> NetworkTypeDTO.EVMOS
NetworkType.FETCH_AI -> NetworkTypeDTO.FETCH_AI
NetworkType.GRAVITY_BRIDGE -> NetworkTypeDTO.GRAVITY_BRIDGE
NetworkType.INJECTIVE -> NetworkTypeDTO.INJECTIVE
NetworkType.IRISNET -> NetworkTypeDTO.IRISNET
NetworkType.JUNO -> NetworkTypeDTO.JUNO
NetworkType.KAVA -> NetworkTypeDTO.KAVA
NetworkType.KI_NETWORK -> NetworkTypeDTO.KI_NETWORK
NetworkType.MARS_PROTOCOL -> NetworkTypeDTO.MARS_PROTOCOL
NetworkType.NYM -> NetworkTypeDTO.NYM
NetworkType.OKEX_CHAIN -> NetworkTypeDTO.OKEX_CHAIN
NetworkType.ONOMY -> NetworkTypeDTO.ONOMY
NetworkType.OSMOSIS -> NetworkTypeDTO.OSMOSIS
NetworkType.PERSISTENCE -> NetworkTypeDTO.PERSISTENCE
NetworkType.QUICKSILVER -> NetworkTypeDTO.QUICKSILVER
NetworkType.REGEN -> NetworkTypeDTO.REGEN
NetworkType.SECRET -> NetworkTypeDTO.SECRET
NetworkType.SENTINEL -> NetworkTypeDTO.SENTINEL
NetworkType.SOMMELIER -> NetworkTypeDTO.SOMMELIER
NetworkType.STAFI -> NetworkTypeDTO.STAFI
NetworkType.STARGAZE -> NetworkTypeDTO.STARGAZE
NetworkType.STRIDE -> NetworkTypeDTO.STRIDE
NetworkType.TERITORI -> NetworkTypeDTO.TERITORI
NetworkType.TGRADE -> NetworkTypeDTO.TGRADE
NetworkType.UMEE -> NetworkTypeDTO.UMEE
NetworkType.POLKADOT -> NetworkTypeDTO.POLKADOT
NetworkType.KUSAMA -> NetworkTypeDTO.KUSAMA
NetworkType.WESTEND -> NetworkTypeDTO.WESTEND
NetworkType.BINANCEBEACON -> NetworkTypeDTO.BINANCEBEACON
NetworkType.NEAR -> NetworkTypeDTO.NEAR
NetworkType.SOLANA -> NetworkTypeDTO.SOLANA
NetworkType.TEZOS -> NetworkTypeDTO.TEZOS
NetworkType.TRON -> NetworkTypeDTO.TRON
else -> NetworkTypeDTO.UNKNOWN
}
}
}

View file

@ -2,11 +2,11 @@ package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
import com.tangem.domain.staking.model.Token
import com.tangem.utils.converter.Converter
import com.tangem.utils.converter.TwoWayConverter
class TokenConverter(
private val stakingNetworkTypeConverter: StakingNetworkTypeConverter,
) : Converter<TokenDTO, Token> {
) : TwoWayConverter<TokenDTO, Token> {
override fun convert(value: TokenDTO): Token {
return Token(
@ -20,4 +20,17 @@ class TokenConverter(
isPoints = value.isPoints,
)
}
override fun convertBack(value: Token): TokenDTO {
return TokenDTO(
name = value.name,
network = stakingNetworkTypeConverter.convertBack(value.network),
symbol = value.symbol,
decimals = value.decimals,
address = value.address,
coinGeckoId = value.coinGeckoId,
logoURI = value.logoURI,
isPoints = value.isPoints,
)
}
}

View file

@ -20,7 +20,10 @@ class YieldConverter(
rewardRate = value.rewardRate,
rewardType = convertRewardType(value.rewardType),
metadata = convertMetadata(value.metadata),
validators = value.validators.map { convertValidator(it) },
validators = value.validators
.filter { it.preferred }
.map { convertValidator(it) }
.sortedByDescending { it.apr },
isAvailable = value.isAvailable,
)
}

View file

@ -6,5 +6,5 @@ import java.math.BigDecimal
data class StakingGasEstimate(
val amount: BigDecimal,
val token: Token,
val gasLimit: BigDecimal,
val gasLimit: String?,
)

View file

@ -0,0 +1,45 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.domain.staking.model.Token
import com.tangem.domain.staking.model.action.EnterAction
import com.tangem.domain.staking.model.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.StakingRepository
import kotlinx.coroutines.delay
import java.math.BigDecimal
/**
* Use case for creating enter action
*/
class InitializeStakingProcessUseCase(private val stakingRepository: StakingRepository) {
suspend operator fun invoke(
integrationId: String,
amount: BigDecimal,
address: String,
validatorAddress: String,
token: Token,
): Either<Throwable, Pair<EnterAction, StakingTransaction>> {
return Either.catch {
val createAction = stakingRepository.createEnterAction(
integrationId = integrationId,
amount = amount,
address = address,
validatorAddress = validatorAddress,
token = token,
)
// workaround, sometimes transaction is not created immediately after actions/enter
delay(PATCH_TRANSACTION_REQUEST_DELAY)
val createdTransaction = createAction.transactions?.get(0) ?: error("No available transaction to patch")
val patchedTransaction = stakingRepository.constructTransaction(createdTransaction.id)
createAction to patchedTransaction
}
}
companion object {
private const val PATCH_TRANSACTION_REQUEST_DELAY = 1000L
}
}

View file

@ -1,5 +1,12 @@
package com.tangem.domain.staking.repositories
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.Token
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.model.action.EnterAction
import com.tangem.domain.staking.model.transaction.StakingTransaction
import java.math.BigDecimal
import com.tangem.domain.staking.model.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyAddress
@ -46,4 +53,14 @@ interface StakingRepository {
addresses: List<CryptoCurrencyAddress>,
integrationId: String,
): Flow<YieldBalanceList>
suspend fun createEnterAction(
integrationId: String,
amount: BigDecimal,
address: String,
validatorAddress: String,
token: Token,
): EnterAction
suspend fun constructTransaction(transactionId: String): StakingTransaction
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.staking.impl.presentation.state
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import java.math.BigDecimal
sealed class FeeState {
data class Content(
val fee: Fee?,
val rate: BigDecimal?,
val isFeeConvertibleToFiat: Boolean,
val appCurrency: AppCurrency,
val isFeeApproximate: Boolean,
) : FeeState()
data object Loading : FeeState()
data object Error : FeeState()
}

View file

@ -1,9 +1,7 @@
package com.tangem.features.staking.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.transaction.TransactionFee
@Immutable
internal sealed class InnerFeeState {
data class Content(

View file

@ -7,22 +7,20 @@ internal class StakingStateRouter(
private val fragmentManager: WeakReference<FragmentManager>,
private val stateController: StakingStateController,
) {
fun clear() {
stateController.update { it.copy(currentStep = getInitialState()) }
}
fun popBackStack() {
private fun closeStaking() {
fragmentManager.get()?.popBackStack()
stateController.clear()
}
fun onBackClick(isSuccess: Boolean = false) {
val type = stateController.uiState.value.currentStep
when {
isSuccess -> popBackStack()
isSuccess -> closeStaking()
else -> when (type) {
StakingStep.Amount -> showInitial()
StakingStep.Confirm -> showAmount()
else -> popBackStack()
else -> closeStaking()
}
}
}
@ -34,14 +32,17 @@ internal class StakingStateRouter(
StakingStep.Amount,
-> showConfirm()
StakingStep.Confirm -> showSuccess()
StakingStep.Success -> onBackClick()
StakingStep.Success -> closeStaking()
}
}
fun onPrevClick() {
when (stateController.uiState.value.currentStep) {
StakingStep.Amount -> showInitial()
else -> popBackStack()
StakingStep.Confirm -> showAmount()
StakingStep.Success -> closeStaking()
StakingStep.Validators -> showConfirm()
else -> closeStaking()
}
}
@ -64,6 +65,4 @@ internal class StakingStateRouter(
fun showSuccess() {
stateController.update { it.copy(currentStep = StakingStep.Success) }
}
private fun getInitialState() = StakingStep.InitialInfo
}

View file

@ -1,16 +1,13 @@
package com.tangem.features.staking.impl.presentation.state
import androidx.compose.runtime.Immutable
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.staking.impl.presentation.state.transformers.InfoType
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import kotlinx.collections.immutable.ImmutableList
import java.math.BigDecimal
/**
* Ui states of the staking screen
@ -79,21 +76,12 @@ internal sealed class StakingStates {
override val isPrimaryButtonEnabled: Boolean = false,
) : ConfirmStakingState()
}
data class FeeState(
val innerFeeState: InnerFeeState,
val fee: Fee?,
val rate: BigDecimal?,
val isFeeConvertibleToFiat: Boolean,
val appCurrency: AppCurrency,
val isFeeApproximate: Boolean,
)
}
enum class StakingStep {
InitialInfo,
Amount,
Validators,
Confirm,
Validators,
Success,
}

View file

@ -3,10 +3,9 @@ package com.tangem.features.staking.impl.presentation.state.previewdata
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType.Coin
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.Yield
import com.tangem.features.staking.impl.presentation.state.InnerFeeState
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.StakingNotification
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.ValidatorState
@ -65,8 +64,7 @@ internal object ConfirmStakingStatePreviewData {
val confirmStakingState = StakingStates.ConfirmStakingState.Data(
isPrimaryButtonEnabled = true,
feeState = StakingStates.FeeState(
innerFeeState = InnerFeeState.Content(TransactionFee.Single(fee)),
feeState = FeeState.Content(
fee = fee,
rate = BigDecimal.ONE,
appCurrency = AppCurrency.Default,

View file

@ -0,0 +1,42 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.Yield
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.StakingNotification
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.ValidatorState
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.persistentListOf
@Suppress("UnusedPrivateMember")
internal class SetConfirmLoadingStateTransformer(
private val yield: Yield,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmStakingState = prevState.confirmStakingState as? StakingStates.ConfirmStakingState.Data
val possibleValidatorState = possibleConfirmStakingState?.validatorState as? ValidatorState.Content
val chosenValidator = possibleValidatorState?.chosenValidator ?: yield.validators[0] // TODO staking add sorting
return prevState.copy(
confirmStakingState = StakingStates.ConfirmStakingState.Data(
isPrimaryButtonEnabled = false,
feeState = FeeState.Loading,
validatorState = ValidatorState.Content(
chosenValidator = chosenValidator,
availableValidators = yield.validators,
),
notifications = persistentListOf(
StakingNotification.Warning.EarnRewards(
currencyName = yield.token.name,
days = yield.metadata.cooldownPeriod.days,
),
),
footerText = "",
isSuccess = false,
isStaking = false, // TODO staking move to enum
),
)
}
}

View file

@ -2,34 +2,49 @@ package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.model.transaction.StakingGasEstimate
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
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.features.staking.impl.presentation.state.previewdata.ConfirmStakingStatePreviewData
import com.tangem.utils.Provider
import com.tangem.utils.transformer.Transformer
import com.tangem.blockchain.common.Amount
@Suppress("UnusedPrivateMember")
internal class SetConfirmStateDataStateTransformer(
private val yield: Yield,
private val appCurrencyProvider: Provider<AppCurrency>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
private val isFeeApproximateUseCase: IsFeeApproximateUseCase,
private val stakingGasEstimate: StakingGasEstimate,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
// TODO staking fill with real data
return prevState.copy(
confirmStakingState = ConfirmStakingStatePreviewData.confirmStakingState,
confirmStakingState = prevState.confirmStakingState.copyWrapped(stakingGasEstimate),
)
}
private fun isFeeApproximate(fee: Fee): Boolean {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
return isFeeApproximateUseCase(
networkId = cryptoCurrencyStatus.currency.network.id,
amountType = fee.amount.type,
)
private fun StakingStates.ConfirmStakingState.copyWrapped(
gasEstimate: StakingGasEstimate,
): StakingStates.ConfirmStakingState {
if (this is StakingStates.ConfirmStakingState.Data) {
return copy(
feeState = FeeState.Content(
fee = Fee.Common(
Amount(
currencySymbol = gasEstimate.token.symbol,
value = gasEstimate.amount,
decimals = gasEstimate.token.decimals,
),
),
rate = cryptoCurrencyStatusProvider().value.fiatRate,
isFeeConvertibleToFiat = cryptoCurrencyStatusProvider().currency.network.hasFiatFeeRate,
appCurrency = appCurrencyProvider(),
isFeeApproximate = false,
),
)
} else {
return this
}
}
}

View file

@ -116,8 +116,8 @@ private fun isButtonEnabled(uiState: StakingUiState): Boolean {
return when (uiState.currentStep) {
StakingStep.InitialInfo -> uiState.initialInfoState.isPrimaryButtonEnabled
StakingStep.Amount -> uiState.amountState.isPrimaryButtonEnabled
StakingStep.Validators -> uiState.confirmStakingState.isPrimaryButtonEnabled
StakingStep.Confirm -> uiState.confirmStakingState.isPrimaryButtonEnabled
StakingStep.Success -> true
StakingStep.Validators -> true
}
}

View file

@ -16,7 +16,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.rows.SelectorRowItem
import com.tangem.core.ui.res.TangemThemePreview
@ -26,12 +25,11 @@ import com.tangem.common.ui.R
import com.tangem.common.ui.amountScreen.utils.getCryptoReference
import com.tangem.common.ui.amountScreen.utils.getFiatReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.staking.impl.presentation.state.InnerFeeState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.FeeState
import java.math.BigDecimal
@Composable
internal fun StakingFeeBlock(feeState: StakingStates.FeeState) {
internal fun StakingFeeBlock(feeState: FeeState) {
Column(
modifier = Modifier
.fillMaxWidth()
@ -48,37 +46,45 @@ internal fun StakingFeeBlock(feeState: StakingStates.FeeState) {
Box(
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
) {
val feeAmount = feeState.fee?.amount
val (title, icon) = R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
SelectorRowItem(
titleRes = title,
iconRes = icon,
preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
postDot = if (feeState.isFeeConvertibleToFiat) {
getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency)
} else {
null
},
ellipsizeOffset = feeAmount?.currencySymbol?.length,
isSelected = true,
showDivider = false,
showSelectedAppearance = false,
paddingValues = PaddingValues(),
)
FeeLoading(feeState.innerFeeState)
FeeError(feeState.innerFeeState)
when (feeState) {
is FeeState.Content -> {
val feeAmount = feeState.fee?.amount
val (title, icon) = R.string.common_fee_selector_option_market to R.drawable.ic_bird_24
SelectorRowItem(
titleRes = title,
iconRes = icon,
preDot = getCryptoReference(feeAmount, feeState.isFeeApproximate),
postDot = if (feeState.isFeeConvertibleToFiat) {
getFiatReference(feeAmount?.value, feeState.rate, feeState.appCurrency)
} else {
null
},
ellipsizeOffset = feeAmount?.currencySymbol?.length,
isSelected = true,
showDivider = false,
showSelectedAppearance = false,
paddingValues = PaddingValues(),
)
}
is FeeState.Loading -> {
FeeLoading(feeState)
}
is FeeState.Error -> {
FeeError(feeState)
}
}
}
}
}
@Composable
private fun BoxScope.FeeLoading(feeSelectorState: InnerFeeState) {
private fun BoxScope.FeeLoading(feeState: FeeState) {
AnimatedContent(
targetState = feeSelectorState,
targetState = feeState,
label = "Fee Loading State Change",
modifier = Modifier.align(Alignment.CenterEnd),
) {
if (it == InnerFeeState.Loading) {
if (it == FeeState.Loading) {
RectangleShimmer(
radius = TangemTheme.dimens.radius3,
modifier = Modifier.size(
@ -91,13 +97,13 @@ private fun BoxScope.FeeLoading(feeSelectorState: InnerFeeState) {
}
@Composable
private fun BoxScope.FeeError(feeSelectorState: InnerFeeState) {
private fun BoxScope.FeeError(feeState: FeeState) {
AnimatedContent(
targetState = feeSelectorState,
targetState = feeState,
label = "Fee Error State Change",
modifier = Modifier.align(Alignment.CenterEnd),
) {
if (it == InnerFeeState.Error) {
if (it == FeeState.Error) {
Text(
text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
color = TangemTheme.colors.text.primary1,
@ -111,7 +117,7 @@ private fun BoxScope.FeeError(feeSelectorState: InnerFeeState) {
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: StakingStates.FeeState) {
private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) value: FeeState.Content) {
TangemThemePreview {
StakingFeeBlock(
feeState = value,
@ -119,9 +125,9 @@ private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) va
}
}
private class FeeBlockPreviewProvider : PreviewParameterProvider<StakingStates.FeeState> {
private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState.Content> {
override val values: Sequence<StakingStates.FeeState>
override val values: Sequence<FeeState.Content>
get() = sequenceOf(
feeState,
)
@ -135,8 +141,7 @@ private class FeeBlockPreviewProvider : PreviewParameterProvider<StakingStates.F
),
)
private val feeState = StakingStates.FeeState(
innerFeeState = InnerFeeState.Content(TransactionFee.Single(normal = fee)),
private val feeState = FeeState.Content(
fee = fee,
rate = BigDecimal.ONE,
appCurrency = AppCurrency.Default,

View file

@ -8,9 +8,11 @@ import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.staking.InitializeStakingProcessUseCase
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
@ -21,6 +23,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.StakingStateController
import com.tangem.features.staking.impl.presentation.state.StakingStateRouter
import com.tangem.features.staking.impl.presentation.state.StakingStep
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.features.staking.impl.presentation.state.transformers.*
import com.tangem.features.staking.impl.presentation.state.transformers.amount.AmountChangeStateTransformer
@ -45,6 +48,7 @@ internal class StakingViewModel @Inject constructor(
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val initializeStakingProcessUseCase: InitializeStakingProcessUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents {
@ -85,10 +89,60 @@ internal class StakingViewModel @Inject constructor(
override fun onNextClick() {
stakingStateRouter.onNextClick()
when (value.currentStep) {
StakingStep.Confirm -> {
initStaking()
}
StakingStep.InitialInfo -> {
// TODO staking
}
StakingStep.Amount -> {
// TODO staking
}
StakingStep.Success -> {
// TODO staking
}
StakingStep.Validators -> {
// TODO staking
}
}
}
private fun initStaking() {
viewModelScope.launch {
stateController.update(
SetConfirmLoadingStateTransformer(
yield = yield,
),
)
val actionWithTransaction = initializeStakingProcessUseCase(
integrationId = yield.id,
amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
?: error("No amount provided"),
address = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
?: error("No available address"),
validatorAddress = yield.validators.getOrNull(0)?.address ?: error("No available validator"),
token = yield.token,
)
val (enterAction, stakingTransaction) = actionWithTransaction.getOrElse {
error("Can't retrieve action and transaction")
}
val stakingGasEstimate = stakingTransaction.gasEstimate ?: error("Can't get fee info")
stateController.update(
SetConfirmStateDataStateTransformer(
appCurrencyProvider = Provider { appCurrency },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
stakingGasEstimate = stakingGasEstimate,
),
)
}
}
override fun onPrevClick() {
stakingStateRouter.onBackClick()
stakingStateRouter.onPrevClick()
}
override fun onInfoClick(infoType: InfoType) {