diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4a8d97d929..952d0831fa 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -668,6 +668,7 @@ Fees All decentralized exchanges require approvals to prevent smart contracts from accessing your wallet without your permission. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the 1-inch smart contract to spend them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can swap your token after giving approval. Approve + Fee estimation error. Please send feedback to support. You swap Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. Insufficient funds diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 6237a44e56..9e9a4eec98 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -14,6 +14,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.utils.InputNumberFormatter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -35,7 +36,10 @@ import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.formatToUIRepresentation import com.tangem.feature.swap.domain.models.ui.* -import com.tangem.feature.swap.models.* +import com.tangem.feature.swap.models.SwapStateHolder +import com.tangem.feature.swap.models.SwapWarning +import com.tangem.feature.swap.models.UiActions +import com.tangem.feature.swap.presentation.R import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder @@ -493,9 +497,9 @@ internal class SwapViewModel @Inject constructor( } val fromCurrency = requireNotNull(dataState.fromCryptoCurrency) val fee = dataState.selectedFee - // TODO: unexpected crash for some users, this workaround to prevent app crash and follow to support + if (fee == null) { - makeDefaultAlert(TextReference.Str("Fee estimation error. Please send feedback to support.")) + makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text)) return } viewModelScope.launch(dispatchers.main) { @@ -587,57 +591,63 @@ internal class SwapViewModel @Inject constructor( private fun givePermissionsToSwap() { viewModelScope.launch(dispatchers.main) { - val fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) { - "dataState.fromCurrency might not be null" - } - val feeForPermission = when (val fee = dataState.approveDataModel?.fee) { - TxFeeState.Empty -> error("Fee should not be Empty") - is TxFeeState.MultipleFeeState -> fee.priorityFee - is TxFeeState.SingleFeeState -> fee.fee - null -> error("Fee should not be null") - } - val approveType = requireNotNull(dataState.approveType) { - "uiState.permissionState should not be null" - }.toDomainApproveType() - runCatching(dispatchers.io) { - swapInteractor.givePermissionToSwap( - networkId = fromToken.network.backendId, - permissionOptions = PermissionOptions( - approveData = requireNotNull(dataState.approveDataModel) { - "dataState.approveDataModel might not be null" - }, - forTokenContractAddress = (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Token) - ?.contractAddress ?: "", - fromToken = fromToken, - approveType = approveType, - txFee = feeForPermission, - spenderAddress = requireNotNull(dataState.approveDataModel?.spenderAddress) { - "dataState.approveDataModel.spenderAddress shouldn't be null" - }, - ), - ) - }.onSuccess { - when (it) { - is SwapTransactionState.TxSent -> { - sendApproveSuccessEvent(fromToken, feeForPermission.feeType, approveType) - updateWalletBalance() - uiState = stateBuilder.loadingPermissionState(uiState) - uiState = stateBuilder.dismissBottomSheet(uiState) - startLoadingQuotesFromLastState(isSilent = true) + runCatching { + val fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) { + "dataState.fromCurrency might not be null" + } + val approveDataModel = requireNotNull(dataState.approveDataModel) { + "dataState.approveDataModel.spenderAddress shouldn't be null" + } + val approveType = requireNotNull(dataState.approveType?.toDomainApproveType()) { + "uiState.permissionState should not be null" + } + val feeForPermission = when (val fee = approveDataModel.fee) { + TxFeeState.Empty -> { + makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text)) + Timber.e("Fee should not be Empty") + return@runCatching } - is SwapTransactionState.UserCancelled -> Unit - else -> { - uiState = stateBuilder.createErrorTransaction(uiState, it) { - uiState = stateBuilder.clearAlert(uiState) + is TxFeeState.MultipleFeeState -> fee.priorityFee + is TxFeeState.SingleFeeState -> fee.fee + } + runCatching(dispatchers.io) { + swapInteractor.givePermissionToSwap( + networkId = fromToken.network.backendId, + permissionOptions = PermissionOptions( + approveData = approveDataModel, + forTokenContractAddress = (fromToken as? CryptoCurrency.Token)?.contractAddress.orEmpty(), + fromToken = fromToken, + approveType = approveType, + txFee = feeForPermission, + spenderAddress = approveDataModel.spenderAddress, + ), + ) + }.onSuccess { + when (it) { + is SwapTransactionState.TxSent -> { + sendApproveSuccessEvent(fromToken, feeForPermission.feeType, approveType) + updateWalletBalance() + uiState = stateBuilder.loadingPermissionState(uiState) + uiState = stateBuilder.dismissBottomSheet(uiState) + startLoadingQuotesFromLastState(isSilent = true) + } + is SwapTransactionState.UserCancelled -> Unit + else -> { + uiState = stateBuilder.createErrorTransaction(uiState, it) { + uiState = stateBuilder.clearAlert(uiState) + } } } - } - }.onFailure { - makeDefaultAlert() - } + }.onFailure { makeDefaultAlert() } + }.onFailure { showGenericError(it.message.orEmpty()) } } } + private fun showGenericError(message: String) { + makeDefaultAlert(resourceReference(R.string.common_unknown_error)) + Timber.e(message) + } + private fun onSearchEntered(searchQuery: String) { viewModelScope.launch(dispatchers.io) { val tokenDataState = dataState.tokensDataState ?: return@launch