diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateWalletAddressUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateWalletAddressUseCase.kt index 1452cbea68..c5a93fdc80 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateWalletAddressUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/ValidateWalletAddressUseCase.kt @@ -2,6 +2,7 @@ package com.tangem.domain.transaction.usecase import arrow.core.left import arrow.core.right +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkAddress import com.tangem.domain.transaction.WalletAddressServiceRepository @@ -43,4 +44,29 @@ class ValidateWalletAddressUseCase( else -> AddressValidation.Success.Valid.right() } } + + suspend operator fun invoke( + userWalletId: UserWalletId, + network: Network, + address: String, + senderAddresses: List, + ): AddressValidationResult { + val isUtxoConsolidationAvailable = + walletManagersFacade.checkUtxoConsolidationAvailability(userWalletId, network) + val isCurrentAddress = senderAddresses.any { it.address == address } + + val isForbidSelfSend = isCurrentAddress && !isUtxoConsolidationAvailable + + val decodedXAddress = BlockchainUtils.decodeRippleXAddress(address, network.id.value) + val addressToValidate = decodedXAddress?.address ?: address + + val isValidAddress = walletAddressServiceRepository.validateAddress(userWalletId, network, addressToValidate) + + return when { + !isValidAddress -> AddressValidation.Error.InvalidAddress.left() + isForbidSelfSend -> AddressValidation.Error.AddressInWallet.left() + decodedXAddress != null -> AddressValidation.Success.ValidXAddress.right() + else -> AddressValidation.Success.Valid.right() + } + } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt index 9d9d7a4778..3ce42734fc 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt @@ -133,7 +133,7 @@ internal class DefaultSendComponent @AssistedInject constructor( currentRoute = currentRoute.filterIsInstance(), isBalanceHidingFlow = model.isBalanceHiddenFlow, analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY, - userWallet = model.userWallet, + userWalletId = params.userWalletId, cryptoCurrency = params.currency, callback = model, isEditMode = route.isEditMode, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt index 28e8110f7f..3ed30ca608 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt @@ -41,7 +41,7 @@ internal class SendConfirmComponent( params = SendDestinationComponentParams.DestinationBlockParams( state = model.uiState.value.destinationUM, analyticsCategoryName = params.analyticsCategoryName, - userWallet = params.userWallet, + userWalletId = params.userWallet.walletId, cryptoCurrency = params.cryptoCurrencyStatus.currency, blockClickEnableFlow = blockClickEnableFlow.asStateFlow(), isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt index 4dc45b2f96..f4a69acb24 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt @@ -1,7 +1,7 @@ package com.tangem.features.send.v2.subcomponents.destination import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.send.v2.send.SendRoute import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent.ModelCallback import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM @@ -12,14 +12,14 @@ internal sealed class SendDestinationComponentParams { abstract val state: DestinationUM abstract val analyticsCategoryName: String - abstract val userWallet: UserWallet + abstract val userWalletId: UserWalletId abstract val cryptoCurrency: CryptoCurrency data class DestinationParams( override val state: DestinationUM, override val analyticsCategoryName: String, - override val userWallet: UserWallet, override val cryptoCurrency: CryptoCurrency, + override val userWalletId: UserWalletId, val isBalanceHidingFlow: StateFlow, val currentRoute: Flow, val callback: ModelCallback, @@ -29,7 +29,7 @@ internal sealed class SendDestinationComponentParams { data class DestinationBlockParams( override val state: DestinationUM, override val analyticsCategoryName: String, - override val userWallet: UserWallet, + override val userWalletId: UserWalletId, override val cryptoCurrency: CryptoCurrency, val blockClickEnableFlow: StateFlow, val predefinedAddressValue: String?, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt index 5622ae7e32..b62798fbb4 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt @@ -1,7 +1,6 @@ package com.tangem.features.send.v2.subcomponents.destination.model import androidx.compose.runtime.Stable -import arrow.core.Either import arrow.core.getOrElse import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter @@ -10,23 +9,10 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router -import com.tangem.features.send.v2.send.ui.state.ButtonsUM import com.tangem.core.ui.extensions.resourceReference -import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.feedback.GetCardInfoUseCase -import com.tangem.domain.feedback.SaveBlockchainErrorUseCase -import com.tangem.domain.feedback.SendFeedbackEmailUseCase -import com.tangem.domain.feedback.models.BlockchainErrorInfo -import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.qrscanning.models.SourceType -import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase -import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase import com.tangem.domain.tokens.GetCryptoCurrencyUseCase -import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase import com.tangem.domain.tokens.GetNetworkAddressesUseCase -import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase -import com.tangem.domain.tokens.error.CurrencyStatusError -import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.transaction.usecase.IsUtxoConsolidationAvailableUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase @@ -38,7 +24,7 @@ import com.tangem.features.send.v2.impl.R import com.tangem.features.send.v2.send.SendRoute import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents.SendScreenSource -import com.tangem.features.send.v2.subcomponents.destination.SendDestinationAlertFactory +import com.tangem.features.send.v2.send.ui.state.ButtonsUM import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource import com.tangem.features.send.v2.subcomponents.destination.analytics.SendDestinationAnalyticEvents @@ -56,7 +42,6 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject -import kotlin.properties.Delegates @Stable @ModelScoped @@ -73,15 +58,7 @@ internal class SendDestinationModel @Inject constructor( private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase, private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase, private val isUtxoConsolidationAvailableUseCase: IsUtxoConsolidationAvailableUseCase, - private val listenToQrScanningUseCase: ListenToQrScanningUseCase, - private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, - private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, - private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase, - private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, - private val getCardInfoUseCase: GetCardInfoUseCase, - private val parseQrCodeUseCase: ParseQrCodeUseCase, private val analyticsEventHandler: AnalyticsEventHandler, - private val sendDestinationAlertFactory: SendDestinationAlertFactory, ) : Model(), SendDestinationClickIntents { private val params: SendDestinationComponentParams = paramsContainer.require() @@ -89,17 +66,26 @@ internal class SendDestinationModel @Inject constructor( val uiState = _uiState.asStateFlow() private val analyticsCategoryName = params.analyticsCategoryName - private val userWallet = params.userWallet private val cryptoCurrency = params.cryptoCurrency - private var cryptoCurrencyStatus by Delegates.notNull() + private val userWalletId = params.userWalletId + + private val senderAddresses = MutableStateFlow>(emptyList()) private var validationJobHolder = JobHolder() init { + initSenderAddress() configDestinationNavigation() initialState() - subscribeOnCurrencyStatusUpdates() - subscribeOnQRScannerResult() + } + + private fun initSenderAddress() { + modelScope.launch { + senderAddresses.value = getNetworkAddressesUseCase.invokeSync(userWalletId, cryptoCurrency.network) + } + senderAddresses.onEach { + getWalletsAndRecent() + }.launchIn(modelScope) } private fun initialState() { @@ -157,95 +143,13 @@ internal class SendDestinationModel @Inject constructor( ) } - private fun subscribeOnCurrencyStatusUpdates() { - val isSingleWalletWithToken = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() - val isMultiCurrency = userWallet.isMultiCurrency - getCurrenciesStatusUpdates( - isSingleWalletWithToken = isSingleWalletWithToken, - isMultiCurrency = isMultiCurrency, - ) - } - - private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) { - getCurrencyStatus( - isSingleWalletWithToken = isSingleWalletWithToken, - isMultiCurrency = isMultiCurrency, - ).onEach { maybeCryptoCurrency -> - maybeCryptoCurrency.fold( - ifRight = { cryptoStatus -> - cryptoCurrencyStatus = cryptoStatus - getWalletsAndRecent() - }, - ifLeft = { - sendDestinationAlertFactory.getGenericErrorState { - onFailedTxEmailClick(it.toString()) - } - }, - ) - }.launchIn(modelScope) - } - - private fun getCurrencyStatus( - isSingleWalletWithToken: Boolean, - isMultiCurrency: Boolean, - ): Flow> { - return if (isMultiCurrency) { - getCurrencyStatusUpdatesUseCase( - userWalletId = userWallet.walletId, - currencyId = cryptoCurrency.id, - isSingleWalletWithTokens = isSingleWalletWithToken, - ) - } else { - getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWallet.walletId) - } - } - - private fun onFailedTxEmailClick(errorMessage: String? = null) { - saveBlockchainErrorUseCase( - error = BlockchainErrorInfo( - errorMessage = errorMessage.orEmpty(), - blockchainId = cryptoCurrency.network.id.value, - derivationPath = cryptoCurrency.network.derivationPath.value, - destinationAddress = "", - tokenSymbol = "", - amount = "", - fee = "", - ), - ) - - val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrNull() ?: return - - modelScope.launch { - sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo)) - } - } - - private fun subscribeOnQRScannerResult() { - listenToQrScanningUseCase(SourceType.SEND) - .getOrElse { emptyFlow() } - .onEach(::onQrCodeScanned) - .launchIn(modelScope) - } - - private fun onQrCodeScanned(address: String) { - parseQrCodeUseCase(address, cryptoCurrency).fold( - ifRight = { parsedCode -> - onRecipientAddressValueChange(parsedCode.address, EnterAddressSource.QRCode) - parsedCode.memo?.let { onRecipientMemoValueChange(it) } - }, - ifLeft = { - onRecipientAddressValueChange(address, EnterAddressSource.QRCode) - }, - ) - } - private fun getWalletsAndRecent() { combine( flow = getWalletsUseCase().conflate().map { waitForDelay(RECENT_LOAD_DELAY) { it.toAvailableWallets() } }, flow2 = getFixedTxHistoryItemsUseCase( - userWalletId = userWallet.walletId, + userWalletId = userWalletId, currency = cryptoCurrency, pageSize = RECENT_TX_SIZE, ).getOrElse { flowOf(emptyList()) }.map { @@ -253,15 +157,17 @@ internal class SendDestinationModel @Inject constructor( }.conflate(), ) { destinationWalletList, txHistoryList -> val isUtxoConsolidationAvailable = isUtxoConsolidationAvailableUseCase.invokeSync( - userWalletId = userWallet.walletId, + userWalletId = userWalletId, network = cryptoCurrency.network, ) + _uiState.update( SendDestinationRecentListTransformer( - cryptoCurrencyStatus = cryptoCurrencyStatus, + cryptoCurrency = cryptoCurrency, + senderAddress = senderAddresses.value.firstOrNull()?.address, isUtxoConsolidationAvailable = isUtxoConsolidationAvailable, destinationWalletList = destinationWalletList, - txHistoryList, + txHistoryList = txHistoryList, ), ) }.launchIn(modelScope) @@ -308,10 +214,10 @@ internal class SendDestinationModel @Inject constructor( _uiState.update(SendDestinationValidationStartedTransformer) val addressValidationResult = validateWalletAddressUseCase( - userWalletId = userWallet.walletId, + userWalletId = userWalletId, network = cryptoCurrency.network, address = address, - currencyAddress = cryptoCurrencyStatus.value.networkAddress?.availableAddresses, + senderAddresses = senderAddresses.value, ) val memoValidationResult = validateWalletMemoUseCase( memo = memo.orEmpty(), diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt index 674921ad63..6837943a94 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt @@ -2,7 +2,6 @@ package com.tangem.features.send.v2.subcomponents.destination.model.converters import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationRecipientListUM import com.tangem.features.send.v2.subcomponents.destination.model.transformers.WALLET_DEFAULT_COUNT import com.tangem.features.send.v2.subcomponents.destination.model.transformers.WALLET_KEY_TAG @@ -13,7 +12,7 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.toPersistentList internal class SendRecipientWalletListConverter( - private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val senderAddress: String?, private val isUtxoConsolidationAvailable: Boolean, ) : Converter, PersistentList> { @@ -25,14 +24,11 @@ internal class SendRecipientWalletListConverter( private fun List.filterWallets(): PersistentList { var walletsCounter = 0 - val currentAddress: String = runCatching { - cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value - }.getOrNull().orEmpty() return this.filterNotNull() .filter { val isCoin = it.cryptoCurrency is CryptoCurrency.Coin - val isNotSameAddress = it.address != currentAddress + val isNotSameAddress = it.address != senderAddress val isNotBlankAddress = it.address.isNotBlank() isNotBlankAddress && isCoin && (isNotSameAddress || isUtxoConsolidationAvailable) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt index f1a3f20621..8f670cab2d 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt @@ -1,6 +1,6 @@ package com.tangem.features.send.v2.subcomponents.destination.model.transformers -import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.txhistory.models.TxHistoryItem import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientHistoryListConverter @@ -9,7 +9,8 @@ import com.tangem.features.send.v2.subcomponents.destination.ui.state.Destinatio import com.tangem.utils.transformer.Transformer internal class SendDestinationRecentListTransformer( - private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val senderAddress: String?, + private val cryptoCurrency: CryptoCurrency, private val isUtxoConsolidationAvailable: Boolean, private val destinationWalletList: List, private val txHistoryList: List, @@ -19,11 +20,11 @@ internal class SendDestinationRecentListTransformer( return state.copy( wallets = SendRecipientWalletListConverter( - cryptoCurrencyStatus, - isUtxoConsolidationAvailable, + senderAddress = senderAddress, + isUtxoConsolidationAvailable = isUtxoConsolidationAvailable, ).convert(destinationWalletList), recent = SendRecipientHistoryListConverter( - cryptoCurrency = cryptoCurrencyStatus.currency, + cryptoCurrency = cryptoCurrency, ).convert(txHistoryList), ) }