Updated on 2026-08-14
This commit is contained in:
parent
8aa6ce0100
commit
eaf587e7b8
12 changed files with 85 additions and 32 deletions
|
|
@ -4,6 +4,8 @@ import android.net.Uri
|
|||
import androidx.core.text.isDigitsOnly
|
||||
import com.tangem.blockchain.blockchains.near.NearWalletManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.NameResolver
|
||||
import com.tangem.blockchain.common.ResolveAddressResult
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
|
|
@ -20,6 +22,28 @@ class DefaultWalletAddressServiceRepository(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAddressServiceRepository {
|
||||
|
||||
override suspend fun resolveAddress(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
): ResolveAddressResult {
|
||||
return withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
)
|
||||
|
||||
if (walletManager is NameResolver) {
|
||||
walletManager.resolve(address)
|
||||
} else {
|
||||
ResolveAddressResult.NotSupported
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun validateAddress(userWalletId: UserWalletId, network: Network, address: String): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.transaction
|
||||
|
||||
import com.tangem.blockchain.common.ResolveAddressResult
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.ParsedQrCode
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -9,6 +10,8 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
*/
|
||||
interface WalletAddressServiceRepository {
|
||||
|
||||
suspend fun resolveAddress(userWalletId: UserWalletId, network: Network, address: String): ResolveAddressResult
|
||||
|
||||
suspend fun validateAddress(userWalletId: UserWalletId, network: Network, address: String): Boolean
|
||||
|
||||
fun validateMemo(network: Network, memo: String): Boolean
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ sealed class AddressValidation {
|
|||
sealed class Success : AddressValidation() {
|
||||
data object Valid : Success()
|
||||
data object ValidXAddress : Success()
|
||||
data class ValidNamedAddress(val blockchainAddress: String) : Success()
|
||||
}
|
||||
|
||||
sealed class Error : AddressValidation() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.transaction.usecase
|
|||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.ResolveAddressResult
|
||||
import com.tangem.domain.models.network.CryptoCurrencyAddress
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
|
|
@ -24,44 +25,59 @@ class ValidateWalletAddressUseCase(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
currencyAddress: Set<NetworkAddress.Address>?,
|
||||
): AddressValidationResult {
|
||||
val decodedXAddress = BlockchainUtils.decodeRippleXAddress(address, network.rawId)
|
||||
val isUtxoConsolidationAvailable =
|
||||
walletManagersFacade.checkUtxoConsolidationAvailability(userWalletId, network)
|
||||
|
||||
val addressToValidate = decodedXAddress?.address ?: address
|
||||
val isCurrentAddress = currencyAddress?.any { it.value == addressToValidate } ?: true
|
||||
|
||||
val isForbidSelfSend = isCurrentAddress && !isUtxoConsolidationAvailable
|
||||
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()
|
||||
}
|
||||
}
|
||||
currencyAddresses: Set<NetworkAddress.Address>?,
|
||||
): AddressValidationResult = validateAddressInternal(
|
||||
userWalletId,
|
||||
network,
|
||||
address,
|
||||
isCurrentAddress = { toValidate ->
|
||||
currencyAddresses?.any { it.value == toValidate } ?: true
|
||||
},
|
||||
)
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
senderAddresses: List<CryptoCurrencyAddress>,
|
||||
): AddressValidationResult = validateAddressInternal(
|
||||
userWalletId,
|
||||
network,
|
||||
address,
|
||||
isCurrentAddress = { toValidate ->
|
||||
senderAddresses.any { it.address == toValidate }
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun validateAddressInternal(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
isCurrentAddress: (String) -> Boolean,
|
||||
): AddressValidationResult {
|
||||
val decodedXAddress = BlockchainUtils.decodeRippleXAddress(address, network.rawId)
|
||||
val isUtxoConsolidationAvailable =
|
||||
walletManagersFacade.checkUtxoConsolidationAvailability(userWalletId, network)
|
||||
|
||||
val addressToValidate = decodedXAddress?.address ?: address
|
||||
val isCurrentAddress = senderAddresses.any { it.address == addressToValidate }
|
||||
|
||||
val isForbidSelfSend = isCurrentAddress && !isUtxoConsolidationAvailable
|
||||
val current = isCurrentAddress(addressToValidate)
|
||||
val isForbidSelfSend = current && !isUtxoConsolidationAvailable
|
||||
val isValidAddress = walletAddressServiceRepository.validateAddress(userWalletId, network, addressToValidate)
|
||||
|
||||
return when {
|
||||
!isValidAddress -> AddressValidation.Error.InvalidAddress.left()
|
||||
!isValidAddress -> {
|
||||
val resolveAddressResult = walletAddressServiceRepository.resolveAddress(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
address = addressToValidate,
|
||||
)
|
||||
|
||||
if (resolveAddressResult is ResolveAddressResult.Resolved) {
|
||||
AddressValidation.Success.ValidNamedAddress(resolveAddressResult.address).right()
|
||||
} else {
|
||||
AddressValidation.Error.InvalidAddress.left()
|
||||
}
|
||||
}
|
||||
isForbidSelfSend -> AddressValidation.Error.AddressInWallet.left()
|
||||
decodedXAddress != null -> AddressValidation.Success.ValidXAddress.right()
|
||||
else -> AddressValidation.Success.Valid.right()
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ internal class DefaultSendComponent @AssistedInject constructor(
|
|||
private fun getFeeComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val state = model.uiState.value
|
||||
val sendAmount = (state.amountUM as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.actualAddress
|
||||
val cryptoCurrencyStatus = model.cryptoCurrencyStatusFlow.value
|
||||
val feeCryptoCurrencyStatus = model.feeCryptoCurrencyStatusFlow.value
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
enteredMemo = destinationUM?.memoTextField?.value,
|
||||
reduceAmountBy = amountState?.reduceAmountBy.orZero(),
|
||||
isIgnoreReduce = amountState?.isIgnoreReduce == true,
|
||||
enteredDestination = destinationUM?.addressTextField?.value,
|
||||
enteredDestination = destinationUM?.addressTextField?.actualAddress,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
)
|
||||
|
|
@ -316,7 +316,7 @@ internal class SendConfirmModel @Inject constructor(
|
|||
|
||||
private fun verifyAndSendTransaction() {
|
||||
val amountValue = amountState?.amountTextField?.cryptoAmount?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.actualAddress ?: return
|
||||
val memo = destinationUM?.memoTextField?.value
|
||||
val fee = feeSelectorUM?.selectedFee
|
||||
val feeValue = fee?.amount?.value ?: return
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ internal class SendModel @Inject constructor(
|
|||
} else {
|
||||
val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: error("Invalid destination")
|
||||
val amountUM = uiState.value.amountUM as? AmountState.Data ?: error("Invalid amount")
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.value
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.actualAddress
|
||||
val enteredMemo = destinationUM.memoTextField?.value
|
||||
val enteredAmount = amountUM.amountTextField.cryptoAmount.value ?: error("Invalid amount")
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ internal class DefaultNFTSendComponent @AssistedInject constructor(
|
|||
|
||||
private fun getFeeComponent(factoryContext: AppComponentContext): ComposableContentComponent {
|
||||
val state = model.uiState.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.value
|
||||
val destinationAddress = (state.destinationUM as? DestinationUM.Content)?.addressTextField?.actualAddress
|
||||
return if (destinationAddress != null) {
|
||||
SendFeeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
|
||||
val confirmData: ConfirmData
|
||||
get() = ConfirmData(
|
||||
enteredDestination = destinationUM?.addressTextField?.value,
|
||||
enteredDestination = destinationUM?.addressTextField?.actualAddress,
|
||||
enteredMemo = destinationUM?.memoTextField?.value,
|
||||
fee = feeSelectorUM?.selectedFee,
|
||||
feeError = (feeUM?.feeSelectorUM as? FeeSelectorUM.Error)?.error,
|
||||
|
|
@ -258,7 +258,7 @@ internal class NFTSendConfirmModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun verifyAndSendTransaction() {
|
||||
val destination = destinationUM?.addressTextField?.value ?: return
|
||||
val destination = destinationUM?.addressTextField?.actualAddress ?: return
|
||||
val memo = destinationUM?.memoTextField?.value
|
||||
val fee = feeSelectorUM?.selectedFee ?: return
|
||||
val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: return
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ internal class NFTSendModel @Inject constructor(
|
|||
val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: error("Invalid destination")
|
||||
val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
?: error("Invalid owner address")
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.value
|
||||
val enteredDestinationAddress = destinationUM.addressTextField.actualAddress
|
||||
val enteredMemo = destinationUM.memoTextField?.value
|
||||
val nftAsset = NFTSdkAssetConverter.convertBack(params.nftAsset).second
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ internal class SendDestinationValidationResultTransformer(
|
|||
}.leftOrNull()
|
||||
|
||||
val shouldDisableMemo = shouldDisableMemo()
|
||||
val blockchainAddress =
|
||||
(addressValidationResult.getOrNull() as? AddressValidation.Success.ValidNamedAddress)?.blockchainAddress
|
||||
|
||||
return state.copy(
|
||||
isValidating = false,
|
||||
|
|
@ -38,6 +40,7 @@ internal class SendDestinationValidationResultTransformer(
|
|||
addressTextField = state.addressTextField.copy(
|
||||
error = addressErrorText?.let(::resourceReference),
|
||||
isError = state.addressTextField.value.isNotEmpty() && !isValidAddress,
|
||||
blockchainAddress = blockchainAddress,
|
||||
),
|
||||
memoTextField = state.memoTextField?.copy(
|
||||
value = state.memoTextField.value.takeIf { !shouldDisableMemo }.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,13 @@ internal sealed class DestinationTextFieldUM {
|
|||
val isError: Boolean = false,
|
||||
val error: TextReference? = null,
|
||||
val isValuePasted: Boolean,
|
||||
) : DestinationTextFieldUM()
|
||||
// if value is human-readable address, this field contains the actual blockchain address
|
||||
val blockchainAddress: String? = null,
|
||||
) : DestinationTextFieldUM() {
|
||||
|
||||
val actualAddress: String
|
||||
get() = blockchainAddress ?: value
|
||||
}
|
||||
|
||||
data class RecipientMemo(
|
||||
override val value: String,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue