Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-09 12:26:31 +03:00
parent 930e6ffc72
commit b75a9370ba
9 changed files with 70 additions and 55 deletions

View file

@ -6,6 +6,7 @@ import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -14,10 +15,7 @@ import com.tangem.domain.transaction.GaslessTransactionRepository
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.transaction.usecase.gasless.GetAvailableFeeTokensUseCase
import com.tangem.domain.transaction.usecase.gasless.GetFeeForGaslessUseCase
import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.domain.transaction.usecase.gasless.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -330,4 +328,22 @@ internal object TransactionDomainModule {
): IsGaslessFeeSupportedForNetwork {
return IsGaslessFeeSupportedForNetwork(gaslessTransactionRepository)
}
@Provides
@Singleton
fun provideCreateAndSendGaslessTransactionUseCase(
walletManagersFacade: WalletManagersFacade,
gaslessTransactionRepository: GaslessTransactionRepository,
getSingCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
cardSdkConfigRepository: CardSdkConfigRepository,
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
): CreateAndSendGaslessTransactionUseCase {
return CreateAndSendGaslessTransactionUseCase(
walletManagersFacade = walletManagersFacade,
getSingleCryptoCurrencyStatusUseCase = getSingCryptoCurrencyStatusUseCase,
gaslessTransactionRepository = gaslessTransactionRepository,
cardSdkConfigRepository = cardSdkConfigRepository,
getHotWalletSigner = tangemHotWalletSignerFactory::create,
)
}
}

View file

