Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-08 15:40:17 +05:00
parent a88572a019
commit 22ba475aae
18 changed files with 80 additions and 17 deletions

View file

@ -4,12 +4,14 @@ import android.util.Base64
import arrow.core.getOrElse
import arrow.core.raise.catch
import com.squareup.moshi.Moshi
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.blockchainsdk.utils.toMigratedCointId
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toCompressedPublicKey
import com.tangem.data.common.cache.CacheRegistry
@ -269,6 +271,7 @@ internal class DefaultStakingRepository(
override suspend fun constructTransaction(
networkId: String,
fee: Fee,
amount: Amount,
transactionId: String,
): Pair<StakingTransaction, TransactionData.Compiled> {
return withContext(dispatchers.io) {
@ -282,6 +285,7 @@ internal class DefaultStakingRepository(
val transactionData = TransactionData.Compiled(
value = getTransactionDataType(networkId, unsignedTransaction),
fee = fee,
amount = amount,
status = TransactionStatus.Unconfirmed,
)

View file

@ -10,6 +10,7 @@ internal object PendingTransactionItemConverter : Converter<PendingTransaction,
override fun convert(value: PendingTransaction): BalanceItem? {
return BalanceItem(
groupId = value.groupId ?: return null,
token = value.token,
type = value.type,
amount = value.amount,
rawCurrencyId = value.rawCurrencyId,

View file

@ -2,13 +2,17 @@ package com.tangem.data.staking.converters
import com.tangem.data.staking.converters.action.PendingActionConverter
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.staking.model.stakekit.*
import com.tangem.domain.staking.model.stakekit.BalanceItem
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceItem
import com.tangem.utils.converter.Converter
internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBalance> {
private val pendingActionConverter by lazy(LazyThreadSafetyMode.NONE) { PendingActionConverter() }
private val networkTypeConverter by lazy(LazyThreadSafetyMode.NONE) { StakingNetworkTypeConverter() }
private val tokenConverter by lazy(LazyThreadSafetyMode.NONE) { TokenConverter(networkTypeConverter) }
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance {
return if (value.balances.isEmpty()) {
YieldBalance.Empty
@ -19,6 +23,7 @@ internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBa
items = value.balances.map { item ->
BalanceItem(
groupId = item.groupId,
token = tokenConverter.convert(item.tokenDTO),
type = BalanceType.valueOf(item.type.name),
amount = item.amount,
rawCurrencyId = item.tokenDTO.coinGeckoId,

View file

@ -1,11 +1,13 @@
package com.tangem.domain.staking.model
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.domain.staking.model.stakekit.Token
import com.tangem.domain.staking.model.stakekit.Yield
import java.math.BigDecimal
data class PendingTransaction(
val groupId: String?,
val token: Token,
val type: BalanceType,
val amount: BigDecimal,
val rawCurrencyId: String?,

View file

@ -130,6 +130,8 @@ data class Yield(
APR, // simple rate
UNKNOWN,
}
fun getCurrentToken(rawCurrencyId: String?) = tokens.firstOrNull { rawCurrencyId == it.coinGeckoId } ?: token
}
@Serializable

View file

@ -54,6 +54,7 @@ data class YieldBalanceItem(
data class BalanceItem(
val groupId: String,
val token: Token,
val type: BalanceType,
val amount: BigDecimal,
val rawCurrencyId: String?,

View file

@ -11,8 +11,8 @@ sealed class YieldBalanceList {
val data = yieldBalance as? YieldBalance.Data
val balance = data?.balance
val isCorrectAddress = balance?.items?.any { address == data.address } == true
val isCorrectIntegration = balance?.integrationId == integrationId
val isCorrectAddress = address != null && address == data?.address
val isCorrectIntegration = integrationId != null && balance?.integrationId == integrationId
isCorrectIntegration && isCorrectAddress
} ?: YieldBalance.Error

View file

@ -1,6 +1,7 @@
package com.tangem.domain.staking
import arrow.core.Either
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.staking.model.stakekit.StakingError
@ -16,9 +17,10 @@ class GetConstructedStakingTransactionUseCase(
suspend operator fun invoke(
networkId: String,
fee: Fee,
amount: Amount,
transactionId: String,
): Either<StakingError, Pair<StakingTransaction, TransactionData.Compiled>> = Either.catch {
stakingRepository.constructTransaction(networkId, fee, transactionId)
stakingRepository.constructTransaction(networkId, fee, amount, transactionId)
}.mapLeft {
stakingErrorResolver.resolve(it)
}

View file

@ -67,6 +67,7 @@ class InvalidatePendingTransactionsUseCase(
validatorAddress = pendingTransaction.validator?.address,
date = null,
pendingActions = emptyList(),
token = pendingTransaction.token,
isPending = true,
)
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.staking.repositories
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.domain.core.lce.LceFlow
@ -71,6 +72,7 @@ interface StakingRepository {
suspend fun constructTransaction(
networkId: String,
fee: Fee,
amount: Amount,
transactionId: String,
): Pair<StakingTransaction, TransactionData.Compiled>

View file

@ -57,9 +57,20 @@ internal class CurrencyStatusOperations(
val hasCurrentNetworkTransactions = status.pendingTransactions.isNotEmpty()
val currentTransactions = status.pendingTransactions.getOrElse(currency.id, ::emptySet)
val isCurrentAddressStaking =
(yieldBalance as? YieldBalance.Data)?.address == status.address.defaultAddress.value
val currentYieldBalance = yieldBalance.takeIf { isCurrentAddressStaking }
val yieldBalanceData = yieldBalance as? YieldBalance.Data
val isCurrentAddressStaking = yieldBalanceData?.address == status.address.defaultAddress.value
val filteredTokenBalances = yieldBalanceData?.balance?.items?.filter {
it.token.coinGeckoId == currency.id.rawCurrencyId
}
val currentYieldBalance = if (isCurrentAddressStaking && filteredTokenBalances?.isNotEmpty() == true) {
yieldBalanceData.copy(
balance = yieldBalanceData.balance.copy(
items = filteredTokenBalances,
),
)
} else {
null
}
return when {
ignoreQuote -> CryptoCurrencyStatus.NoQuote(
amount = amount,

View file

@ -1,5 +1,6 @@
package com.tangem.domain.tokens.repository
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.transaction.Fee
@ -217,6 +218,7 @@ class MockStakingRepository : StakingRepository {
override suspend fun constructTransaction(
networkId: String,
fee: Fee,
amount: Amount,
transactionId: String,
): Pair<StakingTransaction, TransactionData.Compiled> = StakingTransaction(
id = "id",

View file

@ -12,6 +12,7 @@ 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.action.StakingActionCommonType
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
@ -66,7 +67,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
val validatorAddress = validatorState.chosenValidator.address
val approval = stakingApproval as? StakingApproval.Needed
if (approval != null) {
if (approval != null && state.actionType == StakingActionCommonType.ENTER) {
val allowance = getAllowanceUseCase(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrencyStatus.currency,
@ -182,7 +183,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
amount = amount,
address = sourceAddress,
validatorAddress = validatorAddress,
token = yield.token,
token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId),
passthrough = action?.passthrough,
type = action?.type,
),

View file

@ -18,6 +18,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents
import com.tangem.features.staking.impl.presentation.state.*
@ -67,6 +68,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
?: error("No confirmation state")
val fee = (confirmationState.feeState as? FeeState.Content)?.fee
?: error("No fee provided")
val amountState = state.amountState as? AmountState.Data ?: error("No amount state")
val stakingTransactions = getStakingTransactions(
state = state,
@ -77,6 +79,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
val fullTransactionsData = getConstructedTransactions(
stakingTransactions = stakingTransactions,
fee = fee,
amount = amountState.amountTextField.cryptoAmount.value.orZero(),
onConstructError = onConstructError,
)
@ -131,6 +134,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
private suspend fun getConstructedTransactions(
stakingTransactions: List<StakingTransaction>?,
fee: Fee,
amount: BigDecimal,
onConstructError: (StakingError) -> Unit,
) = coroutineScope {
stakingTransactions
@ -142,6 +146,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
getConstructedStakingTransactionUseCase(
networkId = cryptoCurrencyStatus.currency.network.id.value,
fee = fee,
amount = amount.convertToSdkAmount(cryptoCurrencyStatus.currency),
transactionId = transaction.id,
).fold(
ifRight = { (constructedTransaction, transactionData) ->
@ -188,7 +193,7 @@ internal class StakingTransactionSender @AssistedInject constructor(
amount = amount,
address = defaultAddress,
validatorAddress = validatorAddress,
token = yield.token,
token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId),
passthrough = action?.passthrough,
type = action?.type,
),
@ -287,13 +292,17 @@ internal class StakingTransactionSender @AssistedInject constructor(
private fun composeStakeTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction {
val state = stateController.value
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
return PendingTransaction(
groupId = UUID.randomUUID().toString(),
token = token,
type = BalanceType.STAKED,
amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO,
rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId,
validator = (confirmationState.validatorState as? ValidatorState.Content)?.chosenValidator,
balancesId = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.getBalancesUniqueId() ?: 0,
balancesId = yieldBalance?.getBalancesUniqueId() ?: 0,
)
}

View file

@ -1,12 +1,16 @@
package com.tangem.features.staking.impl.presentation.state.transformers
import com.tangem.domain.staking.model.PendingTransaction
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.BalanceState
import com.tangem.features.staking.impl.presentation.state.StakingStates
import com.tangem.features.staking.impl.presentation.state.StakingUiState
import com.tangem.utils.transformer.Transformer
internal class SetPossiblePendingTransactionTransformer(
private val yield: Yield,
private val balanceState: BalanceState,
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
) : Transformer<StakingUiState> {
@ -14,13 +18,16 @@ internal class SetPossiblePendingTransactionTransformer(
override fun transform(prevState: StakingUiState): StakingUiState {
val possibleConfirmationState =
prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState
val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data
val balancesId = (cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data)?.getBalancesUniqueId() ?: 0
val balancesId = yieldBalance?.getBalancesUniqueId() ?: 0
val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId)
return prevState.copy(
confirmationState = possibleConfirmationState.copy(
possiblePendingTransaction = PendingTransaction(
groupId = balanceState.groupId,
token = token,
type = balanceState.type,
amount = balanceState.cryptoDecimal,
rawCurrencyId = balanceState.rawCurrencyId,

View file

@ -232,7 +232,9 @@ internal class StakingViewModel @Inject constructor(
),
)
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
stateController.update(
SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus),
)
}
stateController.update(
@ -252,7 +254,9 @@ internal class StakingViewModel @Inject constructor(
isAssentState() -> {
getFee(pendingAction, pendingActions)
if (balanceState != null) {
stateController.update(SetPossiblePendingTransactionTransformer(balanceState, cryptoCurrencyStatus))
stateController.update(
SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus),
)
}
val amountState = value.amountState as? AmountState.Data
if (amountState?.amountTextField?.isWarning == true) {

View file

@ -89,7 +89,7 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "develop-816"
tangemBlockchainSdk = "develop-817"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-387"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^

View file

@ -366,6 +366,15 @@ fun Blockchain.toCoinId(): String {
}
}
/**
* New CoinId to existing coin "id" field.
* To support both old and new coin id.
*/
fun Blockchain.toMigratedCointId(): String = when (this) {
Blockchain.Polygon, Blockchain.PolygonTestnet -> "polygon-ecosystem-token"
else -> toCoinId()
}
fun Blockchain.isSupportedInApp(): Boolean {
return !excludedBlockchains.contains(this)
}