Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-09 16:44:30 +03:00
parent 3a9bdb16ad
commit aa621dd48f
10 changed files with 78 additions and 36 deletions

View file

@ -294,9 +294,8 @@ internal object TokensDomainModule {
@Singleton @Singleton
fun provideIsAmountSubtractAvailableUseCase( fun provideIsAmountSubtractAvailableUseCase(
currenciesRepository: CurrenciesRepository, currenciesRepository: CurrenciesRepository,
dispatchers: CoroutineDispatcherProvider,
): IsAmountSubtractAvailableUseCase { ): IsAmountSubtractAvailableUseCase {
return IsAmountSubtractAvailableUseCase(currenciesRepository, dispatchers) return IsAmountSubtractAvailableUseCase(currenciesRepository)
} }
@Provides @Provides

View file

@ -2,11 +2,9 @@ package com.tangem.domain.tokens
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.FeePaidCurrency import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
/** /**
* Use case for checking if currency amount can be subtracted. * Use case for checking if currency amount can be subtracted.
@ -14,17 +12,18 @@ import kotlinx.coroutines.withContext
*/ */
class IsAmountSubtractAvailableUseCase( class IsAmountSubtractAvailableUseCase(
private val currenciesRepository: CurrenciesRepository, private val currenciesRepository: CurrenciesRepository,
private val dispatchers: CoroutineDispatcherProvider,
) { ) {
suspend operator fun invoke(userWalletId: UserWalletId, currency: CryptoCurrency): Either<Throwable, Boolean> = suspend operator fun invoke(
Either.catch { userWalletId: UserWalletId,
withContext(dispatchers.io) { currency: CryptoCurrency,
when (val feeCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, currency.network)) { isGaslessEthTx: Boolean = false,
is FeePaidCurrency.Coin -> currency is CryptoCurrency.Coin ): Either<Throwable, Boolean> = Either.catch {
is FeePaidCurrency.SameCurrency -> true if (isGaslessEthTx) return@catch true
is FeePaidCurrency.Token -> currency.id == feeCurrency.tokenId when (val feeCurrency = currenciesRepository.getFeePaidCurrency(userWalletId, currency.network)) {
is FeePaidCurrency.FeeResource -> false is FeePaidCurrency.Coin -> currency is CryptoCurrency.Coin
} is FeePaidCurrency.SameCurrency -> true
} is FeePaidCurrency.Token -> currency.id == feeCurrency.tokenId
is FeePaidCurrency.FeeResource -> false
} }
}
} }

View file

@ -28,7 +28,6 @@ interface SendNotificationsComponent {
val analyticsCategoryName: String, val analyticsCategoryName: String,
val userWalletId: UserWalletId, val userWalletId: UserWalletId,
val cryptoCurrencyStatus: CryptoCurrencyStatus, val cryptoCurrencyStatus: CryptoCurrencyStatus,
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
val appCurrency: AppCurrency, val appCurrency: AppCurrency,
val notificationData: NotificationData, val notificationData: NotificationData,
val callback: ModelCallback, val callback: ModelCallback,
@ -41,6 +40,7 @@ interface SendNotificationsComponent {
val isIgnoreReduce: Boolean, val isIgnoreReduce: Boolean,
val fee: Fee?, val fee: Fee?,
val feeError: GetFeeError?, val feeError: GetFeeError?,
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
) )
} }

View file

@ -105,7 +105,6 @@ internal class SendConfirmComponent(
analyticsCategoryName = params.analyticsCategoryName, analyticsCategoryName = params.analyticsCategoryName,
userWalletId = params.userWallet.walletId, userWalletId = params.userWallet.walletId,
cryptoCurrencyStatus = params.cryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
appCurrency = params.appCurrency, appCurrency = params.appCurrency,
callback = model, callback = model,
notificationData = NotificationData( notificationData = NotificationData(
@ -116,6 +115,7 @@ internal class SendConfirmComponent(
isIgnoreReduce = model.confirmData.isIgnoreReduce, isIgnoreReduce = model.confirmData.isIgnoreReduce,
fee = model.confirmData.fee, fee = model.confirmData.fee,
feeError = model.confirmData.feeError, feeError = model.confirmData.feeError,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
), ),
), ),
) )

