Updated on 2026-08-14
This commit is contained in:
commit
50fe9e603d
1919 changed files with 94940 additions and 15178 deletions
|
|
@ -11,11 +11,9 @@ android {
|
|||
|
||||
dependencies {
|
||||
/** Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
api(projects.core.decompose)
|
||||
api(projects.core.ui)
|
||||
|
||||
/** Project - Domain */
|
||||
api(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
}
|
||||
|
|
@ -4,10 +4,8 @@ interface SwapFeatureToggles {
|
|||
val isYieldSwapEnabled: Boolean
|
||||
val isSwapSwitchToTransferEnabled: Boolean
|
||||
val isSwapIntegratedApproveEnabled: Boolean
|
||||
val isSwapAbEnabled: Boolean
|
||||
val isSwapProviderFilterEnabled: Boolean
|
||||
val isSwapRateExperienceEnabled: Boolean
|
||||
val isSwapPredefinedButtonsEnabled: Boolean
|
||||
val isExpressShareButtonEnabled: Boolean
|
||||
val isSwapBestDexRateEnabled: Boolean
|
||||
val isHighFeeWarningEnabled: Boolean
|
||||
val isTronDexSwapEnabled: Boolean
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ plugins {
|
|||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
alias(deps.plugins.ksp)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
|
|
@ -18,38 +17,29 @@ dependencies {
|
|||
implementation(deps.androidx.datastore)
|
||||
|
||||
/** Project*/
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.features.swap.domain)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
api(projects.core.datasource)
|
||||
api(projects.core.utils)
|
||||
api(projects.features.swap.domain)
|
||||
implementation(projects.domain.express.models)
|
||||
|
||||
/** Network */
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
api(deps.moshi)
|
||||
implementation(deps.arrow.core)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.walletManager)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.express.models)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.txhistory)
|
||||
api(projects.domain.account)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.txhistory)
|
||||
runtimeOnly(projects.domain.account.status)
|
||||
runtimeOnly(projects.domain.wallets)
|
||||
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
/** Data */
|
||||
implementation(projects.data.common)
|
||||
api(projects.data.common)
|
||||
|
||||
/** Tangem SDKs */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
|
|||
|
|
@ -15,22 +15,19 @@ import com.tangem.datasource.api.express.models.request.PairsRequestBody
|
|||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails
|
||||
import com.tangem.datasource.api.express.models.response.SwapPair
|
||||
import com.tangem.datasource.api.express.models.response.SwapPairsWithProviders
|
||||
import com.tangem.data.common.txhistory.ExpressHistoryRepository
|
||||
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||
import com.tangem.datasource.local.converter.toEntity
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.txhistory.TxHistoryFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.feature.swap.converters.*
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.ExpressDataError
|
||||
|
|
@ -40,7 +37,6 @@ import com.tangem.feature.swap.domain.models.domain.*
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
|
|
@ -50,12 +46,10 @@ import com.tangem.datasource.api.express.models.request.LeastTokenInfo as Networ
|
|||
internal class DefaultSwapRepository(
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val errorsDataConverter: ErrorsDataConverter,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val expressHistoryDao: ExpressHistoryDao,
|
||||
private val expressHistoryRepository: ExpressHistoryRepository,
|
||||
private val txHistoryFeatureToggles: TxHistoryFeatureToggles,
|
||||
moshi: Moshi,
|
||||
) : SwapRepository {
|
||||
|
|
@ -124,98 +118,6 @@ internal class DefaultSwapRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getPairsOnly(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: LeastTokenInfo,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
isIgnoreExpress: Boolean,
|
||||
): PairsWithProviders {
|
||||
return withContext(coroutineDispatcher.io) {
|
||||
val currenciesList = filterByAssetRequirements(userWallet, currencyList)
|
||||
|
||||
if (isIgnoreExpress) {
|
||||
buildLocalPairs(initialCurrency, currenciesList)
|
||||
} else {
|
||||
fetchExpressPairs(userWallet, initialCurrency, currenciesList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLocalPairs(
|
||||
initialCurrency: LeastTokenInfo,
|
||||
currenciesList: List<NetworkLeastTokenInfo>,
|
||||
): PairsWithProviders {
|
||||
val pairs = currenciesList.map { tokenInfo ->
|
||||
SwapPairLeast(
|
||||
from = initialCurrency,
|
||||
to = LeastTokenInfo(
|
||||
contractAddress = tokenInfo.contractAddress,
|
||||
network = tokenInfo.network,
|
||||
),
|
||||
providers = emptyList(),
|
||||
)
|
||||
}
|
||||
return PairsWithProviders(pairs = pairs, allProviders = emptyList())
|
||||
}
|
||||
|
||||
private suspend fun fetchExpressPairs(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: LeastTokenInfo,
|
||||
currenciesList: List<NetworkLeastTokenInfo>,
|
||||
): PairsWithProviders {
|
||||
try {
|
||||
val initial = NetworkLeastTokenInfo(
|
||||
contractAddress = initialCurrency.contractAddress,
|
||||
network = initialCurrency.network,
|
||||
)
|
||||
|
||||
val allPairs = supervisorScope {
|
||||
val pairsDeferred = async {
|
||||
getPairsInternal(
|
||||
userWallet = userWallet,
|
||||
from = arrayListOf(initial),
|
||||
to = currenciesList,
|
||||
)
|
||||
}
|
||||
|
||||
val reversedPairsDeferred = async {
|
||||
getPairsInternal(
|
||||
userWallet = userWallet,
|
||||
from = currenciesList,
|
||||
to = arrayListOf(initial),
|
||||
)
|
||||
}
|
||||
|
||||
pairsDeferred.await().getOrThrow() + reversedPairsDeferred.await().getOrThrow()
|
||||
}
|
||||
|
||||
return swapPairInfoConverter.convert(
|
||||
SwapPairsWithProviders(
|
||||
swapPair = allPairs,
|
||||
providers = emptyList(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
if (exception is ApiResponseError.HttpException) {
|
||||
throw ExpressException(errorsDataConverter.convert(exception.errorBody.orEmpty()))
|
||||
} else {
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun filterByAssetRequirements(
|
||||
userWallet: UserWallet,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
): List<NetworkLeastTokenInfo> {
|
||||
return currencyList
|
||||
.filter { currency ->
|
||||
val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, currency)
|
||||
rampStateManager.checkAssetRequirements(requirements)
|
||||
}
|
||||
.map { currency -> leastTokenInfoConverter.convert(currency) }
|
||||
}
|
||||
|
||||
private suspend fun getPairsInternal(
|
||||
userWallet: UserWallet,
|
||||
from: List<NetworkLeastTokenInfo>,
|
||||
|
|
@ -254,9 +156,8 @@ internal class DefaultSwapRepository(
|
|||
)
|
||||
.getOrThrow()
|
||||
|
||||
val entity = response.toEntity(ownerAddress = response.fromAddress.orEmpty())
|
||||
if (txHistoryFeatureToggles.isNewTxHistoryEnabled) {
|
||||
expressHistoryDao.upsertExchanges(listOf(entity))
|
||||
expressHistoryRepository.storeExchanges(items = listOf(response))
|
||||
}
|
||||
|
||||
exchangeStatusConverter.convert(response)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.swap.di
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.common.txhistory.ExpressHistoryRepository
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.api.surveysparrow.SurveySparrowApi
|
||||
|
|
@ -10,11 +11,8 @@ import com.tangem.datasource.crypto.DataSignatureVerifier
|
|||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.txhistory.db.dao.ExpressHistoryDao
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.txhistory.TxHistoryFeatureToggles
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.feature.swap.DefaultSwapFeedbackRepository
|
||||
import com.tangem.feature.swap.DefaultSwapRepository
|
||||
import com.tangem.feature.swap.NoOpSwapFeedbackRepository
|
||||
|
|
@ -40,24 +38,20 @@ internal class SwapDataModule {
|
|||
tangemExpressApi: TangemExpressApi,
|
||||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
dataSignature: DataSignatureVerifier,
|
||||
walletManagerFacade: WalletManagersFacade,
|
||||
errorsDataConverter: ErrorsDataConverter,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
rampStateManager: RampStateManager,
|
||||
expressHistoryDao: ExpressHistoryDao,
|
||||
expressHistoryRepository: ExpressHistoryRepository,
|
||||
txHistoryFeatureToggles: TxHistoryFeatureToggles,
|
||||
): SwapRepository {
|
||||
return DefaultSwapRepository(
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
coroutineDispatcher = coroutineDispatcher,
|
||||
walletManagersFacade = walletManagerFacade,
|
||||
errorsDataConverter = errorsDataConverter,
|
||||
dataSignatureVerifier = dataSignature,
|
||||
moshi = moshi,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
rampStateManager = rampStateManager,
|
||||
expressHistoryDao = expressHistoryDao,
|
||||
expressHistoryRepository = expressHistoryRepository,
|
||||
txHistoryFeatureToggles = txHistoryFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,4 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace = "com.tangem.feature.swap.domain.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.express.models)
|
||||
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
}
|
||||
|
|
@ -23,59 +23,56 @@ dependencies {
|
|||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.swap.models)
|
||||
implementation(projects.domain.swap)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.quotes)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.express.models)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.visa.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.notifications)
|
||||
api(projects.domain.account.status)
|
||||
api(projects.domain.appCurrency)
|
||||
api(projects.domain.appCurrency.models)
|
||||
api(projects.domain.balanceHiding)
|
||||
api(projects.domain.demo)
|
||||
api(projects.domain.express.models)
|
||||
api(projects.domain.legacy)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.notifications)
|
||||
api(projects.domain.quotes)
|
||||
api(projects.domain.swap)
|
||||
api(projects.domain.swap.models)
|
||||
api(projects.domain.tokens)
|
||||
api(projects.domain.tokens.models)
|
||||
api(projects.domain.transaction)
|
||||
api(projects.domain.transaction.models)
|
||||
api(projects.domain.visa)
|
||||
api(projects.domain.visa.models)
|
||||
api(projects.domain.walletManager)
|
||||
api(projects.domain.yieldSupply)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.common.ui)
|
||||
api(projects.common.ui)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.configToggles)
|
||||
api(projects.core.abTests)
|
||||
api(projects.core.datasource)
|
||||
api(projects.core.ui)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.abTests)
|
||||
implementation(projects.core.error)
|
||||
|
||||
/** Feature Apis */
|
||||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.swap.api)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
api(projects.features.swap.api)
|
||||
implementation(projects.features.send.api)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
|
||||
/** Other Libraries **/
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(tangemDeps.blockchain)
|
||||
api(deps.arrow.core)
|
||||
api(deps.jodatime)
|
||||
api(deps.kotlin.coroutines)
|
||||
api(deps.moshi)
|
||||
api(tangemDeps.blockchain)
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(deps.moshi)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
|
||||
/** Test */
|
||||
testRuntimeOnly(projects.domain.account)
|
||||
testRuntimeOnly(projects.domain.card)
|
||||
testRuntimeOnly(projects.domain.staking)
|
||||
testRuntimeOnly(projects.domain.wallets)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(projects.test.core)
|
||||
}
|
||||
|
|
@ -1,30 +1,9 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.ksp)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.feature.swap.domain.models"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Domain */
|
||||
api(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.datasource)
|
||||
|
||||
/** Other Libraries **/
|
||||
implementation(tangemDeps.blockchain)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.jodatime)
|
||||
ksp(deps.moshi.kotlin.codegen)
|
||||
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.LeastTokenInfo
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapPairLeast
|
||||
|
||||
class GetAvailablePairsUseCase(
|
||||
private val swapRepository: SwapRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: LeastTokenInfo,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): List<SwapPairLeast> {
|
||||
return swapRepository.getPairsOnly(
|
||||
userWallet = userWallet,
|
||||
initialCurrency = initialCurrency,
|
||||
currencyList = currencies,
|
||||
isIgnoreExpress = true,
|
||||
).pairs
|
||||
}
|
||||
}
|
||||
|
|
@ -3,17 +3,14 @@ package com.tangem.feature.swap.domain
|
|||
import com.tangem.core.abtests.manager.ABTestsManager
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
||||
class GetSwapUiModeUseCase(
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val swapRepository: SwapRepository,
|
||||
private val abTestsManager: ABTestsManager,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): SwapUIMode {
|
||||
if (!swapFeatureToggles.isSwapAbEnabled) return SwapUIMode.Detailed
|
||||
swapRepository.getStoredSwapUiMode()?.let { return it }
|
||||
val variant = abTestsManager.getValue(KEY_SWAP_FORM_VARIANT, SwapUIMode.Detailed.key)
|
||||
TangemLogger.d("Get $variant Swap AB variant from Amplitude as default value")
|
||||
|
|
|
|||
|
|
@ -16,14 +16,6 @@ interface SwapRepository {
|
|||
currencyList: List<CryptoCurrency>,
|
||||
): PairsWithProviders
|
||||
|
||||
/** Express getPairs request variant without providers request */
|
||||
suspend fun getPairsOnly(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: LeastTokenInfo,
|
||||
currencyList: List<CryptoCurrency>,
|
||||
isIgnoreExpress: Boolean = false,
|
||||
): PairsWithProviders
|
||||
|
||||
suspend fun getExchangeStatus(
|
||||
userWallet: UserWallet?,
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -39,11 +39,9 @@ internal class SwapDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetSwapUiModeUseCase(
|
||||
swapFeatureToggles: SwapFeatureToggles,
|
||||
swapRepository: SwapRepository,
|
||||
abTestsManager: ABTestsManager,
|
||||
): GetSwapUiModeUseCase = GetSwapUiModeUseCase(
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
swapRepository = swapRepository,
|
||||
abTestsManager = abTestsManager,
|
||||
)
|
||||
|
|
@ -77,6 +75,7 @@ internal class SwapDomainModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
@SwapDexGasLimit patchEthGasLimitForSwap: PatchEthGasLimitForSwap,
|
||||
wrapYieldSwapCallDataWithUpgradeUseCase: WrapYieldSwapCallDataWithUpgradeUseCase,
|
||||
swapFeatureToggles: SwapFeatureToggles,
|
||||
): DexSwapFeeCalculator = DexSwapFeeCalculator(
|
||||
getFeeUseCase = getFeeUseCase,
|
||||
getEthSpecificFeeUseCase = getEthSpecificFeeUseCase,
|
||||
|
|
@ -85,6 +84,7 @@ internal class SwapDomainModule {
|
|||
walletManagersFacade = walletManagersFacade,
|
||||
patchEthGasLimitForSwap = patchEthGasLimitForSwap,
|
||||
wrapYieldSwapCallDataWithUpgradeUseCase = wrapYieldSwapCallDataWithUpgradeUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -32,9 +32,11 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.yield.supply.usecase.WrapYieldSwapCallDataWithUpgradeUseCase
|
||||
import com.tangem.feature.swap.domain.models.domain.ExpressTransactionModel
|
||||
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.lib.crypto.BlockchainUtils.SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
|
@ -66,6 +68,7 @@ class DexSwapFeeCalculator(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val patchEthGasLimitForSwap: PatchEthGasLimitForSwap,
|
||||
private val wrapYieldSwapCallDataWithUpgradeUseCase: WrapYieldSwapCallDataWithUpgradeUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend fun calculate(
|
||||
|
|
@ -93,6 +96,11 @@ class DexSwapFeeCalculator(
|
|||
transaction = transaction,
|
||||
otherNativeFee = otherNativeFee,
|
||||
)
|
||||
isTron(networkRawId) && swapFeatureToggles.isTronDexSwapEnabled -> calculateTronFee(
|
||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||
transaction = transaction,
|
||||
otherNativeFee = otherNativeFee,
|
||||
)
|
||||
else -> calculateEvmFee(
|
||||
fromSwapCurrencyStatus = fromSwapCurrencyStatus,
|
||||
transaction = transaction,
|
||||
|
|
@ -192,6 +200,47 @@ class DexSwapFeeCalculator(
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* TRON DEX swap fee ([REDACTED_TASK_KEY], gated by [SwapFeatureToggles.isTronDexSwapEnabled]).
|
||||
*
|
||||
* The swap arrives in EVM format — a native-value contract call to the router [txTo] carrying
|
||||
* raw [txData]. TRON has no EVM gas, so unlike [calculateEvmFee] there is no gas-limit bump and
|
||||
* no eth-specific fallback: the fee (bandwidth + energy) is estimated directly for a
|
||||
* [TransactionData.Uncompiled]. The `TronTransactionExtras` produced from [txData] is what makes
|
||||
* the SDK build (and energy-estimate) the tx as a smart-contract call rather than a plain send.
|
||||
*/
|
||||
private suspend fun Raise<GetFeeError>.calculateTronFee(
|
||||
fromSwapCurrencyStatus: SwapCurrencyStatus,
|
||||
transaction: ExpressTransactionModel.DEX,
|
||||
otherNativeFee: BigDecimal,
|
||||
): DexFeeResult {
|
||||
val network = fromSwapCurrencyStatus.currency.network
|
||||
val txValue = transaction.txValue ?: raise(GetFeeError.UnknownError)
|
||||
val extras = createTransactionExtrasUseCase(
|
||||
data = transaction.txData,
|
||||
network = network,
|
||||
).getOrNull() ?: raise(GetFeeError.UnknownError)
|
||||
|
||||
val transactionData = TransactionData.Uncompiled(
|
||||
amount = createNativeAmountForDex(txValue, network),
|
||||
destinationAddress = transaction.txTo,
|
||||
fee = null,
|
||||
sourceAddress = transaction.txFrom,
|
||||
extras = extras,
|
||||
)
|
||||
val fee = getFeeUseCase(
|
||||
transactionData = transactionData,
|
||||
network = network,
|
||||
userWallet = fromSwapCurrencyStatus.userWallet,
|
||||
).getOrNull() ?: raise(GetFeeError.UnknownError)
|
||||
|
||||
return DexFeeResult(
|
||||
transactionFee = TransactionFeeResult.Loaded(fee),
|
||||
otherNativeFee = otherNativeFee,
|
||||
gas = null,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Yield-mode DEX fee path: routes the swap through the user's yield module proxy.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.google.common.truth.Truth.assertThat
|
|||
import com.tangem.core.abtests.manager.ABTestsManager
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.feature.swap.domain.models.domain.SwapUIMode
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
|
|
@ -13,32 +12,17 @@ import org.junit.jupiter.api.Test
|
|||
|
||||
internal class GetSwapUiModeUseCaseTest {
|
||||
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk()
|
||||
private val swapRepository: SwapRepository = mockk()
|
||||
private val abTestsManager: ABTestsManager = mockk()
|
||||
|
||||
private val sut = GetSwapUiModeUseCase(
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
swapRepository = swapRepository,
|
||||
abTestsManager = abTestsManager,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature toggle is disabled WHEN invoke THEN returns Detailed without reading repository or AB tests`() =
|
||||
fun `GIVEN repository has Detailed WHEN invoke THEN returns Detailed without reading AB tests`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns false
|
||||
|
||||
val actual = sut.invoke()
|
||||
|
||||
assertThat(actual).isEqualTo(SwapUIMode.Detailed)
|
||||
coVerify(exactly = 0) { swapRepository.getStoredSwapUiMode() }
|
||||
coVerify(exactly = 0) { abTestsManager.getValue(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository has Detailed WHEN invoke THEN returns Detailed without reading AB tests`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns SwapUIMode.Detailed
|
||||
|
||||
val actual = sut.invoke()
|
||||
|
|
@ -48,9 +32,8 @@ internal class GetSwapUiModeUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository has Simple WHEN invoke THEN returns Simple without reading AB tests`() =
|
||||
fun `GIVEN repository has Simple WHEN invoke THEN returns Simple without reading AB tests`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns SwapUIMode.Simple
|
||||
|
||||
val actual = sut.invoke()
|
||||
|
|
@ -60,9 +43,8 @@ internal class GetSwapUiModeUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository empty and AB returns detailed WHEN invoke THEN returns Detailed`() =
|
||||
fun `GIVEN repository empty and AB returns detailed WHEN invoke THEN returns Detailed`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns null
|
||||
coEvery { abTestsManager.getValue("swap_form_variant", "detailed") } returns "detailed"
|
||||
|
||||
|
|
@ -73,8 +55,7 @@ internal class GetSwapUiModeUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository empty and AB returns simple WHEN invoke THEN returns Simple`() = runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
fun `GIVEN repository empty and AB returns simple WHEN invoke THEN returns Simple`() = runTest {
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns null
|
||||
coEvery { abTestsManager.getValue("swap_form_variant", "detailed") } returns "simple"
|
||||
|
||||
|
|
@ -85,9 +66,8 @@ internal class GetSwapUiModeUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository empty and AB returns SIMPLE uppercase WHEN invoke THEN returns Simple`() =
|
||||
fun `GIVEN repository empty and AB returns SIMPLE uppercase WHEN invoke THEN returns Simple`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns null
|
||||
coEvery { abTestsManager.getValue("swap_form_variant", "detailed") } returns "SIMPLE"
|
||||
|
||||
|
|
@ -97,9 +77,8 @@ internal class GetSwapUiModeUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository empty and AB returns unknown variant WHEN invoke THEN returns Detailed`() =
|
||||
fun `GIVEN repository empty and AB returns unknown variant WHEN invoke THEN returns Detailed`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns null
|
||||
coEvery { abTestsManager.getValue("swap_form_variant", "detailed") } returns "something_else"
|
||||
|
||||
|
|
@ -109,9 +88,8 @@ internal class GetSwapUiModeUseCaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle enabled and repository empty and AB returns empty string WHEN invoke THEN returns Detailed`() =
|
||||
fun `GIVEN repository empty and AB returns empty string WHEN invoke THEN returns Detailed`() =
|
||||
runTest {
|
||||
coEvery { swapFeatureToggles.isSwapAbEnabled } returns true
|
||||
coEvery { swapRepository.getStoredSwapUiMode() } returns null
|
||||
coEvery { abTestsManager.getValue("swap_form_variant", "detailed") } returns ""
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.tangem.feature.swap.domain.buildSwapCurrencyStatus
|
|||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.ExpressTransactionModel
|
||||
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
|
|
@ -49,6 +50,7 @@ internal class DexSwapFeeCalculatorTest {
|
|||
private val ethNetwork = Blockchain.Ethereum.toNetworkId()
|
||||
private val solanaNetwork = Blockchain.Solana.toNetworkId()
|
||||
private val bitcoinNetwork = Blockchain.Bitcoin.toNetworkId()
|
||||
private val tronNetwork = Blockchain.Tron.toNetworkId()
|
||||
|
||||
private val getFeeUseCase: GetFeeUseCase = mockk(relaxed = true)
|
||||
private val getEthSpecificFeeUseCase: GetEthSpecificFeeUseCase = mockk(relaxed = true)
|
||||
|
|
@ -56,6 +58,7 @@ internal class DexSwapFeeCalculatorTest {
|
|||
private val createTransactionExtrasUseCase: CreateTransactionDataExtrasUseCase = mockk(relaxed = true)
|
||||
private val walletManagersFacade: WalletManagersFacade = mockk(relaxed = true)
|
||||
private val wrapYieldSwapCallDataWithUpgradeUseCase: WrapYieldSwapCallDataWithUpgradeUseCase = mockk(relaxed = true)
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true)
|
||||
|
||||
private val dexBump = PatchEthGasLimitForSwap(percentage = PatchEthGasLimitForSwap.DEX_PERCENTAGE)
|
||||
|
||||
|
|
@ -68,6 +71,7 @@ internal class DexSwapFeeCalculatorTest {
|
|||
walletManagersFacade = walletManagersFacade,
|
||||
patchEthGasLimitForSwap = dexBump,
|
||||
wrapYieldSwapCallDataWithUpgradeUseCase = wrapYieldSwapCallDataWithUpgradeUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -900,6 +904,73 @@ internal class DexSwapFeeCalculatorTest {
|
|||
gasPrice = BigInteger.valueOf(20_000_000_000),
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TRON ([REDACTED_TASK_KEY]) — gated by SwapFeatureToggles.isTronDexSwapEnabled
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
fun `GIVEN tron toggle ON WHEN calculate THEN tron fee path estimates fee with no evm gas`() = runTest {
|
||||
// Arrange
|
||||
every { swapFeatureToggles.isTronDexSwapEnabled } returns true
|
||||
val fromStatus = buildSwapCurrencyStatus(networkRawId = tronNetwork, isCoin = true)
|
||||
val transaction = buildDex(
|
||||
txValue = "5000000", // 5 TRX
|
||||
txTo = "TRouterAddress",
|
||||
txFrom = "TSenderAddress",
|
||||
txData = "a9059cbb",
|
||||
)
|
||||
val capturedTxData = slot<TransactionData>()
|
||||
coEvery {
|
||||
getFeeUseCase.invoke(userWallet = any(), network = any(), transactionData = capture(capturedTxData))
|
||||
} returns mockk<TransactionFee.Single>(relaxed = true).right()
|
||||
|
||||
// Act
|
||||
val result = sut.calculate(fromStatus, transaction)
|
||||
|
||||
// Assert
|
||||
assertThat(result.isRight()).isTrue()
|
||||
assertThat(capturedTxData.captured).isInstanceOf(TransactionData.Uncompiled::class.java)
|
||||
result.onRight { dexFeeResult ->
|
||||
// TRON path carries no EVM gas and applies no gas-limit bump.
|
||||
assertThat(dexFeeResult.gas).isNull()
|
||||
}
|
||||
// No eth-specific fallback on the TRON path.
|
||||
coVerify(exactly = 0) {
|
||||
getEthSpecificFeeUseCase.invoke(
|
||||
userWallet = any(),
|
||||
cryptoCurrency = any(),
|
||||
gasLimit = any(),
|
||||
gasPrice = any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN tron toggle OFF WHEN calculate THEN evm path is used and carries gas`() = runTest {
|
||||
// Arrange
|
||||
every { swapFeatureToggles.isTronDexSwapEnabled } returns false
|
||||
val fromStatus = buildSwapCurrencyStatus(networkRawId = tronNetwork, isCoin = true)
|
||||
val transaction = buildDex(
|
||||
txValue = "5000000",
|
||||
txTo = "TRouterAddress",
|
||||
txFrom = "TSenderAddress",
|
||||
txData = "a9059cbb",
|
||||
)
|
||||
coEvery {
|
||||
getFeeUseCase.invoke(userWallet = any(), network = any(), transactionData = any())
|
||||
} returns mockk<TransactionFee.Single>(relaxed = true).right()
|
||||
|
||||
// Act
|
||||
val result = sut.calculate(fromStatus, transaction)
|
||||
|
||||
// Assert
|
||||
assertThat(result.isRight()).isTrue()
|
||||
result.onRight { dexFeeResult ->
|
||||
// Toggle off → TRON falls through to the EVM branch, which carries the tx gas.
|
||||
assertThat(dexFeeResult.gas).isEqualTo(transaction.gas)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDex(
|
||||
txData: String = "dGVzdA==",
|
||||
txValue: String? = "0",
|
||||
|
|
|
|||
|
|
@ -12,107 +12,102 @@ android {
|
|||
}
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.commonFeatures.api)
|
||||
api(projects.features.approval.api)
|
||||
api(projects.features.commonFeatures.api)
|
||||
api(projects.features.send.api)
|
||||
api(projects.features.swap.api)
|
||||
api(projects.features.swap.domain)
|
||||
implementation(projects.features.send.impl)
|
||||
|
||||
/** Core modules */
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.core.decompose) // For Route supertype
|
||||
api(projects.core.analytics)
|
||||
api(projects.core.analytics.models)
|
||||
api(projects.core.configToggles)
|
||||
api(projects.core.datasource)
|
||||
api(projects.core.decompose)
|
||||
api(projects.core.navigation)
|
||||
api(projects.core.ui)
|
||||
api(projects.core.utils)
|
||||
implementation(projects.core.error)
|
||||
implementation(projects.common.uiMarkets)
|
||||
implementation(projects.common.uiCharts)
|
||||
api(projects.common.routing)
|
||||
api(projects.common.ui)
|
||||
|
||||
/** Data modules */
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Domain modules **/
|
||||
implementation(projects.domain.models)
|
||||
api(projects.domain.account.status)
|
||||
api(projects.domain.appCurrency)
|
||||
api(projects.domain.appCurrency.models)
|
||||
api(projects.domain.balanceHiding)
|
||||
api(projects.domain.card)
|
||||
api(projects.domain.express.models)
|
||||
api(projects.domain.feedback)
|
||||
api(projects.domain.legacy)
|
||||
api(projects.domain.models)
|
||||
api(projects.domain.settings)
|
||||
api(projects.domain.stories)
|
||||
api(projects.domain.swap)
|
||||
api(projects.domain.swap.models)
|
||||
api(projects.domain.tokens)
|
||||
api(projects.domain.transaction)
|
||||
api(projects.domain.transaction.models)
|
||||
api(projects.domain.txhistory)
|
||||
api(projects.domain.visa)
|
||||
api(projects.domain.wallets)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.appCurrency)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.feedback.models)
|
||||
implementation(projects.domain.stories)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.stories.models)
|
||||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.express.models)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.swap)
|
||||
implementation(projects.domain.swap.models)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.swap.domain)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.swap.api)
|
||||
implementation(projects.features.send.api)
|
||||
implementation(projects.features.send.impl)
|
||||
implementation(projects.features.feed.api)
|
||||
implementation(projects.domain.visa.models)
|
||||
runtimeOnly(projects.domain.staking)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.androidx.annotation)
|
||||
implementation(deps.androidx.appCompat)
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.lifecycle.viewModel.ktx)
|
||||
implementation(deps.androidx.browser)
|
||||
implementation(deps.lifecycle.compose)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.compose.foundation)
|
||||
api(deps.compose.coil)
|
||||
api(deps.compose.foundation)
|
||||
api(deps.decompose.ext.compose)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.constraintLayout)
|
||||
|
||||
/** Api */
|
||||
implementation(projects.features.swap.api)
|
||||
implementation(projects.features.tokendetails.api)
|
||||
implementation(projects.features.approval.api)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
api(deps.arrow.core)
|
||||
api(deps.kotlin.coroutines)
|
||||
api(deps.kotlin.immutable.collections)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.decompose.ext.compose)
|
||||
implementation(deps.firebase.perf) {
|
||||
exclude(group = "com.google.firebase", module = "protolite-well-known-types")
|
||||
exclude(group = "com.google.protobuf", module = "protobuf-javalite")
|
||||
}
|
||||
|
||||
/** Tangem libs */
|
||||
implementation(tangemDeps.blockchain)
|
||||
api(tangemDeps.blockchain)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Test */
|
||||
testImplementation(projects.domain.core)
|
||||
testImplementation(projects.domain.legacy)
|
||||
testImplementation(projects.libs.blockchainSdk)
|
||||
testImplementation(projects.test.core)
|
||||
}
|
||||
|
|
@ -24,25 +24,6 @@ internal class DefaultSwapFeatureToggles @Inject constructor(
|
|||
toggle = FeatureToggles.AND_15120_SWAP_INTEGRATED_APPROVE,
|
||||
)
|
||||
|
||||
override val isSwapAbEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.SWAP_AB_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapProviderFilterEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15009_SWAP_PROVIDER_FILTER_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapRateExperienceEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15103_SWAP_RATE_EXPERIENCE_ENABLED,
|
||||
)
|
||||
|
||||
override val isSwapPredefinedButtonsEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15122_SWAP_PREDEFINED_BUTTONS_ENABLED,
|
||||
)
|
||||
override val isExpressShareButtonEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_15489_EXPRESS_SHARE_BUTTON_ENABLED,
|
||||
|
|
@ -52,4 +33,14 @@ 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,
|
||||
)
|
||||
|
||||
override val isTronDexSwapEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(
|
||||
toggle = FeatureToggles.AND_16080_TRON_DEX_SWAP_ENABLED,
|
||||
)
|
||||
}
|
||||
|
|
@ -12,10 +12,10 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.domain.transaction.models.TransactionFeeExtended
|
||||
import com.tangem.features.send.api.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorBlockComponent
|
||||
import com.tangem.features.send.api.analytics.CommonSendAnalyticEvents
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.params.FeeSelectorParams
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.params.FeeSelectorParams
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -109,8 +110,8 @@ import com.tangem.features.approval.api.SelectApprovalTypeComponent
|
|||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
|
||||
import com.tangem.features.send.api.entity.FeeItem
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeItem
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.swap.SwapComponent
|
||||
|
|
@ -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>()
|
||||
|
|
@ -208,7 +210,6 @@ internal class SwapModel @Inject constructor(
|
|||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
isAccountsModeProvider = Provider { isAccountsMode },
|
||||
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
|
||||
|
|
@ -1102,7 +1103,7 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun setupLoadedState(
|
||||
private suspend fun setupLoadedState(
|
||||
provider: SwapProvider,
|
||||
state: SwapState,
|
||||
fromSwapCurrencyStatus: SwapCurrencyStatus,
|
||||
|
|
@ -1126,7 +1127,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,
|
||||
|
|
@ -1135,17 +1136,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,
|
||||
|
|
@ -2078,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 ->
|
||||
|
|
@ -2824,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()
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import com.tangem.lib.crypto.BlockchainUtils
|
|||
import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -125,6 +126,7 @@ internal class SwapNotificationsFactory(
|
|||
swapFee: SwapFee?,
|
||||
feeError: GetFeeError?,
|
||||
appRouter: AppRouter,
|
||||
isHighNetworkFee: Boolean = false,
|
||||
): ImmutableList<NotificationUM> {
|
||||
val warnings = buildList {
|
||||
maybeAddFeeErrorNotification(feeCryptoCurrencyStatus, quoteModel, feeError)
|
||||
|
|
@ -136,10 +138,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))
|
||||
|
|
@ -208,7 +217,7 @@ internal class SwapNotificationsFactory(
|
|||
actions.onReduceToAmount(amount.copy(value = reduceTo))
|
||||
},
|
||||
)
|
||||
if (!isCardano) {
|
||||
if (!isCardano && !feeValue.isZero()) {
|
||||
addDustWarningNotification(
|
||||
dustValue = quoteModel.currencyCheck?.dustValue,
|
||||
feeValue = feeValue,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ internal data class SwapStateHolder(
|
|||
val predefinedButtons: ImmutableList<PredefinedPercentButtonUM> = persistentListOf(),
|
||||
val tosState: TosState? = null,
|
||||
val swapUIMode: SwapUIMode = SwapUIMode.Detailed,
|
||||
val shouldShowAbMenu: Boolean = false,
|
||||
|
||||
val transferFooter: TextReference? = null,
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ import com.tangem.feature.swap.models.SwapButton.Mode
|
|||
import com.tangem.feature.swap.models.states.*
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.utils.formatToUIRepresentation
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
|
|
@ -73,7 +72,6 @@ internal class StateBuilder(
|
|||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val isAccountsModeProvider: Provider<Boolean>,
|
||||
private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val appRouter: AppRouter,
|
||||
) {
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
|
@ -124,7 +122,6 @@ internal class StateBuilder(
|
|||
onSwapUIModeChange = actions.onSwapUIModeChange,
|
||||
onSwapTypeMenuOpened = actions.onSwapTypeMenuOpened,
|
||||
onTronBannerShown = actions.onTronBannerShown,
|
||||
shouldShowAbMenu = swapFeatureToggles.isSwapAbEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -577,6 +574,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 +588,7 @@ internal class StateBuilder(
|
|||
swapFee = swapFee,
|
||||
feeError = feeError?.error,
|
||||
appRouter = appRouter,
|
||||
isHighNetworkFee = isHighNetworkFee,
|
||||
)
|
||||
|
||||
val fromAccountTitleUM = when {
|
||||
|
|
@ -735,16 +734,15 @@ internal class StateBuilder(
|
|||
|
||||
/**
|
||||
* Builds the predefined percent buttons once per state update (off the composition path).
|
||||
* The row is gated by the feature toggle; the MAX button is included only when
|
||||
* [shouldShowMaxAmount] is `true` (e.g. it is dropped for a native coin swapped within the same
|
||||
* network, where spending the full balance would leave nothing for the network fee).
|
||||
* The MAX button is included only when [shouldShowMaxAmount] is `true` (e.g. it is dropped for a
|
||||
* native coin swapped within the same network, where spending the full balance would leave nothing
|
||||
* for the network fee).
|
||||
*/
|
||||
private fun createPredefinedButtons(
|
||||
fromToken: CryptoCurrency?,
|
||||
toCurrency: CryptoCurrency?,
|
||||
isTransferMode: Boolean = false,
|
||||
): ImmutableList<PredefinedPercentButtonUM> {
|
||||
if (!swapFeatureToggles.isSwapPredefinedButtonsEnabled) return persistentListOf()
|
||||
val shouldShowMaxAmount = shouldShowMaxAmount(fromToken, toCurrency, isTransferMode)
|
||||
return PredefinedPercentAmount.entries
|
||||
.filter { it != PredefinedPercentAmount.MAX || shouldShowMaxAmount }
|
||||
|
|
@ -1257,7 +1255,7 @@ internal class StateBuilder(
|
|||
providerType == ExchangeProviderType.DEX.providerName ||
|
||||
providerType == ExchangeProviderType.DEX_BRIDGE.providerName
|
||||
}
|
||||
val availableFilters = if (swapFeatureToggles.isSwapProviderFilterEnabled && hasCex && hasDex) {
|
||||
val availableFilters = if (hasCex && hasDex) {
|
||||
persistentListOf(ProviderFilterType.ALL, ProviderFilterType.CEX, ProviderFilterType.DEX)
|
||||
} else {
|
||||
persistentListOf()
|
||||
|
|
|
|||
|
|
@ -85,44 +85,38 @@ private fun SwapTopBar(stateHolder: SwapStateHolder) {
|
|||
AppBarWithBackButtonAndIcon(
|
||||
text = stringResourceSafe(stateHolder.titleId),
|
||||
backIconRes = R.drawable.ic_close_24,
|
||||
iconRes = if (stateHolder.shouldShowAbMenu) R.drawable.ic_more_vertical_24 else null,
|
||||
onIconClick = if (stateHolder.shouldShowAbMenu) {
|
||||
{
|
||||
stateHolder.onSwapTypeMenuOpened()
|
||||
shouldShowModeMenu = true
|
||||
}
|
||||
} else {
|
||||
null
|
||||
iconRes = R.drawable.ic_more_vertical_24,
|
||||
onIconClick = {
|
||||
stateHolder.onSwapTypeMenuOpened()
|
||||
shouldShowModeMenu = true
|
||||
},
|
||||
onBackClick = stateHolder.onBackClicked,
|
||||
)
|
||||
if (stateHolder.shouldShowAbMenu) {
|
||||
Box(modifier = Modifier.align(Alignment.TopEnd)) {
|
||||
TangemDropdownMenu(
|
||||
expanded = shouldShowModeMenu,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
offset = DpOffset(x = TangemTheme.dimens.spacing20, y = 44.dp),
|
||||
onDismissRequest = { shouldShowModeMenu = false },
|
||||
content = {
|
||||
SwapUiModeMenuItem(
|
||||
title = stringResourceSafe(R.string.swap_simple_mode),
|
||||
isSelected = stateHolder.swapUIMode == SwapUIMode.Simple,
|
||||
onClick = {
|
||||
shouldShowModeMenu = false
|
||||
stateHolder.onSwapUIModeChange(SwapUIMode.Simple)
|
||||
},
|
||||
)
|
||||
SwapUiModeMenuItem(
|
||||
title = stringResourceSafe(R.string.swap_detailed_mode),
|
||||
isSelected = stateHolder.swapUIMode == SwapUIMode.Detailed,
|
||||
onClick = {
|
||||
shouldShowModeMenu = false
|
||||
stateHolder.onSwapUIModeChange(SwapUIMode.Detailed)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
Box(modifier = Modifier.align(Alignment.TopEnd)) {
|
||||
TangemDropdownMenu(
|
||||
expanded = shouldShowModeMenu,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
offset = DpOffset(x = TangemTheme.dimens.spacing20, y = 44.dp),
|
||||
onDismissRequest = { shouldShowModeMenu = false },
|
||||
content = {
|
||||
SwapUiModeMenuItem(
|
||||
title = stringResourceSafe(R.string.swap_simple_mode),
|
||||
isSelected = stateHolder.swapUIMode == SwapUIMode.Simple,
|
||||
onClick = {
|
||||
shouldShowModeMenu = false
|
||||
stateHolder.onSwapUIModeChange(SwapUIMode.Simple)
|
||||
},
|
||||
)
|
||||
SwapUiModeMenuItem(
|
||||
title = stringResourceSafe(R.string.swap_detailed_mode),
|
||||
isSelected = stateHolder.swapUIMode == SwapUIMode.Detailed,
|
||||
onClick = {
|
||||
shouldShowModeMenu = false
|
||||
stateHolder.onSwapUIModeChange(SwapUIMode.Detailed)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import com.tangem.core.ui.utils.toTimeFormat
|
|||
import com.tangem.feature.swap.models.SwapSuccessStateHolder
|
||||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.preview.SwapSuccessStatePreview
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.common.ui.FeeBlockSuccess
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ import com.tangem.feature.swap.domain.models.SwapAmount
|
|||
import com.tangem.feature.swap.domain.models.ui.SwapState
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.utils.FeeCalculationUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.lib.crypto.BlockchainUtils.getTezosThreshold
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTezos
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isTron
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
|
@ -107,7 +108,7 @@ internal class SwapTransferNotificationsFactory @Inject constructor() {
|
|||
onReduceToAmount(amount.copy(value = reduceTo))
|
||||
},
|
||||
)
|
||||
if (!isCardano) {
|
||||
if (!isCardano && !feeValue.isZero()) {
|
||||
addDustWarningNotification(
|
||||
dustValue = state.currencyCheck?.dustValue,
|
||||
feeValue = feeValue,
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import com.tangem.feature.swap.models.states.SwapNotificationUM
|
|||
import com.tangem.feature.swap.presentation.R
|
||||
import com.tangem.feature.swap.ui.SwapAmountScreenClickIntents
|
||||
import com.tangem.feature.swap.ui.swapSuccessNavigation
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.utils.formatFooterFiatFee
|
||||
import com.tangem.features.send.api.utils.getTronTokenFeeSendingText
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.feature.swap.models.*
|
|||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.ui.StateBuilder
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -33,7 +32,6 @@ internal class StateBuilderInitialStateTest {
|
|||
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
|
||||
private val isAccountsModeProvider: Provider<Boolean> = mockk()
|
||||
private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true)
|
||||
private val appRouter: AppRouter = mockk()
|
||||
|
||||
private lateinit var sut: StateBuilder
|
||||
|
|
@ -52,7 +50,6 @@ internal class StateBuilderInitialStateTest {
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
isAccountsModeProvider = isAccountsModeProvider,
|
||||
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.feature.swap.models.*
|
|||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.feature.swap.ui.StateBuilder
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -33,7 +32,6 @@ internal class StateBuilderPairsTest {
|
|||
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
|
||||
private val isAccountsModeProvider: Provider<Boolean> = mockk()
|
||||
private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true)
|
||||
private val appRouter: AppRouter = mockk()
|
||||
|
||||
private lateinit var sut: StateBuilder
|
||||
|
|
@ -62,7 +60,6 @@ internal class StateBuilderPairsTest {
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
isAccountsModeProvider = isAccountsModeProvider,
|
||||
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
}
|
||||
|
|
@ -394,8 +391,7 @@ internal class StateBuilderPairsTest {
|
|||
inner class PredefinedButtonsVisibility {
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle on and native coin within same network WHEN updateCurrenciesState THEN MAX button is dropped but percents stay`() {
|
||||
every { swapFeatureToggles.isSwapPredefinedButtonsEnabled } returns true
|
||||
fun `GIVEN native coin within same network WHEN updateCurrenciesState THEN MAX button is dropped but percents stay`() {
|
||||
val baseState = buildReadyState(coldWallet)
|
||||
val networkId: Network.ID = mockk(relaxed = true)
|
||||
val fromStatus = buildCoinSwapCurrencyStatus(coldWallet, networkId)
|
||||
|
|
@ -420,8 +416,7 @@ internal class StateBuilderPairsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle on and non-coin WHEN updateCurrenciesState THEN all percents including MAX are built`() {
|
||||
every { swapFeatureToggles.isSwapPredefinedButtonsEnabled } returns true
|
||||
fun `GIVEN non-coin WHEN updateCurrenciesState THEN all percents including MAX are built`() {
|
||||
val baseState = buildReadyState(coldWallet)
|
||||
val fromStatus = buildSwapCurrencyStatus(coldWallet)
|
||||
val toStatus = buildSwapCurrencyStatus(coldWallet)
|
||||
|
|
@ -440,24 +435,6 @@ internal class StateBuilderPairsTest {
|
|||
.inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN toggle off WHEN updateCurrenciesState THEN no predefined buttons are built`() {
|
||||
every { swapFeatureToggles.isSwapPredefinedButtonsEnabled } returns false
|
||||
val baseState = buildReadyState(coldWallet)
|
||||
val fromStatus = buildSwapCurrencyStatus(coldWallet)
|
||||
val toStatus = buildSwapCurrencyStatus(coldWallet)
|
||||
|
||||
val result = sut.updateCurrenciesState(
|
||||
uiStateHolder = baseState,
|
||||
emptyAmountState = emptyAmountState,
|
||||
fromSwapCurrencyStatus = fromStatus,
|
||||
toSwapCurrencyStatus = toStatus,
|
||||
shouldResetAmount = false,
|
||||
)
|
||||
|
||||
assertThat(result.predefinedButtons).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN createInitialLoadingState THEN no predefined buttons are built`() {
|
||||
val result = sut.createInitialLoadingState()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import com.tangem.feature.swap.domain.models.ui.*
|
|||
import com.tangem.feature.swap.models.*
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
import com.tangem.feature.swap.ui.StateBuilder
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -59,7 +58,6 @@ internal class StateBuilderSwapButtonTest {
|
|||
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
|
||||
private val isAccountsModeProvider: Provider<Boolean> = mockk()
|
||||
private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true)
|
||||
private val appRouter: AppRouter = mockk()
|
||||
|
||||
private lateinit var sut: StateBuilder
|
||||
|
|
@ -81,7 +79,6 @@ internal class StateBuilderSwapButtonTest {
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
isAccountsModeProvider = isAccountsModeProvider,
|
||||
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
}
|
||||
|
|
@ -109,6 +106,7 @@ internal class StateBuilderSwapButtonTest {
|
|||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
swapFee = null,
|
||||
feeError = null,
|
||||
isHighNetworkFee = false,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isTrue()
|
||||
|
|
@ -134,6 +132,7 @@ internal class StateBuilderSwapButtonTest {
|
|||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
swapFee = null,
|
||||
feeError = null,
|
||||
isHighNetworkFee = false,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isFalse()
|
||||
|
|
@ -163,6 +162,7 @@ internal class StateBuilderSwapButtonTest {
|
|||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
swapFee = null,
|
||||
feeError = null,
|
||||
isHighNetworkFee = false,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isFalse()
|
||||
|
|
@ -187,6 +187,7 @@ internal class StateBuilderSwapButtonTest {
|
|||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
swapFee = buildSwapFee(),
|
||||
feeError = null,
|
||||
isHighNetworkFee = false,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isTrue()
|
||||
|
|
@ -215,6 +216,7 @@ internal class StateBuilderSwapButtonTest {
|
|||
additionalBadge = ProviderState.AdditionalBadge.Empty,
|
||||
swapFee = buildSwapFee(),
|
||||
feeError = null,
|
||||
isHighNetworkFee = false,
|
||||
)
|
||||
|
||||
assertThat(result.swapButton.isEnabled).isFalse()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.feature.swap.models.SwapStateHolder
|
|||
import com.tangem.feature.swap.models.TransactionCardType
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.ui.StateBuilder
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.Provider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -34,7 +33,6 @@ internal class StateBuilderUpdateSwapAmountTest {
|
|||
private val appCurrencyProvider: Provider<AppCurrency> = mockk()
|
||||
private val isAccountsModeProvider: Provider<Boolean> = mockk()
|
||||
private val isGaslessFeeSupportedForNetwork: IsGaslessFeeSupportedForNetwork = mockk()
|
||||
private val swapFeatureToggles: SwapFeatureToggles = mockk(relaxed = true)
|
||||
private val appRouter: AppRouter = mockk()
|
||||
|
||||
private val appCurrency = AppCurrency.Default
|
||||
|
|
@ -58,7 +56,6 @@ internal class StateBuilderUpdateSwapAmountTest {
|
|||
appCurrencyProvider = appCurrencyProvider,
|
||||
isAccountsModeProvider = isAccountsModeProvider,
|
||||
isGaslessFeeSupportedForNetwork = isGaslessFeeSupportedForNetwork,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
appRouter = appRouter,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.feature.swap.model
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
|
||||
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import io.mockk.coVerify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.domain.models.ui.PermissionDataState
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ import com.tangem.feature.swap.domain.models.ui.SwapState
|
|||
import com.tangem.feature.swap.domain.models.ui.TokenSwapInfo
|
||||
import com.tangem.feature.swap.models.UiActions
|
||||
import com.tangem.feature.swap.models.states.SwapNotificationUM
|
||||
import com.tangem.features.send.api.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.api.entity.FeeItem
|
||||
import com.tangem.features.send.api.entity.FeeSelectorUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.CustomFeeFieldUM
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeItem
|
||||
import com.tangem.features.send.api.subcomponents.feeSelector.entity.FeeSelectorUM
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -113,7 +113,9 @@ internal class SwapTransferNotificationsFactoryTest {
|
|||
|
||||
val result = sut.getNotifications(
|
||||
transferState = transferState,
|
||||
feeSelectorUM = null,
|
||||
// A loaded (non-zero) fee is required: the manual dust check is now skipped when feeValue is zero
|
||||
// (fee errored / not yet loaded), so it cannot duplicate the fee-error path's MinimumAmountError.
|
||||
feeSelectorUM = contentWithFee(feeValue = BigDecimal("0.001")),
|
||||
feeCryptoCurrencyStatus = null,
|
||||
actions = actions,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue