Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-18 13:35:51 +05:00
parent a4340e40a8
commit d7c4218f92
8 changed files with 74 additions and 37 deletions

View file

@ -1,8 +1,19 @@
package com.tangem.utils.transformer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
/**
* Transforms state to updated state.
*/
interface Transformer<S> {
fun transform(prevState: S): S
}
fun <T> MutableStateFlow<T>.update(transformer: Transformer<T>) {
update { transformer.transform(this.value) }
}
fun <T> MutableStateFlow<T>.updateAll(vararg transformers: Transformer<T>) {
transformers.forEach { transformer -> update { transformer.transform(this.value) } }
}

View file

@ -25,6 +25,7 @@ dependencies {
implementation(projects.domain.models)
implementation(projects.domain.legacy)
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
implementation(projects.domain.wallets.models)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)

View file

@ -1,7 +1,18 @@
package com.tangem.domain.transaction.error
sealed class ValidateAddressError {
data object AddressInWallet : ValidateAddressError()
data object InvalidAddress : ValidateAddressError()
data class DataError(val throwable: Throwable) : ValidateAddressError()
import arrow.core.Either
typealias AddressValidationResult = Either<AddressValidation.Error, AddressValidation.Success>
sealed class AddressValidation {
sealed class Success : AddressValidation() {
data object Valid : Success()
data object ValidXAddress : Success()
}
sealed class Error : AddressValidation() {
data object AddressInWallet : Error()
data object InvalidAddress : Error()
data class DataError(val throwable: Throwable) : Error()
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.domain.transaction.error
sealed class ValidateMemoError {
data object InvalidMemo : ValidateMemoError()
data class DataError(val throwable: Throwable) : ValidateMemoError()
}

View file

@ -1,14 +1,15 @@
package com.tangem.domain.transaction.usecase
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.error.ValidateAddressError
import com.tangem.domain.transaction.error.AddressValidation
import com.tangem.domain.transaction.error.AddressValidationResult
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.lib.crypto.BlockchainUtils
/**
* Use case for validating wallet address.
@ -23,18 +24,23 @@ class ValidateWalletAddressUseCase(
network: Network,
address: String,
currencyAddress: Set<NetworkAddress.Address>?,
): Either<ValidateAddressError, Unit> {
): AddressValidationResult {
val isUtxoConsolidationAvailable =
walletManagersFacade.checkUtxoConsolidationAvailability(userWalletId, network)
val isCurrentAddress = currencyAddress?.any { it.value == address } ?: true
val isForbidSelfSend = isCurrentAddress && !isUtxoConsolidationAvailable
val isValidAddress = walletAddressServiceRepository.validateAddress(userWalletId, network, address)
val decodedXAddress = BlockchainUtils.decodeRippleXAddress(address, network.id.value)
val addressToValidate = decodedXAddress?.address ?: address
val isValidAddress = walletAddressServiceRepository.validateAddress(userWalletId, network, addressToValidate)
return when {
!isValidAddress -> ValidateAddressError.InvalidAddress.left()
isForbidSelfSend -> ValidateAddressError.AddressInWallet.left()
else -> Unit.right()
!isValidAddress -> AddressValidation.Error.InvalidAddress.left()
isForbidSelfSend -> AddressValidation.Error.AddressInWallet.left()
decodedXAddress != null -> AddressValidation.Success.ValidXAddress.right()
else -> AddressValidation.Success.Valid.right()
}
}
}

View file

@ -1,8 +1,11 @@
package com.tangem.domain.transaction.usecase
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.error.ValidateMemoError
/**
* Use case for validating wallet memo.
@ -11,7 +14,16 @@ class ValidateWalletMemoUseCase(
private val walletAddressServiceRepository: WalletAddressServiceRepository,
) {
operator fun invoke(network: Network, memo: String): Either<Throwable, Boolean> = Either.catch {
walletAddressServiceRepository.validateMemo(network, memo)
operator fun invoke(network: Network, memo: String): Either<ValidateMemoError, Unit> {
return try {
val isValidMemo = walletAddressServiceRepository.validateMemo(network, memo)
if (isValidMemo) {
Unit.right()
} else {
ValidateMemoError.InvalidMemo.left()
}
} catch (ex: Throwable) {
ValidateMemoError.DataError(ex).left()
}
}
}

View file

@ -36,8 +36,9 @@ import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.AddressValidation
import com.tangem.domain.transaction.error.AddressValidationResult
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.error.ValidateAddressError
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
@ -116,7 +117,7 @@ internal class SendModel @Inject constructor(
private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter,
@DelayedWork private val coroutineScope: CoroutineScope,
private val innerRouter: InnerSendRouter,
private val appRouter: AppRouter,
appRouter: AppRouter,
paramsContainer: ParamsContainer,
validateTransactionUseCase: ValidateTransactionUseCase,
getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
@ -693,7 +694,7 @@ internal class SendModel @Inject constructor(
}.saveIn(memoValidationJobHolder)
}
private suspend fun validateAddress(value: String): Either<ValidateAddressError, Unit> = runCatching {
private suspend fun validateAddress(value: String): AddressValidationResult = runCatching {
val maybeValidAddress = validateWalletAddressUseCase(
userWalletId = userWalletId,
network = cryptoCurrency.network,
@ -702,10 +703,10 @@ internal class SendModel @Inject constructor(
)
onEnteredValidAddress(maybeValidAddress.isLeft())
maybeValidAddress
}.getOrElse { ValidateAddressError.DataError(it).left() }
}.getOrElse { AddressValidation.Error.DataError(it).left() }
private fun validateMemo(value: String?): Boolean {
return value?.let { validateWalletMemoUseCase(cryptoCurrency.network, it).getOrElse { false } } ?: true
return value?.let { validateWalletMemoUseCase(cryptoCurrency.network, it).isRight() } != false
}
private suspend fun checkIfXrpAddressValue(value: String): Boolean {

View file

@ -1,20 +1,18 @@
package com.tangem.features.send.impl.presentation.state.recipient
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.transaction.error.ValidateAddressError
import com.tangem.domain.transaction.error.AddressValidation
import com.tangem.domain.transaction.error.AddressValidationResult
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.features.send.impl.R
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
import com.tangem.features.send.impl.presentation.state.SendUiState
import com.tangem.features.send.impl.presentation.state.StateRouter
import com.tangem.utils.Provider
import kotlinx.collections.immutable.toPersistentList
import timber.log.Timber
internal class RecipientSendFactory(
private val stateRouterProvider: Provider<StateRouter>,
@ -66,10 +64,7 @@ internal class RecipientSendFactory(
)
}
fun getOnRecipientAddressValidState(
value: String,
maybeValidAddress: Either<ValidateAddressError, Unit>,
): SendUiState {
fun getOnRecipientAddressValidState(value: String, maybeValidAddress: AddressValidationResult): SendUiState {
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val state = currentStateProvider()
val isEditState = stateRouterProvider().isEditState
@ -78,10 +73,7 @@ internal class RecipientSendFactory(
val isValidMemo = validateWalletMemoUseCase(
memo = recipientState.memoTextField?.value.orEmpty(),
network = cryptoCurrencyStatus.currency.network,
).getOrElse {
Timber.e("Failed to validateWalletMemoUseCase: $it")
false
}
).isRight()
return state.copyWrapped(
isEditState = isEditState,
@ -92,10 +84,10 @@ internal class RecipientSendFactory(
error = maybeValidAddress.fold(
ifLeft = {
when (it) {
ValidateAddressError.InvalidAddress -> resourceReference(
AddressValidation.Error.InvalidAddress -> resourceReference(
R.string.send_recipient_address_error,
)
ValidateAddressError.AddressInWallet -> resourceReference(
AddressValidation.Error.AddressInWallet -> resourceReference(
R.string.send_error_address_same_as_wallet,
)
else -> null
@ -143,10 +135,7 @@ internal class RecipientSendFactory(
val isValidMemo = validateWalletMemoUseCase(
memo = value,
network = cryptoCurrencyStatus.currency.network,
).getOrElse {
Timber.e("Failed to validateWalletMemoUseCase: $it")
false
}
).isRight()
return state.copyWrapped(
isEditState = isEditState,