@ -11,6 +11,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.features.send.v2.api.R
import com.tangem.features.send.v2.api.entity.FeeItem.*
import kotlinx.collections.immutable.ImmutableList
@ -66,6 +67,7 @@ data class FeeExtraInfo(
val isTronToken: Boolean,
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
val availableFeeCurrencies: ImmutableList<CryptoCurrencyStatus>? = null,
val transactionFeeExtended: TransactionFeeExtended? = null,
)
sealed class FeeNonce {

View file

@ -81,12 +81,14 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
val isScreenSource = params.feeDisplaySource == FeeSelectorParams.FeeDisplaySource.Screen
val isNotSingleFee = (state as? FeeSelectorUM.Content)?.feeItems?.isSingleItem() == false
val isGaslessAvailable = state is FeeSelectorUM.Content &&
(state as FeeSelectorUM.Content).feeExtraInfo.transactionFeeExtended != null
FeeSelectorBlockContent(
state = state,
onReadMoreClick = model::onReadMoreClicked,
isGaslessFeatureEnabled = sendFeatureToggles.isGaslessTransactionsEnabled,
modifier = modifier
.conditional(isScreenSource && isNotSingleFee) {
.conditional(isScreenSource && (isNotSingleFee || isGaslessAvailable)) {
Modifier.clickable {
model.showFeeSelector()
}

View file

@ -32,12 +32,10 @@ import com.tangem.utils.transformer.update
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
@ -58,7 +56,6 @@ internal class FeeSelectorLogic @AssistedInject constructor(
) : FeeSelectorIntents {
private var appCurrency: AppCurrency = AppCurrency.Default
private var selectedToken: CryptoCurrencyStatus? = null
val uiState = MutableStateFlow<FeeSelectorUM>(params.state)
init {
@ -115,34 +112,7 @@ internal class FeeSelectorLogic @AssistedInject constructor(
}
override fun onTokenSelected(status: CryptoCurrencyStatus) {
if (status.currency !is CryptoCurrency.Token ||
status.currency.id == params.feeCryptoCurrencyStatus.currency.id) {
selectedToken = null
loadFee()
return
}
uiState.update(FeeSelectorTokenSelectedTransformer(status))
uiState.update { state ->
if (state is FeeSelectorUM.Content) {
state.copy(
selectedFeeItem = FeeItem.Loading,
feeItems = persistentListOf(FeeItem.Loading),
feeExtraInfo = state.feeExtraInfo.copy(
feeCryptoCurrencyStatus = status,
),
)
} else {
state
}
}
if (status.currency is CryptoCurrency.Token) {
selectedToken = status
} else {
selectedToken = null
}
loadFee()
}
@ -240,6 +210,9 @@ internal class FeeSelectorLogic @AssistedInject constructor(
private suspend fun callLoadFee(): Either<GetFeeError, LoadedFeeResult> {
val extended = params.onLoadFeeExtended
return if (extended != null && sendFeatureToggles.isGaslessTransactionsEnabled) {
val selectedToken = (uiState.value as? FeeSelectorUM.Content)?.feeExtraInfo?.feeCryptoCurrencyStatus
?.takeIf { it.currency is CryptoCurrency.Token }
extended(selectedToken).fold(
ifLeft = { error ->
if (error is GetFeeError.GaslessError) {

View file

@ -89,14 +89,11 @@ internal class FeeSelectorModel @Inject constructor(
}
fun getInitialRoute(): FeeSelectorRoute {
if (sendFeatureToggles.isGaslessTransactionsEnabled.not()) {
return FeeSelectorRoute.ChooseSpeed
}
return if (isGaslessFeeSupportedForNetwork(params.feeCryptoCurrencyStatus.currency.network)) {
FeeSelectorRoute.NetworkFee
} else {
FeeSelectorRoute.ChooseSpeed
return when {
sendFeatureToggles.isGaslessTransactionsEnabled && params.onLoadFeeExtended != null &&
isGaslessFeeSupportedForNetwork(params.feeCryptoCurrencyStatus.currency.network) ->
FeeSelectorRoute.NetworkFee
else -> FeeSelectorRoute.ChooseSpeed
}
}
}

View file

@ -38,13 +38,17 @@ internal class FeeSelectorLoadedTransformer(
} else {
null
}
val feeItems: ImmutableList<FeeItem> = feeItemsConverter.convert(
FeeItemConverter.Input(fees.transactionFee, prevCustomFee),
)
val selectedFee = when (prevState) {
is FeeSelectorUM.Content ->
feeItems.firstOrNull { it.isSameClass(prevState.selectedFeeItem) } ?: FeeItem.Loading
is FeeSelectorUM.Content -> if (prevState.selectedFeeItem is FeeItem.Loading) {
feeItems.firstOrNull { it is FeeItem.Market }
} else {
feeItems.firstOrNull { it.isSameClass(prevState.selectedFeeItem) }
} ?: FeeItem.Loading
is FeeSelectorUM.Error,
FeeSelectorUM.Loading,
-> feeItems.find { it is FeeItem.Suggested } ?: feeItems.first { it is FeeItem.Market }
@ -64,6 +68,7 @@ internal class FeeSelectorLoadedTransformer(
isTron(cryptoCurrencyStatus.currency.network.rawId),
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
availableFeeCurrencies = getAvailableFeeCurrencies(),
transactionFeeExtended = (fees as? LoadedFeeResult.Extended)?.fee,
),
feeFiatRateUM = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
FeeFiatRateUM(

View file

@ -213,7 +213,10 @@ private fun FeeContent(state: FeeSelectorUM.Content, isGaslessFeatureEnabled: Bo
.padding(start = TangemTheme.dimens.spacing4)
.testTag(FeeSelectorBlockTestTags.FEE_AMOUNT),
)
if (!state.feeItems.isSingleItem()) {
val isGaslessAvailable = isGaslessFeatureEnabled && state.feeExtraInfo.transactionFeeExtended != null
if (!state.feeItems.isSingleItem() || isGaslessAvailable) {
Icon(
modifier = Modifier
.size(width = 18.dp, height = 24.dp)

View file

@ -36,6 +36,7 @@ import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.transaction.usecase.gasless.CreateAndSendGaslessTransactionUseCase
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.features.send.v2.api.SendNotificationsComponent
@ -111,6 +112,7 @@ internal class SendConfirmModel @Inject constructor(
private val accountsFeatureToggles: AccountsFeatureToggles,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val currenciesRepository: CurrenciesRepository,
private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase,
sendBalanceUpdaterFactory: SendBalanceUpdater.Factory,
) : Model(), SendConfirmClickIntents, FeeSelectorModelCallback, SendNotificationsComponent.ModelCallback {
@ -388,11 +390,21 @@ internal class SendConfirmModel @Inject constructor(
}
private suspend fun sendTransaction(txData: TransactionData.Uncompiled) {
val result = sendTransactionUseCase(
txData = txData,
userWallet = userWallet,
network = cryptoCurrency.network,
)
val feeExtended = feeUMV2?.feeExtraInfo?.transactionFeeExtended
val result = if (feeExtended != null) {
createAndSendGaslessTransactionUseCase(
userWallet = userWallet,
transactionData = txData,
fee = feeExtended,
)
} else {
sendTransactionUseCase(
txData = txData,
userWallet = userWallet,
network = cryptoCurrency.network,
)
}
_uiState.update(SendConfirmSendingStateTransformer(isSending = false))

View file

@ -14,6 +14,7 @@ import com.tangem.features.send.v2.api.entity.CustomFeeFieldUM
import com.tangem.features.send.v2.api.subcomponents.feeSelector.utils.FeeCalculationUtils.checkExceedBalance
import com.tangem.features.send.v2.impl.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
internal class EthereumCustomFeeConverter(
@ -38,12 +39,16 @@ internal class EthereumCustomFeeConverter(
)
override fun convert(value: Fee.Ethereum): ImmutableList<CustomFeeFieldUM> {
if (value is Fee.Ethereum.TokenCurrency) {
return persistentListOf()
}
return buildList {
add(convertFeeValue(value))
addAll(
when (value) {
is Fee.Ethereum.TokenCurrency -> error("handle in [REDACTED_TASK_KEY]")
is Fee.Ethereum.TokenCurrency -> error("unreachable")
is Fee.Ethereum.EIP1559 -> eipFeeConverter.convert(value)
is Fee.Ethereum.Legacy -> legacyFeeConverter.convert(value)
},
@ -56,7 +61,7 @@ internal class EthereumCustomFeeConverter(
override fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList<CustomFeeFieldUM>): Fee.Ethereum {
return when (normalFee) {
is Fee.Ethereum.TokenCurrency -> error("handle in [REDACTED_TASK_KEY]")
is Fee.Ethereum.TokenCurrency -> error("unreachable")
is Fee.Ethereum.EIP1559 -> eipFeeConverter.convertBack(normalFee = normalFee, value = value)
is Fee.Ethereum.Legacy -> legacyFeeConverter.convertBack(normalFee = normalFee, value = value)
}
@ -64,7 +69,7 @@ internal class EthereumCustomFeeConverter(
override fun getGasLimitIndex(feeValue: Fee.Ethereum): Int {
return when (feeValue) {
is Fee.Ethereum.TokenCurrency -> error("handle in [REDACTED_TASK_KEY]")
is Fee.Ethereum.TokenCurrency -> error("unreachable")
is Fee.Ethereum.EIP1559 -> eipFeeConverter.getGasLimitIndex(feeValue)
is Fee.Ethereum.Legacy -> legacyFeeConverter.getGasLimitIndex(feeValue)
}
@ -77,7 +82,7 @@ internal class EthereumCustomFeeConverter(
value: String,
): ImmutableList<CustomFeeFieldUM> {
return when (feeValue) {
is Fee.Ethereum.TokenCurrency -> error("handle in [REDACTED_TASK_KEY]")
is Fee.Ethereum.TokenCurrency -> error("unreachable")
is Fee.Ethereum.EIP1559 -> eipFeeConverter.onValueChange(
feeValue = feeValue,
customValues = customValues,