Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-26 19:50:01 +04:00
parent 4acd7ce3b2
commit 04a21de3b6
13 changed files with 48 additions and 118 deletions

View file

@ -146,9 +146,7 @@ abstract class BaseTestCase : TestCase(
return ApplicationInjectionExecutionRule(
toggleStates = mapOf(
"SWAP_REDESIGN_ENABLED" to false,
"HOT_WALLET_ENABLED" to true,
"ACCOUNTS_FEATURE_ENABLED" to true,
"GASLESS_TRANSACTIONS_ENABLED" to true,
)
)
}

View file

@ -32,10 +32,6 @@
"name": "APP_REDESIGN_ENABLED",
"version": "undefined"
},
{
"name": "GASLESS_TRANSACTIONS_ENABLED",
"version": "5.33.0"
},
{
"name": "SWAP_MARKET_LIST_ENABLED",
"version": "5.34"

View file

@ -22,7 +22,6 @@ import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -73,12 +72,10 @@ internal object TokensDataModule {
fun provideCurrencyChecksRepository(
walletManagersFacade: WalletManagersFacade,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
sendFeatureToggles: SendFeatureToggles,
): CurrencyChecksRepository {
return DefaultCurrencyChecksRepository(
walletManagersFacade = walletManagersFacade,
coroutineDispatchers = coroutineDispatcherProvider,
sendFeatureToggles = sendFeatureToggles,
)
}

View file

@ -15,7 +15,6 @@ import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.isZero
import com.tangem.utils.extensions.orZero
@ -25,7 +24,6 @@ import java.math.BigDecimal
internal class DefaultCurrencyChecksRepository(
private val walletManagersFacade: WalletManagersFacade,
private val coroutineDispatchers: CoroutineDispatcherProvider,
private val sendFeatureToggles: SendFeatureToggles,
) : CurrencyChecksRepository {
override suspend fun getExistentialDeposit(userWalletId: UserWalletId, network: Network): BigDecimal? {
@ -67,7 +65,6 @@ internal class DefaultCurrencyChecksRepository(
}
override fun isNetworkSupportedForGaslessTx(network: Network): Boolean {
if (!sendFeatureToggles.isGaslessTransactionsEnabled) return false
val blockchain = Blockchain.fromId(network.rawId)
return blockchain.isGaslessTxSupported
}

View file

@ -13,7 +13,6 @@ import com.tangem.domain.transaction.GaslessTransactionRepository
import com.tangem.domain.transaction.models.Eip7702Authorization
import com.tangem.domain.transaction.models.GaslessSignedTransactionResult
import com.tangem.domain.transaction.models.GaslessTransactionData
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@ -26,7 +25,6 @@ class DefaultGaslessTransactionRepository(
private val gaslessTxServiceApi: GaslessTxServiceApi,
private val coroutineDispatcherProvider: CoroutineDispatcherProvider,
private val responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
private val sendFeatureToggles: SendFeatureToggles,
) : GaslessTransactionRepository {
private val supportedTokensState = MutableStateFlow<Map<Network.ID, Set<CryptoCurrency>>>(hashMapOf())
@ -131,9 +129,6 @@ class DefaultGaslessTransactionRepository(
}
override suspend fun getGaslessFeeAddresses(): Set<String> {
if (!sendFeatureToggles.isGaslessTransactionsEnabled) {
return EMPTY_ADDRESSES
}
return allAddressesMutex.withLock {
allFeeRecipientAddress.ifEmpty {
val allFeeAddresses = getAllFeeRecipientAddresses()
@ -150,6 +145,5 @@ class DefaultGaslessTransactionRepository(
private companion object {
val BASE_GAS_FOR_TRANSACTION: BigInteger = BigInteger("60000")
val EMPTY_ADDRESSES = emptySet<String>()
}
}

View file

@ -16,7 +16,6 @@ import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.error.FeeErrorResolver
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -77,13 +76,11 @@ internal object TransactionDataModule {
responseCryptoCurrenciesFactory: ResponseCryptoCurrenciesFactory,
gaslessTxServiceApi: GaslessTxServiceApi,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
sendFeatureToggles: SendFeatureToggles,
): GaslessTransactionRepository {
return DefaultGaslessTransactionRepository(
gaslessTxServiceApi = gaslessTxServiceApi,
coroutineDispatcherProvider = coroutineDispatcherProvider,
responseCryptoCurrenciesFactory = responseCryptoCurrenciesFactory,
sendFeatureToggles = sendFeatureToggles,
)
}
}

View file

@ -1,5 +1,3 @@
package com.tangem.features.send.v2.api
interface SendFeatureToggles {
val isGaslessTransactionsEnabled: Boolean
}
interface SendFeatureToggles

View file

@ -1,12 +1,6 @@
package com.tangem.features.send.v2
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.send.v2.api.SendFeatureToggles
import javax.inject.Inject
internal class DefaultSendFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) : SendFeatureToggles {
override val isGaslessTransactionsEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled("GASLESS_TRANSACTIONS_ENABLED")
}
internal class DefaultSendFeatureToggles @Inject constructor() : SendFeatureToggles

View file

@ -14,7 +14,6 @@ import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.extensions.conditional
import com.tangem.features.send.v2.api.FeeSelectorBlockComponent
import com.tangem.features.send.v2.api.FeeSelectorComponent
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.params.FeeSelectorParams
import com.tangem.features.send.v2.feeselector.model.FeeSelectorBlockModel
@ -32,7 +31,6 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
@Assisted private val params: FeeSelectorParams.FeeSelectorBlockParams,
@Assisted onResult: (feeSelectorUM: FeeSelectorUM) -> Unit,
private val feeSelectorComponentFactory: FeeSelectorComponent.Factory,
private val sendFeatureToggles: SendFeatureToggles,
) : FeeSelectorBlockComponent, AppComponentContext by appComponentContext {
private val model: FeeSelectorBlockModel = getOrCreateModel(params = params)
@ -91,7 +89,6 @@ internal class DefaultFeeSelectorBlockComponent @AssistedInject constructor(
FeeSelectorBlockContent(
state = state,
onReadMoreClick = model::onReadMoreClicked,
isGaslessFeatureEnabled = sendFeatureToggles.isGaslessTransactionsEnabled,
modifier = modifier
.conditional(isScreenSource && (isNotSingleFee || isGaslessAvailable)) {
Modifier.clickable {

View file

@ -19,7 +19,6 @@ import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
import com.tangem.domain.transaction.usecase.gasless.GetAvailableFeeTokensUseCase
import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents.NonceInserted
import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeNonce
@ -59,7 +58,6 @@ internal class FeeSelectorLogic @AssistedInject constructor(
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getAvailableFeeTokensUseCase: GetAvailableFeeTokensUseCase,
sendFeatureToggles: SendFeatureToggles,
isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
) : FeeSelectorIntents {
@ -67,8 +65,7 @@ internal class FeeSelectorLogic @AssistedInject constructor(
private val loadFeeJobHolder = JobHolder()
val uiState = MutableStateFlow(params.state)
val isGaslessEnabled = sendFeatureToggles.isGaslessTransactionsEnabled &&
params.onLoadFeeExtended != null &&
val isGaslessEnabled = params.onLoadFeeExtended != null &&
isGaslessFeeSupportedForNetwork(params.feeCryptoCurrencyStatus.currency.network) &&
params.cryptoCurrencyStatus.currency is CryptoCurrency.Token
@ -227,7 +224,7 @@ internal class FeeSelectorLogic @AssistedInject constructor(
},
)
},
ifLeft = { feeError ->
ifLeft = { _ ->
feeSelectorCheckReloadTrigger.callbackCheckResult(false)
feeSelectorAlertFactory.getFeeUnreachableErrorState { loadFee(isReload = true) }
},

View file

@ -57,7 +57,6 @@ private const val READ_MORE_TAG = "READ_MORE"
@Composable
internal fun FeeSelectorBlockContent(
state: FeeSelectorUM,
isGaslessFeatureEnabled: Boolean,
onReadMoreClick: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -80,25 +79,16 @@ internal fun FeeSelectorBlockContent(
contentDescription = null,
tint = TangemTheme.colors.icon.accent,
)
FeeSelectorDescription(
state = state,
isGaslessFeatureEnabled = isGaslessFeatureEnabled,
onReadMoreClick = onReadMoreClick,
)
FeeSelectorDescription(state = state, onReadMoreClick = onReadMoreClick)
}
}
@Composable
private fun FeeSelectorDescription(
state: FeeSelectorUM,
isGaslessFeatureEnabled: Boolean,
onReadMoreClick: () -> Unit,
modifier: Modifier = Modifier,
) {
private fun FeeSelectorDescription(state: FeeSelectorUM, onReadMoreClick: () -> Unit, modifier: Modifier = Modifier) {
Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceBetween) {
FeeSelectorStaticPart(modifier = Modifier.weight(1f), onReadMoreClick = onReadMoreClick)
when (state) {
is FeeSelectorUM.Content -> FeeContent(state, isGaslessFeatureEnabled)
is FeeSelectorUM.Content -> FeeContent(state)
is FeeSelectorUM.Loading -> FeeLoading()
is FeeSelectorUM.Error -> FeeError()
}
@ -181,17 +171,15 @@ private fun FeeLoading() {
}
@Composable
private fun FeeContent(state: FeeSelectorUM.Content, isGaslessFeatureEnabled: Boolean, modifier: Modifier = Modifier) {
private fun FeeContent(state: FeeSelectorUM.Content, modifier: Modifier = Modifier) {
val fiatRate = state.feeFiatRateUM
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
if (isGaslessFeatureEnabled) {
AuditLabel(
state = AuditLabelUM(
text = stringReference(state.selectedFeeItem.fee.amount.currencySymbol),
type = AuditLabelUM.Type.General,
),
)
}
AuditLabel(
state = AuditLabelUM(
text = stringReference(state.selectedFeeItem.fee.amount.currencySymbol),
type = AuditLabelUM.Type.General,
),
)
EllipsisText(
text = if (state.feeExtraInfo.isFeeConvertibleToFiat && fiatRate != null) {
@ -218,7 +206,7 @@ private fun FeeContent(state: FeeSelectorUM.Content, isGaslessFeatureEnabled: Bo
.testTag(FeeSelectorBlockTestTags.FEE_AMOUNT),
)
val isGaslessAvailable = isGaslessFeatureEnabled && state.feeExtraInfo.transactionFeeExtended != null
val isGaslessAvailable = state.feeExtraInfo.transactionFeeExtended != null
if (!state.feeItems.isSingleItem() || isGaslessAvailable) {
Icon(
@ -240,7 +228,6 @@ private fun FeeSelectorBlockContent_Preview(@PreviewParameter(FeeSelectorUMProvi
TangemThemePreview {
FeeSelectorBlockContent(
modifier = Modifier.fillMaxWidth(),
isGaslessFeatureEnabled = true,
state = state,
onReadMoreClick = {},
)

View file

@ -32,7 +32,6 @@ import com.tangem.feature.swap.ui.SwapSelectTokenScreen
import com.tangem.feature.swap.ui.SwapSuccessScreen
import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents
import com.tangem.features.swap.SwapComponent
import com.tangem.utils.extensions.isZero
@ -46,7 +45,6 @@ internal class DefaultSwapComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted private val params: SwapComponent.Params,
private val swapFeeSelectorBlockComponentFactory: SwapFeeSelectorBlockComponent.Factory,
private val sendFeatureToggles: SendFeatureToggles,
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
private val giveApprovalComponentFactory: GiveApprovalComponent.Factory,
) : SwapComponent, AppComponentContext by appComponentContext {
@ -58,7 +56,7 @@ internal class DefaultSwapComponent @AssistedInject constructor(
serializer = AddToPortfolioRoute.serializer(),
key = BOTTOM_SHEET_SLOT_KEY,
handleBackButton = false,
childFactory = { configuration, context -> bottomSheetChild(context) },
childFactory = { _, context -> bottomSheetChild(context) },
)
private val approvalSlot = childSlot(
@ -83,8 +81,8 @@ internal class DefaultSwapComponent @AssistedInject constructor(
)
}
val slotNavigation = SlotNavigation<FeeSelectorConfig>()
val childSlot = childSlot(
private val slotNavigation = SlotNavigation<FeeSelectorConfig>()
private val childSlot = childSlot(
source = slotNavigation,
serializer = null,
key = FEE_SELECTOR_SLOT_KEY,
@ -123,37 +121,35 @@ internal class DefaultSwapComponent @AssistedInject constructor(
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
override fun Content(modifier: Modifier) {
if (sendFeatureToggles.isGaslessTransactionsEnabled) {
val dataState by model.dataStateStateFlow.collectAsStateWithLifecycle()
val fromCryptoCurrency by remember { derivedStateOf { dataState.fromCryptoCurrency } }
val feePaidCryptoCurrency by remember { derivedStateOf { dataState.feePaidCryptoCurrency } }
val shouldHideBlock by remember {
derivedStateOf { toBigDecimalOrZero(dataState.amount).isZero() || model.uiState.isInsufficientFunds }
val dataState by model.dataStateStateFlow.collectAsStateWithLifecycle()
val fromCryptoCurrency by remember { derivedStateOf { dataState.fromCryptoCurrency } }
val feePaidCryptoCurrency by remember { derivedStateOf { dataState.feePaidCryptoCurrency } }
val shouldHideBlock by remember {
derivedStateOf { toBigDecimalOrZero(dataState.amount).isZero() || model.uiState.isInsufficientFunds }
}
LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, shouldHideBlock) {
if (shouldHideBlock) {
slotNavigation.dismiss()
return@LaunchedEffect
}
LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, shouldHideBlock) {
if (shouldHideBlock) {
slotNavigation.dismiss()
return@LaunchedEffect
}
val sendingCryptoCurrencyStatus = fromCryptoCurrency ?: run {
slotNavigation.dismiss()
return@LaunchedEffect
}
val feeCurrencyStatus = feePaidCryptoCurrency ?: run {
slotNavigation.dismiss()
return@LaunchedEffect
}
slotNavigation.activate(
FeeSelectorConfig(
sendingCurrencyStatus = sendingCryptoCurrencyStatus,
feeCurrencyStatus = feeCurrencyStatus,
),
)
val sendingCryptoCurrencyStatus = fromCryptoCurrency ?: run {
slotNavigation.dismiss()
return@LaunchedEffect
}
val feeCurrencyStatus = feePaidCryptoCurrency ?: run {
slotNavigation.dismiss()
return@LaunchedEffect
}
slotNavigation.activate(
FeeSelectorConfig(
sendingCurrencyStatus = sendingCryptoCurrencyStatus,
feeCurrencyStatus = feeCurrencyStatus,
),
)
}
val feeSelectorChildStackState by childSlot.subscribeAsState()

View file

@ -30,18 +30,11 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.HoldToConfirmButtonFeatureToggles
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.toWrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.core.ui.utils.parseBigDecimal
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
import com.tangem.feature.swap.models.SwapAlertUM
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
@ -90,6 +83,7 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.swap.analytics.StoriesEvents
import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent
import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter
import com.tangem.feature.swap.domain.SwapInteractor
import com.tangem.feature.swap.domain.TransactionFeeResult
import com.tangem.feature.swap.domain.TxFeeSealedState
@ -99,6 +93,7 @@ import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.feature.swap.models.AddToPortfolioRoute
import com.tangem.feature.swap.models.SwapAlertUM
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.models.UiActions
import com.tangem.feature.swap.models.market.SwapMarketsListBatchFlowManager
@ -110,7 +105,6 @@ import com.tangem.feature.swap.ui.StateBuilder
import com.tangem.feature.swap.utils.formatToUIRepresentation
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent
import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager
import com.tangem.features.send.v2.api.SendFeatureToggles
import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
import com.tangem.features.swap.SwapComponent
@ -167,7 +161,6 @@ internal class SwapModel @Inject constructor(
private val tangemPayWithdrawUseCase: TangemPayWithdrawUseCase,
private val iGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
private val sendFeatureToggles: SendFeatureToggles,
private val getMarketsTokenListFlowUseCase: GetMarketsTokenListFlowUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
@ -241,7 +234,7 @@ internal class SwapModel @Inject constructor(
val feeSelectorRepository = FeeSelectorRepository()
// shows currency order (direct - swap initial to selected, reversed = selected to initial)
var isOrderReversed by mutableStateOf(false)
private var isOrderReversed by mutableStateOf(false)
private val lastAmount = mutableStateOf(INITIAL_AMOUNT)
private val lastReducedBalanceBy = mutableStateOf(BigDecimal.ZERO)
private val swapRouter: SwapRouter = SwapRouter(router = router)
@ -2399,13 +2392,6 @@ internal class SwapModel @Inject constructor(
}
private fun getSelectedFeeState(): TxFeeSealedState {
if (!sendFeatureToggles.isGaslessTransactionsEnabled) {
return TxFeeSealedState.Legacy(
txFeeState = TxFeeState.Empty,
selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL,
)
}
val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content
?: return TxFeeSealedState.Legacy(
txFeeState = TxFeeState.Empty,
@ -2424,10 +2410,6 @@ internal class SwapModel @Inject constructor(
}
private fun getSelectedFee(): TxFee? {
if (!sendFeatureToggles.isGaslessTransactionsEnabled) {
return dataState.selectedFee
}
val feeStateUM = feeSelectorRepository.state.value as? FeeSelectorUM.Content ?: return null
val transactionFeeExtended = feeStateUM.feeExtraInfo.transactionFeeExtended