diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 613f391bf4..b1b03a0bb9 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -600,288 +600,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - @Suppress("NullableToStringCall") - override suspend fun onSwapWithUnifiedFee( - fromSwapCurrencyStatus: SwapCurrencyStatus, - toSwapCurrencyStatus: SwapCurrencyStatus, - swapProvider: SwapProvider, - swapData: SwapDataModel?, - amountToSwap: String, - includeFeeInAmount: IncludeFeeInAmount, - fee: SwapFee?, - expressOperationType: ExpressOperationType, - isTangemPayWithdrawal: Boolean, - ): SwapTransactionState { - TangemLogger.i( - """ - Swap (unified fee) - |- swapProvider: $swapProvider - |- swapData: $swapData - |- fromSwapCurrencyStatus: - |---- walletId: ${fromSwapCurrencyStatus.userWalletId} - |---- accountId: ${fromSwapCurrencyStatus.account.accountId} - |---- currencyId: ${fromSwapCurrencyStatus.currency.id} - |- toSwapCurrencyStatus: $toSwapCurrencyStatus - |---- walletId: ${toSwapCurrencyStatus.userWalletId} - |---- accountId: ${toSwapCurrencyStatus.account.accountId} - |---- currencyId: ${toSwapCurrencyStatus.currency.id} - |- amountToSwap: $amountToSwap - |- includeFeeInAmount: $includeFeeInAmount - |- fee: $fee - """.trimIndent(), - shouldSanitize = false, - ) - - val userWallet = fromSwapCurrencyStatus.userWallet - if (userWallet is UserWallet.Cold && isDemoCardUseCase(userWallet.scanResponse.card.cardId)) { - return SwapTransactionState.DemoMode - } - - return when (swapProvider.type) { - ExchangeProviderType.CEX -> { - val amountDecimal = toBigDecimalOrNull(amountToSwap) - val amount = SwapAmount(requireNotNull(amountDecimal), fromSwapCurrencyStatus.currency.decimals) - val amountToSwapWithFee = if (includeFeeInAmount is IncludeFeeInAmount.Included) { - includeFeeInAmount.amountSubtractFee - } else { - amount - } - onSwapCexUnified( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - amount = amountToSwapWithFee, - swapFee = fee, - swapProvider = swapProvider, - expressOperationType = expressOperationType, - isTangemPayWithdrawal = isTangemPayWithdrawal, - ) - } - ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { - val networkId = fromSwapCurrencyStatus.currency.network.rawId - if (isSolana(networkId)) { - onSwapSolanaDex( - provider = swapProvider, - swapData = requireNotNull(swapData), - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - amountToSwap = amountToSwap, - ) - } else { - if (fee == null) return SwapTransactionState.Error.UnknownError - onSwapDex( - provider = swapProvider, - swapData = requireNotNull(swapData), - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - swapFee = fee, - amountToSwap = amountToSwap, - ) - } - } - } - } - - private suspend fun onSwapDex( - fromSwapCurrencyStatus: SwapCurrencyStatus, - toSwapCurrencyStatus: SwapCurrencyStatus, - provider: SwapProvider, - swapData: SwapDataModel, - amountToSwap: String, - swapFee: SwapFee, - ): SwapTransactionState { - val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" } - val txValue = requireNotNull(swapData.transaction.txValue) { "txValue is null" } - val amount = SwapAmount(amountDecimal, fromSwapCurrencyStatus.currency.decimals) - val dexTransaction = swapData.transaction as ExpressTransactionModel.DEX - val dataToSign = dexTransaction.txData - val amountToSend = createNativeAmountForDex(txValue, fromSwapCurrencyStatus.currency.network) - val txData = createTransactionUseCase( - amount = amountToSend, - fee = swapFee.fee, - memo = null, - destination = swapData.transaction.txTo, - userWalletId = fromSwapCurrencyStatus.userWalletId, - network = toSwapCurrencyStatus.currency.network, - txExtras = createDexTxExtras( - dataToSign, - fromSwapCurrencyStatus.currency.network, - swapFee.fee.getGasLimit(), - ), - ).getOrElse { error -> - TangemLogger.e("Failed to create swap dex tx data", error) - return SwapTransactionState.Error.UnknownError - } - - return handleSwapResult( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - provider = provider, - swapData = swapData, - amount = amount, - txData = txData, - payInAddress = getPayoutAddress(txData), - ) - } - - /** - * Branch selection: - * - Gasless token path: `swapFee.transactionFeeResult is LoadedExtended && selectedFeeToken.currency is Token` - * → `createAndSendGaslessTransactionUseCase`. - * - Otherwise → `sendTransactionUseCase` with `swapFee.fee`. - */ - @Suppress("LongMethod", "CanBeNonNullable") - private suspend fun onSwapCex( - fromSwapCurrencyStatus: SwapCurrencyStatus, - toSwapCurrencyStatus: SwapCurrencyStatus, - amount: SwapAmount, - swapFee: SwapFee?, - swapProvider: SwapProvider, - expressOperationType: ExpressOperationType, - isTangemPayWithdrawal: Boolean, - ): SwapTransactionState { - val fromNetworkAddress = fromSwapCurrencyStatus.status.value.networkAddress - val fromAddress = fromNetworkAddress?.defaultAddress?.value.orEmpty() - val toNetworkAddress = toSwapCurrencyStatus.status.value.networkAddress - val toAddress = toNetworkAddress?.defaultAddress?.value.orEmpty() - val exchangeData = repository.getExchangeData( - userWallet = fromSwapCurrencyStatus.userWallet, - fromContractAddress = fromSwapCurrencyStatus.currency.getContractAddress(), - fromNetwork = fromSwapCurrencyStatus.currency.network.rawId, - toContractAddress = toSwapCurrencyStatus.currency.getContractAddress(), - fromAddress = fromAddress, - toNetwork = toSwapCurrencyStatus.currency.network.rawId, - fromAmount = amount.toStringWithRightOffset(), - fromDecimals = amount.decimals, - toDecimals = toSwapCurrencyStatus.currency.decimals, - providerId = swapProvider.providerId, - rateType = RateType.FLOAT, - expressOperationType = expressOperationType, - toAddress = toAddress, - refundAddress = fromNetworkAddress?.defaultAddress?.value, - refundExtraId = null, // currently always null, - ).getOrElse { error -> return SwapTransactionState.Error.ExpressError(error) } - - val exchangeDataCex = - exchangeData.transaction as? ExpressTransactionModel.CEX ?: return SwapTransactionState.Error.UnknownError - - if (isTangemPayWithdrawal) { - return SwapTransactionState.TangemPayWithdrawalData( - cryptoAmount = amount.value, - cryptoCurrencyId = requireNotNull(fromSwapCurrencyStatus.currency.id.rawCurrencyId), - cexAddress = exchangeDataCex.txTo, - fromAmount = amountFormatter.formatSwapAmountToUI( - amount, - fromSwapCurrencyStatus.currency.symbol, - ), - fromAmountValue = amount.value, - toAmount = amountFormatter.formatSwapAmountToUI( - exchangeData.toTokenAmount, - toSwapCurrencyStatus.currency.symbol, - ), - toAmountValue = exchangeData.toTokenAmount.value, - storeData = SwapTransactionState.TangemPayWithdrawalData.StoreTransactionData( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - amount = amount, - swapProvider = swapProvider, - swapDataModel = exchangeData, - txExternalUrl = exchangeDataCex.externalTxUrl, - txExternalId = exchangeDataCex.externalTxId, - averageDuration = null, - ), - exchangeData = TangemPayWithdrawExchangeState( - txId = exchangeDataCex.txId, - fromNetwork = fromSwapCurrencyStatus.currency.network.rawId, - fromAddress = fromNetworkAddress?.defaultAddress?.value.orEmpty(), - payInAddress = exchangeData.transaction.txTo, - payInExtraId = exchangeDataCex.txExtraId, - ), - ) - } - - val userWallet = fromSwapCurrencyStatus.userWallet - if (userWallet is UserWallet.Cold && isDemoCardUseCase(userWallet.scanResponse.card.cardId)) { - return SwapTransactionState.Error.UnknownError - } - val fee = requireNotNull(swapFee) - val txData = createTransferTransactionUseCase( - amount = amount.value.convertToSdkAmount(fromSwapCurrencyStatus.status), - fee = fee.fee, - memo = exchangeDataCex.txExtraId, - destination = exchangeDataCex.txTo, - userWalletId = fromSwapCurrencyStatus.userWalletId, - network = fromSwapCurrencyStatus.currency.network, - ).getOrElse { error -> - TangemLogger.e("Failed to create swap CEX tx data", error) - return SwapTransactionState.Error.UnknownError - } - - if (txData.extras == null && exchangeDataCex.txExtraId != null) { - return SwapTransactionState.Error.UnknownError - } - - val isGaslessToken = fee.selectedFeeToken.currency is CryptoCurrency.Token && - fee.transactionFeeResult is TransactionFeeResult.LoadedExtended - val result = if (isGaslessToken) { - createAndSendGaslessTransactionUseCase.invoke( - transactionData = txData, - userWallet = userWallet, - fee = fee.transactionFeeResult.fee, - ) - } else { - sendTransactionUseCase( - txData = txData, - userWallet = userWallet, - network = fromSwapCurrencyStatus.currency.network, - ) - } - - val cexNetworkAddress = fromSwapCurrencyStatus.status.value.networkAddress - val cexFromAddress = cexNetworkAddress?.defaultAddress?.value.orEmpty() - return result.fold( - ifLeft = { error -> SwapTransactionState.Error.TransactionError(error) }, - ifRight = { txHash -> - repository.exchangeSent( - userWallet = userWallet, - txId = exchangeDataCex.txId, - fromNetwork = fromSwapCurrencyStatus.currency.network.rawId, - fromAddress = cexFromAddress, - payInAddress = getPayoutAddress(txData), - txHash = txHash, - payInExtraId = exchangeDataCex.txExtraId, - ) - val timestamp = System.currentTimeMillis() - val txExternalUrl = exchangeDataCex.externalTxUrl - storeSwapTransaction( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - amount = amount, - swapProvider = swapProvider, - swapDataModel = exchangeData, - timestamp = timestamp, - txExternalUrl = txExternalUrl, - txExternalId = exchangeDataCex.externalTxId, - ) - storeLastCryptoCurrencyId(toSwapCurrencyStatus) - SwapTransactionState.TxSent( - fromAmount = amountFormatter.formatSwapAmountToUI( - amount, - fromSwapCurrencyStatus.currency.symbol, - ), - fromAmountValue = amount.value, - toAmount = amountFormatter.formatSwapAmountToUI( - exchangeData.toTokenAmount, - toSwapCurrencyStatus.currency.symbol, - ), - toAmountValue = exchangeData.toTokenAmount.value, - txHash = txHash, - txExternalUrl = txExternalUrl, - timestamp = timestamp, - ) - }, - ) - } - private suspend fun onSwapDex( fromSwapCurrencyStatus: SwapCurrencyStatus, toSwapCurrencyStatus: SwapCurrencyStatus, @@ -1501,301 +1219,6 @@ internal class SwapInteractorImpl @Inject constructor( } } - /** - * [REDACTED_TASK_KEY] — Phase 3 unified fee API. Delegates to [DexSwapFeeCalculator] / - * [CexSwapFeeCalculator] and wraps the result in a [SwapFee]. - * - * Behavior parity with the legacy `loadFeeForSwapTransaction` overloads is intentional — - * the legacy methods stay in place through Phase 4. See `SwapInteractor.loadSwapFee` for - * the full contract. - */ - @Suppress("LongParameterList", "ReturnCount") - override suspend fun loadSwapFee( - provider: SwapProvider, - fromStatus: SwapCurrencyStatus, - toStatus: SwapCurrencyStatus, - amount: SwapAmount, - swapData: SwapDataModel?, - selectedFeeToken: CryptoCurrencyStatus?, - ): Either = either { - if (amount.value.signum() == 0) { - raise(GetFeeError.UnknownError) - } - return when (provider.type) { - ExchangeProviderType.DEX, - ExchangeProviderType.DEX_BRIDGE, - -> loadDexSwapFee( - fromStatus = fromStatus, - swapData = swapData, - selectedFeeToken = selectedFeeToken, - ) - ExchangeProviderType.CEX -> loadCexSwapFee( - fromStatus = fromStatus, - amount = amount, - selectedFeeToken = selectedFeeToken, - ) - } - } - - /** - * [REDACTED_TASK_KEY] — DEX branch of [loadSwapFee]. Pulls the cached `ExpressTransactionModel.DEX` - * out of [swapData] and hands it to [DexSwapFeeCalculator]. Maps [ExpressDataError] → - * `Left(GetFeeError.UnknownError)` to keep the unified surface a single error type, matching - * what the legacy `loadFeeForSwapTransaction` overload 2 does for DEX failures (line 1027 of - * the original code). - */ - private suspend fun loadDexSwapFee( - fromStatus: SwapCurrencyStatus, - swapData: SwapDataModel?, - selectedFeeToken: CryptoCurrencyStatus?, - ): Either { - val transaction = swapData?.transaction as? ExpressTransactionModel.DEX - ?: return GetFeeError.UnknownError.left() - - return dexSwapFeeCalculator.calculate( - fromSwapCurrencyStatus = fromStatus, - transaction = transaction, - selectedToken = selectedFeeToken, - ).fold( - ifLeft = { error -> GetFeeError.DataError(error).left() }, - ifRight = { dexFeeResult -> - val feeToken = selectedFeeToken - ?: resolveNativeFeeTokenStatus(fromStatus) - ?: return@fold GetFeeError.UnknownError.left() - SwapFeeFactory.from( - transactionFeeResult = dexFeeResult.transactionFee, - selectedFeeToken = feeToken, - otherNativeFee = dexFeeResult.otherNativeFee, - feeBucket = FeeBucket.MARKET, - ).right() - }, - ) - } - - /** - * [REDACTED_TASK_KEY] — CEX branch of [loadSwapFee]. Native-fallback behavior is preserved: when - * [selectedFeeToken] is null the gasless use case (invoked inside [CexSwapFeeCalculator]) - * decides native vs token. The resulting `SwapFee.selectedFeeToken` is the explicit choice - * if provided, otherwise the native coin status of the from-token's network. - */ - private suspend fun loadCexSwapFee( - fromStatus: SwapCurrencyStatus, - amount: SwapAmount, - selectedFeeToken: CryptoCurrencyStatus?, - ): Either { - return cexSwapFeeCalculator.calculate( - userWallet = fromStatus.userWallet, - fromSwapCurrencyStatus = fromStatus, - amount = amount.value, - selectedFeeToken = selectedFeeToken, - ).fold( - ifLeft = { it.left() }, - ifRight = { cexFeeResult -> - val feeToken = selectedFeeToken - ?: resolveNativeFeeTokenStatus(fromStatus) - ?: return@fold GetFeeError.UnknownError.left() - SwapFeeFactory.from( - transactionFeeResult = cexFeeResult.transactionFee, - selectedFeeToken = feeToken, - otherNativeFee = BigDecimal.ZERO, - feeBucket = FeeBucket.MARKET, - ).right() - }, - ) - } - - /** - * [REDACTED_TASK_KEY] — resolves the native-coin [CryptoCurrencyStatus] for the from-token's network. - * Used as the default `selectedFeeToken` of [SwapFee] when the caller did not provide an - * explicit choice. Mirrors how `SwapModel.updateFeePaidCryptoCurrencyFor` populates - * `dataState.feePaidCryptoCurrency`. - */ - private suspend fun resolveNativeFeeTokenStatus(fromStatus: SwapCurrencyStatus): CryptoCurrencyStatus? { - return getFeePaidCryptoCurrencyStatusSyncUseCase( - userWalletId = fromStatus.userWalletId, - cryptoCurrencyStatus = fromStatus.status, - ).getOrNull() ?: run { - val feeNetwork = fromStatus.currency.network - - val feePaidCurrency = currenciesRepository.getFeePaidCurrency( - fromStatus.userWalletId, - feeNetwork, - ) - - val (feeCurrency, balance) = when (feePaidCurrency) { - FeePaidCurrency.Coin -> currenciesRepository.createCoinCurrency(feeNetwork) to - walletManagersFacade.getNativeTokenBalance( - userWalletId = fromStatus.userWalletId, - networkId = feeNetwork.rawId, - derivationPath = feeNetwork.derivationPath.value, - ) - is FeePaidCurrency.Token -> currenciesRepository.createTokenCurrency( - userWalletId = fromStatus.userWalletId, - contractAddress = feePaidCurrency.contractAddress, - networkId = feeNetwork.rawId, - ) to feePaidCurrency.balance - is FeePaidCurrency.FeeResource, - FeePaidCurrency.SameCurrency, - -> fromStatus.currency to fromStatus.status.value.amount - } - - val feeCurrencyRawID = feeCurrency.id.rawCurrencyId ?: return@run null - val quote = quotesRepository.getMultiQuoteSyncOrNull(setOf(feeCurrencyRawID)) - ?.firstOrNull()?.value as? QuoteStatus.Data - - CryptoCurrencyStatus( - currency = feeCurrency, - value = if (quote == null) { - CryptoCurrencyStatus.NoQuote( - amount = balance.orZero(), - stakingBalance = null, - yieldSupplyStatus = null, - hasCurrentNetworkTransactions = false, - pendingTransactions = emptySet(), - networkAddress = fromStatus.status.value.networkAddress ?: return@run null, - sources = CryptoCurrencyStatus.Sources(), - ) - } else { - CryptoCurrencyStatus.Loaded( - amount = balance.orZero(), - fiatAmount = quote.fiatRate.multiply(balance), - fiatRate = quote.fiatRate, - priceChange = quote.priceChange, - stakingBalance = null, - yieldSupplyStatus = null, - hasCurrentNetworkTransactions = false, - pendingTransactions = emptySet(), - networkAddress = fromStatus.status.value.networkAddress ?: return@run null, - sources = CryptoCurrencyStatus.Sources(), - ) - }, - ) - } - } - - /** - * Patches an existing [SwapState.QuotesLoadedState] with a freshly resolved [SwapFee]. - * See [SwapInteractor.applySwapFee] for the full contract. - * - * Numeric fee used for downstream computation: - * - If `fee.selectedFeeToken.currency` is a token → `0` for the balance / include-fee math - * when the fee currency differs from the from-token (matches legacy `manageWarnings` - * semantics at line 422 of the pre-Phase-4 code). - * - Otherwise → `fee.fee.amount.value + fee.otherNativeFee` (the bridge-aware native fee). - * - * The fee is folded into a single [SwapBalanceStatus] by [computeBalanceStatus], which is - * then assigned to `preparedSwapConfigState.balanceStatus`. - */ - override suspend fun applySwapFee( - state: SwapState.QuotesLoadedState, - fee: SwapFee, - lastReducedBalanceBy: BigDecimal, - ): SwapState.QuotesLoadedState { - val fromSwapCurrencyStatus = state.fromTokenInfo.swapCurrencyStatus - val amount = state.fromTokenInfo.tokenAmount - val isFeeInToken = fee.selectedFeeToken.currency is CryptoCurrency.Token - val nativeFee = (fee.fee.amount.value ?: BigDecimal.ZERO) + fee.otherNativeFee - - // Mirrors legacy manageWarnings: token-fee paths skip the native deduction. - val warningsFee = if (isFeeInToken && fromSwapCurrencyStatus.currency.id != fee.selectedFeeToken.currency.id) { - BigDecimal.ZERO - } else { - nativeFee - } - - val balanceStatus = computeBalanceStatus( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - amount = amount, - reduceBalanceBy = lastReducedBalanceBy, - feeValue = nativeFee, - selectedFeeToken = fee.selectedFeeToken, - provider = state.swapProvider, - ) - val currencyCheck = manageWarnings( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - amount = amount, - fee = warningsFee, - balanceStatus = balanceStatus, - ) - val validationResult = manageTransactionValidationWarnings( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - amount = amount, - feeValue = nativeFee, - ) - val minAdaValue = (fee.fee as? Fee.CardanoToken)?.minAdaValue - - return state.copy( - preparedSwapConfigState = state.preparedSwapConfigState.copy( - balanceStatus = balanceStatus, - ), - currencyCheck = currencyCheck, - validationResult = validationResult, - minAdaValue = minAdaValue, - ) - } - - /** - * Decision tree (matches the user-approved derivation table plus the implicit Token-fee sub-case): - * 1. `Included` from `getIncludeFeeInAmountInternal` ⇒ [SwapBalanceStatus.FeeAdjustedAmount]. - * 2. `!isBalanceEnough` (from-token balance can't cover the amount itself) ⇒ - * [SwapBalanceStatus.InsufficientAmount]. - * 3. `feeBalanceState is NotEnough` ⇒ [SwapBalanceStatus.InsufficientFee]. This catches: - * - From-token is a Token, native balance can't cover the fee - * (legacy `includeFeeInAmount=BalanceNotEnough` for the Token branch). - * - From-token is a Coin and `balance - amount < fee` - * (legacy `feeState=NotEnough && includeFeeInAmount=Excluded`). - * 4. Otherwise ⇒ [SwapBalanceStatus.Sufficient]. - * - * The legacy ambiguity where `BalanceNotEnough` meant "amount > balance" for Coin - * from-currencies but "fee > native balance" for Token from-currencies is resolved here - * by consulting `isBalanceEnough` (amount-alone check) directly. - */ - private suspend fun computeBalanceStatus( - fromSwapCurrencyStatus: SwapCurrencyStatus, - amount: SwapAmount, - reduceBalanceBy: BigDecimal, - feeValue: BigDecimal, - selectedFeeToken: CryptoCurrencyStatus?, - provider: SwapProvider, - ): SwapBalanceStatus { - when (provider.type) { - ExchangeProviderType.CEX -> { - val includeStatus = getIncludeFeeInAmountInternal( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - amount = amount, - reduceBalanceBy = reduceBalanceBy, - feeValue = feeValue, - selectedFeeToken = selectedFeeToken, - ) - if (includeStatus is IncludeFeeInAmountInternal.Included) { - return SwapBalanceStatus.FeeAdjustedAmount(adjustedAmount = includeStatus.amountSubtractFee) - } - } - ExchangeProviderType.DEX, - ExchangeProviderType.DEX_BRIDGE, - -> Unit - } - - val isAmountAlone = isBalanceEnough(fromSwapCurrencyStatus, amount, fee = feeValue) - if (!isAmountAlone) { - return SwapBalanceStatus.InsufficientAmount - } - - val feeBalanceState = getFeeBalanceState( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - fee = feeValue, - spendAmount = amount, - selectedFeeToken = selectedFeeToken, - ) - return when (feeBalanceState) { - is FeeBalanceState.Enough -> SwapBalanceStatus.Sufficient - is FeeBalanceState.NotEnough -> SwapBalanceStatus.InsufficientFee( - feeCurrencyName = feeBalanceState.currencyName, - feeCurrencySymbol = feeBalanceState.currencySymbol, - ) - } - } - private suspend fun storeLastCryptoCurrencyId(swapCurrencyStatus: SwapCurrencyStatus) { swapTransactionRepository.storeLastSwappedCryptoCurrencyId( userWalletId = swapCurrencyStatus.userWalletId, diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 4d915395ad..d4c16b5b71 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -1,18 +1,10 @@ package com.tangem.feature.swap.domain.di +import com.tangem.core.abtests.manager.ABTestsManager import com.tangem.domain.transaction.usecase.CreateTransactionDataExtrasUseCase import com.tangem.domain.transaction.usecase.EstimateFeeUseCase import com.tangem.domain.transaction.usecase.GetEthSpecificFeeUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase -import com.tangem.core.abtests.manager.ABTestsManager -import com.tangem.feature.swap.domain.AllowPermissionsHandler -import com.tangem.feature.swap.domain.AllowPermissionsHandlerImpl -import com.tangem.feature.swap.domain.GetSwapUiModeUseCase -import com.tangem.feature.swap.domain.SetSwapUiModeUseCase -import com.tangem.feature.swap.domain.SwapFeedbackUseCase -import com.tangem.feature.swap.domain.SwapInteractor -import com.tangem.feature.swap.domain.SwapInteractorImpl -import com.tangem.domain.transaction.usecase.* import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForGaslessTxUseCase import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForTokenUseCase import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase @@ -23,9 +15,9 @@ import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.fee.CexSwapFeeCalculator import com.tangem.feature.swap.domain.fee.DexSwapFeeCalculator import com.tangem.feature.swap.domain.fee.PatchEthGasLimitForSwap -import com.tangem.features.swap.SwapFeatureToggles import com.tangem.feature.swap.domain.transfer.SwapTransferInteractor import com.tangem.feature.swap.domain.transfer.SwapTransferInteractorImpl +import com.tangem.features.swap.SwapFeatureToggles import dagger.Binds import dagger.Module import dagger.Provides diff --git a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt index bf926cf0da..0c0243150f 100644 --- a/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt +++ b/features/swap/domain/src/test/kotlin/com/tangem/feature/swap/domain/SwapInteractorImplFindBestQuoteTest.kt @@ -238,7 +238,6 @@ internal class SwapInteractorImplFindBestQuoteTest : SwapInteractorImplTestBase( providers = listOf(dexProvider), amountToSwap = "1.0", reduceBalanceBy = BigDecimal.ZERO, - ) // Then — has a result entry for the DEX provider; type of state is decided by internal logic diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 53160c708a..ce2ca9769e 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -1326,55 +1326,6 @@ internal class SwapModel @Inject constructor( } } - private fun onTransferClick() { - val fromSwapCurrencyStatus = dataState.fromSwapCurrencyStatus - val toSwapCurrencyStatus = dataState.toSwapCurrencyStatus - val fee = (feeSelectorRepository.state.value as? FeeSelectorUM.Content)?.selectedFeeItem?.fee - if (fromSwapCurrencyStatus == null || toSwapCurrencyStatus == null || fee == null) { - TangemLogger.e("onTransferClick: missing currency status or fee, aborting") - showAlert() - return - } - uiState = swapTransferStateBuilder.createTransferInProgressState(uiState) - modelScope.launch(dispatchers.main) { - swapTransferInteractor.sendTransfer( - fromSwapCurrencyStatus = fromSwapCurrencyStatus, - toSwapCurrencyStatus = toSwapCurrencyStatus, - fromTokenAmount = lastAmount.value, - fee = fee, - transactionFeeResult = requireNotNull(getSelectedSwapFee()?.transactionFeeResult) { - "It should be not null at this stage" - }, - ).fold( - ifLeft = { error -> - TangemLogger.e("onTransferClick: transfer failed: ${error.getAnalyticsDescription()}") - refreshTransferUIStateAfterFeeUpdate() - showAlert() - }, - ifRight = { txHash -> - val txUrl = getExplorerTransactionUrlUseCase( - txHash = txHash, - currency = fromSwapCurrencyStatus.currency, - ).getOrElse { - TangemLogger.i("onTransferClick: tx hash explore not supported") - "" - } - updateWalletBalance() - uiState = swapTransferStateBuilder.createSuccessState( - uiState = uiState, - dataState = dataState, - appCurrency = selectedAppCurrencyFlow.value, - isAccountsMode = isAccountsMode, - txUrl = txUrl, - timestamp = System.currentTimeMillis(), - fee = null, - ) - router.replaceAll(SwapRoute.Success) - }, - ) - } - } - private suspend fun processTangemPayWithdrawal( fromSwapCurrencyStatus: SwapCurrencyStatus, swapTransactionState: SwapTransactionState.TangemPayWithdrawalData, @@ -1754,15 +1705,6 @@ internal class SwapModel @Inject constructor( appRouter.push(route) }, - openTokenDetailsScreen = { cryptoCurrency -> - val fromSwapCurrencyStatus = dataState.fromSwapCurrencyStatus ?: return@UiActions - val route = AppRoute.CurrencyDetails( - userWalletId = fromSwapCurrencyStatus.userWalletId, - currency = cryptoCurrency, - ) - - appRouter.push(route) - }, onRetryClick = { startLoadingQuotesFromLastState() }, @@ -2119,34 +2061,6 @@ internal class SwapModel @Inject constructor( is FeeItem.Loading -> FeeBucket.MARKET } - /** - * [REDACTED_TASK_KEY] — Phase 4. Extracts the bridge protocol fee from the cached - * [SwapDataModel.transaction] payload (DEX bridge providers carry `otherNativeFeeWei`). - * - * The UI's `FeeSelectorUM` doesn't carry this value, so we read it from the most-recent - * swap data. Returns [BigDecimal.ZERO] when no swap data is cached, the transaction is not - * a DEX payload, or `otherNativeFeeWei` is null (non-bridge providers). - */ - private fun resolveOtherNativeFee(): BigDecimal { - val transaction = - dataState.getCurrentLoadedSwapState()?.swapDataModel?.transaction as? ExpressTransactionModel.DEX - ?: return BigDecimal.ZERO - val otherNativeFeeWei = transaction.otherNativeFeeWei ?: return BigDecimal.ZERO - val nativeDecimals = dataState.fromSwapCurrencyStatus?.currency?.network?.let { network -> - Blockchain.fromNetworkId(network.rawId)?.decimals() - } ?: return BigDecimal.ZERO - return otherNativeFeeWei.movePointLeft(nativeDecimals) - } - - private fun FeeItem.toFeeBucket(): FeeBucket = when (this) { - is FeeItem.Slow -> FeeBucket.SLOW - is FeeItem.Market -> FeeBucket.MARKET - is FeeItem.Fast -> FeeBucket.FAST - is FeeItem.Suggested -> FeeBucket.SUGGESTED - is FeeItem.Custom -> FeeBucket.CUSTOM - is FeeItem.Loading -> FeeBucket.MARKET - } - inner class FeeSelectorRepository : SwapFeeSelectorBlockComponent.ModelRepositoryExtended { override val state = MutableStateFlow( diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt index e504ccfdfb..91e28c1b4e 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapNotificationsFactory.kt @@ -379,52 +379,6 @@ internal class SwapNotificationsFactory( } } - private fun MutableList.maybeAddFeeErrorNotification( - feeCryptoCurrencyStatus: CryptoCurrencyStatus?, - quoteModel: SwapState.QuotesLoadedState, - feeError: GetFeeError?, - ) { - if ( - feeError == null || feeCryptoCurrencyStatus == null || - quoteModel.permissionState !is PermissionDataState.Empty - ) { - return - } - - when (feeError) { - is GetFeeError.DataError -> { - val error = feeError.cause - if (error is ExpressDataError) { - addAll( - getQuotesErrorStateNotifications( - expressDataError = error, - fromToken = quoteModel.fromTokenInfo.swapCurrencyStatus.currency, - balanceStatus = quoteModel.preparedSwapConfigState.balanceStatus, - swapFee = null, - ), - ) - } else { - addFeeUnreachableNotification( - tokenStatus = quoteModel.fromTokenInfo.swapCurrencyStatus.status, - coinStatus = feeCryptoCurrencyStatus, - feeError = feeError, - dustValue = quoteModel.currencyCheck?.dustValue, - onReload = actions.onRetryClick, - onClick = actions.openTokenDetailsScreen, - ) - } - } - else -> addFeeUnreachableNotification( - tokenStatus = quoteModel.fromTokenInfo.swapCurrencyStatus.status, - coinStatus = feeCryptoCurrencyStatus, - feeError = feeError, - dustValue = quoteModel.currencyCheck?.dustValue, - onReload = actions.onRetryClick, - onClick = actions.openTokenDetailsScreen, - ) - } - } - private fun MutableList.addReduceAmountNotification( cryptoCurrencyStatus: CryptoCurrencyStatus, fromAmount: SwapAmount,