Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-29 15:20:33 +04:00
parent 78c4581372
commit 3069b8d32d
23 changed files with 287 additions and 20 deletions

View file

@ -52,4 +52,9 @@ internal class DefaultSwapFeatureToggles @Inject constructor(
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.AND_15715_SWAP_BEST_DEX_RATE_ENABLED,
) && isSwapIntegratedApproveEnabled
override val isHighFeeWarningEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(
toggle = FeatureToggles.TWI_1367_HIGH_FEE_WARNING_ENABLED,
)
}

View file

@ -65,6 +65,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isHotWallet
import com.tangem.domain.pay.WithdrawalResult
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
@ -173,6 +174,7 @@ internal class SwapModel @Inject constructor(
private val getSwapUiModeUseCase: GetSwapUiModeUseCase,
private val setSwapUiModeUseCase: SetSwapUiModeUseCase,
private val calculateAmountUseCase: CalculateAmountUseCase,
private val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase,
) : Model() {
private val params = paramsContainer.require<SwapComponent.Params>()
@ -1103,7 +1105,7 @@ internal class SwapModel @Inject constructor(
)
}
private fun setupLoadedState(
private suspend fun setupLoadedState(
provider: SwapProvider,
state: SwapState,
fromSwapCurrencyStatus: SwapCurrencyStatus,
@ -1127,7 +1129,7 @@ internal class SwapModel @Inject constructor(
}
}
private fun setupQuotesLoadedUiState(provider: SwapProvider, state: SwapState.QuotesLoadedState) {
private suspend fun setupQuotesLoadedUiState(provider: SwapProvider, state: SwapState.QuotesLoadedState) {
val loadedStates = dataState.getLastLoadedSuccessStates()
val additionalBadge = SwapProviderResolver.resolveBadge(
provider = provider,
@ -1136,17 +1138,26 @@ internal class SwapModel @Inject constructor(
state = state,
isSwapBestDexRateEnabled = swapFeatureToggles.isSwapBestDexRateEnabled,
)
val swapFee = getSelectedSwapFee()
uiState = stateBuilder.createQuotesLoadedState(
uiStateHolder = uiState,
quoteModel = state,
feeCryptoCurrencyStatus = dataState.feePaidCryptoCurrency,
swapProvider = provider,
additionalBadge = additionalBadge,
swapFee = getSelectedSwapFee(),
swapFee = swapFee,
feeError = feeSelectorRepository.state.value as? FeeSelectorUM.Error,
isHighNetworkFee = isHighNetworkFee(swapFee),
)
}
private suspend fun isHighNetworkFee(swapFee: SwapFee?): Boolean {
if (!swapFeatureToggles.isHighFeeWarningEnabled) return false
swapFee ?: return false
val feeAmount = swapFee.fee.amount.value ?: return false
return isHighNetworkFeeUseCase(swapFee.selectedFeeToken.currency, feeAmount)
}
private fun sendAnalyticsForNotifications(
provider: SwapProvider,
fromToken: CryptoCurrencyStatus,
@ -2077,12 +2088,14 @@ internal class SwapModel @Inject constructor(
}
analyticsEventHandler.send(SwapEvents.ProviderChosen(provider))
uiState = stateBuilder.dismissBottomSheet(uiState)
setupLoadedState(
provider = provider,
state = swapState,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
modelScope.launch {
setupLoadedState(
provider = provider,
state = swapState,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
}
}
},
onProviderFilterSelect = { filterType ->
@ -2823,12 +2836,14 @@ internal class SwapModel @Inject constructor(
}
},
)
setupLoadedState(
provider = provider,
state = swapState,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
modelScope.launch {
setupLoadedState(
provider = provider,
state = swapState,
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
toSwapCurrencyStatus = toSwapCurrencyStatus,
)
}
} else {
TangemLogger.e("loadFee: ${feeError.error}, isHidden = true")
refreshTransferUIStateIfNeeded()

View file

@ -124,6 +124,7 @@ internal class SwapNotificationsFactory(
swapFee: SwapFee?,
feeError: GetFeeError?,
appRouter: AppRouter,
isHighNetworkFee: Boolean = false,
): ImmutableList<NotificationUM> {
val warnings = buildList {
maybeAddFeeErrorNotification(feeCryptoCurrencyStatus, quoteModel, feeError)
@ -135,10 +136,17 @@ internal class SwapNotificationsFactory(
maybeAddUnableCoverFeeWarning(quoteModel, feeCryptoCurrencyStatus, appRouter)
maybeAddTransactionInProgressWarning(quoteModel)
maybeAddPriceImpactNotification(quoteModel.priceImpact)
maybeAddHighNetworkFeeWarning(isHighNetworkFee)
}
return warnings.toPersistentList()
}
private fun MutableList<NotificationUM>.maybeAddHighNetworkFeeWarning(isHighNetworkFee: Boolean) {
if (isHighNetworkFee) {
add(NotificationUM.Warning.HighNetworkFee)
}
}
private fun MutableList<NotificationUM>.maybeAddRentExemptionError(quoteModel: SwapState.QuotesLoadedState) {
quoteModel.currencyCheck?.rentWarning?.let {
add(NotificationUM.Solana.RentInfo(it))

View file

@ -577,6 +577,7 @@ internal class StateBuilder(
additionalBadge: ProviderState.AdditionalBadge,
swapFee: SwapFee?,
feeError: FeeSelectorUM.Error?,
isHighNetworkFee: Boolean,
): SwapStateHolder {
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
@ -590,6 +591,7 @@ internal class StateBuilder(
swapFee = swapFee,
feeError = feeError?.error,
appRouter = appRouter,
isHighNetworkFee = isHighNetworkFee,
)
val fromAccountTitleUM = when {

View file

@ -109,6 +109,7 @@ internal class StateBuilderSwapButtonTest {
additionalBadge = ProviderState.AdditionalBadge.Empty,
swapFee = null,
feeError = null,
isHighNetworkFee = false,
)
assertThat(result.swapButton.isEnabled).isTrue()
@ -134,6 +135,7 @@ internal class StateBuilderSwapButtonTest {
additionalBadge = ProviderState.AdditionalBadge.Empty,
swapFee = null,
feeError = null,
isHighNetworkFee = false,
)
assertThat(result.swapButton.isEnabled).isFalse()
@ -163,6 +165,7 @@ internal class StateBuilderSwapButtonTest {
additionalBadge = ProviderState.AdditionalBadge.Empty,
swapFee = null,
feeError = null,
isHighNetworkFee = false,
)
assertThat(result.swapButton.isEnabled).isFalse()
@ -187,6 +190,7 @@ internal class StateBuilderSwapButtonTest {
additionalBadge = ProviderState.AdditionalBadge.Empty,
swapFee = buildSwapFee(),
feeError = null,
isHighNetworkFee = false,
)
assertThat(result.swapButton.isEnabled).isTrue()
@ -215,6 +219,7 @@ internal class StateBuilderSwapButtonTest {
additionalBadge = ProviderState.AdditionalBadge.Empty,
swapFee = buildSwapFee(),
feeError = null,
isHighNetworkFee = false,
)
assertThat(result.swapButton.isEnabled).isFalse()

View file

@ -26,6 +26,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
import com.tangem.domain.quotes.IsHighNetworkFeeUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.stories.ShouldShowStoriesUseCase
@ -103,6 +104,7 @@ internal abstract class SwapModelTestBase {
protected val getSwapUiModeUseCase: GetSwapUiModeUseCase = mockk(relaxed = true)
protected val setSwapUiModeUseCase: SetSwapUiModeUseCase = mockk(relaxed = true)
protected val calculateAmountUseCase: CalculateAmountUseCase = mockk(relaxed = true)
protected val isHighNetworkFeeUseCase: IsHighNetworkFeeUseCase = mockk(relaxed = true)
protected val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase = mockk(relaxed = true)
protected val sendBackupProblemEmailUseCase: SendBackupProblemEmailUseCase = mockk(relaxed = true)
@ -175,6 +177,7 @@ internal abstract class SwapModelTestBase {
getSwapUiModeUseCase = getSwapUiModeUseCase,
setSwapUiModeUseCase = setSwapUiModeUseCase,
calculateAmountUseCase = calculateAmountUseCase,
isHighNetworkFeeUseCase = isHighNetworkFeeUseCase,
isWalletBackupProblematicUseCase = isWalletBackupProblematicUseCase,
sendBackupProblemEmailUseCase = sendBackupProblemEmailUseCase,
)