View file

@ -5,6 +5,7 @@ import androidx.compose.runtime.Stable
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.models.AmountState
@ -29,6 +30,7 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase
import com.tangem.domain.settings.NeverShowTapHelpUseCase import com.tangem.domain.settings.NeverShowTapHelpUseCase
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
@ -155,10 +157,7 @@ internal class SendConfirmModel @Inject constructor(
internal var feeStateConfiguration: FeeStateConfiguration = FeeStateConfiguration.None internal var feeStateConfiguration: FeeStateConfiguration = FeeStateConfiguration.None
init { init {
modelScope.launch { updateAmountSubtractAvailability()
isAmountSubtractAvailable =
isAmountSubtractAvailableUseCase(userWallet.walletId, cryptoCurrency).getOrElse { false }
}
configConfirmNavigation() configConfirmNavigation()
subscribeOnNotificationsUpdateTrigger() subscribeOnNotificationsUpdateTrigger()
subscribeOnCheckFeeResultUpdates() subscribeOnCheckFeeResultUpdates()
@ -357,7 +356,7 @@ internal class SendConfirmModel @Inject constructor(
val receivingAmount = checkAndCalculateSubtractedAmount( val receivingAmount = checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable = isAmountSubtractAvailable, isAmountSubtractAvailable = isAmountSubtractAvailable,
cryptoCurrencyStatus = cryptoCurrencyStatus, cryptoCurrencyStatus = getCurrencyStatusForFeePayment(),
amountValue = amountValue, amountValue = amountValue,
feeValue = feeValue, feeValue = feeValue,
reduceAmountBy = confirmData.reduceAmountBy.orZero(), reduceAmountBy = confirmData.reduceAmountBy.orZero(),
@ -392,7 +391,9 @@ internal class SendConfirmModel @Inject constructor(
private suspend fun sendTransaction(txData: TransactionData.Uncompiled) { private suspend fun sendTransaction(txData: TransactionData.Uncompiled) {
val feeExtended = feeUMV2?.feeExtraInfo?.transactionFeeExtended val feeExtended = feeUMV2?.feeExtraInfo?.transactionFeeExtended
val result = if (feeExtended != null) { val isFeeInTokenCurrency = feeExtended?.transactionFee?.normal is Fee.Ethereum.TokenCurrency
val result = if (isFeeInTokenCurrency) {
createAndSendGaslessTransactionUseCase( createAndSendGaslessTransactionUseCase(
userWallet = userWallet, userWallet = userWallet,
transactionData = txData, transactionData = txData,
@ -439,6 +440,16 @@ internal class SendConfirmModel @Inject constructor(
) )
} }
private fun getCurrencyStatusForFeePayment(): CryptoCurrencyStatus {
val feeExtended = feeUMV2?.feeExtraInfo?.transactionFeeExtended
val isFeeInTokenCurrency = feeExtended?.transactionFee?.normal is Fee.Ethereum.TokenCurrency
return if (isFeeInTokenCurrency) {
feeUMV2?.feeExtraInfo?.feeCryptoCurrencyStatus ?: cryptoCurrencyStatus
} else {
params.feeCryptoCurrencyStatus
}
}
private fun addTokenToWalletIfNeeded() { private fun addTokenToWalletIfNeeded() {
if (cryptoCurrency !is CryptoCurrency.Token) return if (cryptoCurrency !is CryptoCurrency.Token) return
val wallets = destinationUM?.wallets ?: return val wallets = destinationUM?.wallets ?: return
@ -509,6 +520,7 @@ internal class SendConfirmModel @Inject constructor(
isIgnoreReduce = confirmData.isIgnoreReduce, isIgnoreReduce = confirmData.isIgnoreReduce,
fee = confirmData.fee, fee = confirmData.fee,
feeError = confirmData.feeError, feeError = confirmData.feeError,
feeCryptoCurrencyStatus = getCurrencyStatusForFeePayment(),
), ),
) )
_uiState.update { state -> _uiState.update { state ->
@ -610,9 +622,23 @@ internal class SendConfirmModel @Inject constructor(
override fun onFeeResult(feeSelectorUM: FeeSelectorUMRedesigned) { override fun onFeeResult(feeSelectorUM: FeeSelectorUMRedesigned) {
sendIdleTimer = SystemClock.elapsedRealtime() sendIdleTimer = SystemClock.elapsedRealtime()
_uiState.update { it.copy(feeSelectorUM = feeSelectorUM) } _uiState.update { it.copy(feeSelectorUM = feeSelectorUM) }
updateAmountSubtractAvailability()
updateConfirmNotifications() updateConfirmNotifications()
} }
private fun updateAmountSubtractAvailability() {
modelScope.launch {
val isGaslessEthTx =
feeUMV2?.feeExtraInfo?.transactionFeeExtended?.transactionFee?.normal is Fee.Ethereum.TokenCurrency
isAmountSubtractAvailable =
isAmountSubtractAvailableUseCase(
userWalletId = userWallet.walletId,
currency = cryptoCurrency,
isGaslessEthTx = isGaslessEthTx,
).getOrElse { false }
}
}
private companion object { private companion object {
const val CHECK_FEE_UPDATE_DELAY = 10_000L const val CHECK_FEE_UPDATE_DELAY = 10_000L
} }

View file

@ -102,7 +102,6 @@ internal class NFTSendConfirmComponent @AssistedInject constructor(
analyticsCategoryName = params.analyticsCategoryName, analyticsCategoryName = params.analyticsCategoryName,
userWalletId = params.userWallet.walletId, userWalletId = params.userWallet.walletId,
cryptoCurrencyStatus = params.cryptoCurrencyStatus, cryptoCurrencyStatus = params.cryptoCurrencyStatus,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
appCurrency = params.appCurrency, appCurrency = params.appCurrency,
callback = model, callback = model,
notificationData = SendNotificationsComponent.Params.NotificationData( notificationData = SendNotificationsComponent.Params.NotificationData(
@ -113,6 +112,7 @@ internal class NFTSendConfirmComponent @AssistedInject constructor(
isIgnoreReduce = false, isIgnoreReduce = false,
fee = model.confirmData.fee, fee = model.confirmData.fee,
feeError = model.confirmData.feeError, feeError = model.confirmData.feeError,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus,
), ),
), ),
) )

View file

@ -350,6 +350,7 @@ internal class NFTSendConfirmModel @Inject constructor(
isIgnoreReduce = false, isIgnoreReduce = false,
fee = confirmData.fee, fee = confirmData.fee,
feeError = confirmData.feeError, feeError = confirmData.feeError,
feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus, // TODO change when gasless implement
), ),
) )
} }

View file

@ -79,7 +79,7 @@ internal class NotificationsModel @Inject constructor(
private val analyticsCategoryName = params.analyticsCategoryName private val analyticsCategoryName = params.analyticsCategoryName
private val userWalletId = params.userWalletId private val userWalletId = params.userWalletId
private val cryptoCurrencyStatus = params.cryptoCurrencyStatus private val cryptoCurrencyStatus = params.cryptoCurrencyStatus
private val feeCryptoCurrencyStatus = params.feeCryptoCurrencyStatus private val feeCryptoCurrencyStatus = params.notificationData.feeCryptoCurrencyStatus
private val currency = cryptoCurrencyStatus.currency private val currency = cryptoCurrencyStatus.currency
private val appCurrency = params.appCurrency private val appCurrency = params.appCurrency
@ -92,7 +92,10 @@ internal class NotificationsModel @Inject constructor(
init { init {
subscribeToNotificationUpdateTrigger() subscribeToNotificationUpdateTrigger()
checkIfSubtractAvailable() modelScope.launch {
checkIfSubtractAvailable()
buildNotifications()
}
incrementNotificationsShowCount() incrementNotificationsShowCount()
} }
@ -102,11 +105,12 @@ internal class NotificationsModel @Inject constructor(
.launchIn(modelScope) .launchIn(modelScope)
} }
private fun checkIfSubtractAvailable() { private suspend fun checkIfSubtractAvailable() {
modelScope.launch { isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(
isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(userWalletId, currency).getOrElse { false } userWalletId = userWalletId,
buildNotifications() currency = currency,
} isGaslessEthTx = notificationData.fee is Fee.Ethereum.TokenCurrency,
).getOrElse { false }
} }
private fun incrementNotificationsShowCount() { private fun incrementNotificationsShowCount() {
@ -115,16 +119,19 @@ internal class NotificationsModel @Inject constructor(
} }
} }
private suspend fun updateState(data: NotificationData) { private fun updateState(data: NotificationData) {
notificationData = data notificationData = data
buildNotifications() modelScope.launch {
checkIfSubtractAvailable()
buildNotifications()
}
} }
private suspend fun buildNotifications() = with(notificationData) { private suspend fun buildNotifications() = with(notificationData) {
val feeValue = fee?.amount?.value val feeValue = fee?.amount?.value
val sendingAmount = checkAndCalculateSubtractedAmount( val sendingAmount = checkAndCalculateSubtractedAmount(
isAmountSubtractAvailable = isAmountSubtractAvailable, isAmountSubtractAvailable = isAmountSubtractAvailable,
cryptoCurrencyStatus = cryptoCurrencyStatus, cryptoCurrencyStatus = getCurrencyStatusForFeePayment(),
amountValue = amountValue, amountValue = amountValue,
feeValue = feeValue, feeValue = feeValue,
reduceAmountBy = reduceAmountBy, reduceAmountBy = reduceAmountBy,
@ -369,6 +376,15 @@ internal class NotificationsModel @Inject constructor(
} }
} }
private fun getCurrencyStatusForFeePayment(): CryptoCurrencyStatus {
val isFeeInTokenCurrency = notificationData.fee is Fee.Ethereum.TokenCurrency
return if (isFeeInTokenCurrency) {
notificationData.feeCryptoCurrencyStatus
} else {
cryptoCurrencyStatus
}
}
companion object { companion object {
const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3 const val TRON_FEE_NOTIFICATION_MAX_SHOW_COUNT = 3
} }

View file

@ -107,7 +107,6 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
analyticsCategoryName = params.analyticsCategoryName, analyticsCategoryName = params.analyticsCategoryName,
userWalletId = params.userWallet.walletId, userWalletId = params.userWallet.walletId,
cryptoCurrencyStatus = model.primaryCurrencyStatus, cryptoCurrencyStatus = model.primaryCurrencyStatus,
feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
appCurrency = params.appCurrency, appCurrency = params.appCurrency,
callback = model, callback = model,
notificationData = SendNotificationsComponent.Params.NotificationData( notificationData = SendNotificationsComponent.Params.NotificationData(
@ -121,6 +120,7 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
isIgnoreReduce = model.confirmData.isIgnoreReduce, isIgnoreReduce = model.confirmData.isIgnoreReduce,
fee = model.confirmData.fee, fee = model.confirmData.fee,
feeError = model.confirmData.feeError, feeError = model.confirmData.feeError,
feeCryptoCurrencyStatus = model.primaryFeePaidCurrencyStatus,
), ),
), ),
) )

View file

@ -357,6 +357,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
isIgnoreReduce = confirmData.isIgnoreReduce, isIgnoreReduce = confirmData.isIgnoreReduce,
fee = confirmData.fee, fee = confirmData.fee,
feeError = confirmData.feeError, feeError = confirmData.feeError,
feeCryptoCurrencyStatus = params.primaryFeePaidCurrencyStatusFlow.value,
), ),
) )
swapNotificationsUpdateTrigger.triggerUpdate( swapNotificationsUpdateTrigger.triggerUpdate(