Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add address validation to WithdrawCex module #6321

Merged
merged 1 commit into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,56 +1,156 @@
package io.horizontalsystems.bankwallet.modules.withdrawcex

import io.horizontalsystems.bankwallet.entities.Address
import io.horizontalsystems.bankwallet.entities.DataState
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerBase58
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerBech32
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerBinanceChain
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerBitcoinCash
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerEns
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerEvm
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerPure
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerSolana
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerTron
import io.horizontalsystems.bankwallet.modules.address.AddressHandlerUdn
import io.horizontalsystems.bankwallet.modules.address.EnsResolverHolder
import io.horizontalsystems.bankwallet.modules.address.IAddressHandler
import io.horizontalsystems.bankwallet.modules.contacts.ContactAddressParser
import io.horizontalsystems.bitcoincash.MainNetBitcoinCash
import io.horizontalsystems.bitcoinkit.MainNet
import io.horizontalsystems.dashkit.MainNetDash
import io.horizontalsystems.ecash.MainNetECash
import io.horizontalsystems.litecoinkit.MainNetLitecoin
import io.horizontalsystems.marketkit.models.Blockchain
import io.horizontalsystems.marketkit.models.BlockchainType
import io.horizontalsystems.marketkit.models.TokenQuery
import io.horizontalsystems.marketkit.models.TokenType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class CexWithdrawAddressService {

private var address: Address? = null
private var addressError: Throwable? = null
private var address: String? = null
private var addressParser: ContactAddressParser? = null
private var state: DataState<Address>? = null
set(value) {
field = value
_stateFlow.update { value }
}

private val _stateFlow = MutableStateFlow(
State(
address = address,
addressError = addressError,
canBeSend = address != null && addressError == null
)
)
private val _stateFlow = MutableStateFlow<DataState<Address>?>(null)
val stateFlow = _stateFlow.asStateFlow()

fun setAddress(address: Address?) {
suspend fun setAddress(address: String?) {
this.address = address

validateAddress()
}

suspend fun setBlockchain(blockchain: Blockchain?) {
this.addressParser = blockchain?.let { addressParser(it) }

emitState()
validateAddress()
}

private fun validateAddress() {
addressError = null
val address = this.address ?: return
private var validationJob: Job? = null

private suspend fun validateAddress() = withContext(Dispatchers.IO) {
validationJob?.cancel()

try {
// validate address
} catch (e: Exception) {
addressError = e
val address = address
if (address.isNullOrEmpty()) {
state = null
return@withContext
}
}

private fun emitState() {
_stateFlow.update {
State(
address = address,
addressError = addressError,
canBeSend = address != null && addressError == null
)
validationJob = launch {
state = DataState.Loading

state = try {
val parsedAddress = addressParser?.parseAddress(address) ?: Address(address)
ensureActive()
DataState.Success(parsedAddress)
} catch (error: Throwable) {
ensureActive()
DataState.Error(error)
}
}
}

data class State(
val address: Address?,
val addressError: Throwable?,
val canBeSend: Boolean
)
private fun addressParser(blockchain: Blockchain): ContactAddressParser {
val udnHandler = AddressHandlerUdn(TokenQuery(blockchain.type, TokenType.Native), "")
val domainAddressHandlers = mutableListOf<IAddressHandler>(udnHandler)
val rawAddressHandlers = mutableListOf<IAddressHandler>()

when (blockchain.type) {
BlockchainType.Bitcoin -> {
val network = MainNet()
rawAddressHandlers.add(AddressHandlerBase58(network))
rawAddressHandlers.add(AddressHandlerBech32(network))
}

BlockchainType.BitcoinCash -> {
val network = MainNetBitcoinCash()
rawAddressHandlers.add(AddressHandlerBase58(network))
rawAddressHandlers.add(AddressHandlerBitcoinCash(network))
}

BlockchainType.ECash -> {
val network = MainNetECash()
rawAddressHandlers.add(AddressHandlerBase58(network))
rawAddressHandlers.add(AddressHandlerBitcoinCash(network))
}

BlockchainType.Litecoin -> {
val network = MainNetLitecoin()
rawAddressHandlers.add(AddressHandlerBase58(network))
rawAddressHandlers.add(AddressHandlerBech32(network))
}

BlockchainType.Dash -> {
val network = MainNetDash()
rawAddressHandlers.add(AddressHandlerBase58(network))
}

BlockchainType.BinanceChain -> {
rawAddressHandlers.add(AddressHandlerBinanceChain())
}

BlockchainType.Zcash -> {
//No validation
rawAddressHandlers.add(AddressHandlerPure())
}

BlockchainType.Ethereum,
BlockchainType.BinanceSmartChain,
BlockchainType.Polygon,
BlockchainType.Avalanche,
BlockchainType.Optimism,
BlockchainType.Gnosis,
BlockchainType.Fantom,
BlockchainType.ArbitrumOne -> {
domainAddressHandlers.add(AddressHandlerEns(EnsResolverHolder.resolver))
rawAddressHandlers.add(AddressHandlerEvm())
}

BlockchainType.Solana -> {
rawAddressHandlers.add(AddressHandlerSolana())
}

BlockchainType.Tron -> {
rawAddressHandlers.add(AddressHandlerTron())
}

is BlockchainType.Unsupported -> {
}
}

return ContactAddressParser(domainAddressHandlers, rawAddressHandlers, blockchain)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.horizontalsystems.bankwallet.core.providers.CexAsset
import io.horizontalsystems.bankwallet.core.providers.CexWithdrawNetwork
import io.horizontalsystems.bankwallet.core.providers.CoinzixCexProvider
import io.horizontalsystems.bankwallet.entities.Address
import io.horizontalsystems.bankwallet.entities.DataState
import io.horizontalsystems.bankwallet.modules.coinzixverify.CoinzixVerificationMode
import io.horizontalsystems.bankwallet.modules.contacts.ContactsRepository
import io.horizontalsystems.bankwallet.modules.contacts.model.Contact
Expand Down Expand Up @@ -55,10 +56,10 @@ class WithdrawCexViewModel(
networkName = network.networkName,
availableBalance = amountState.availableBalance,
amountCaution = amountState.amountCaution,
addressError = addressState.addressError,
addressState = addressState,
feeItem = feeItem,
feeFromAmount = feeFromAmount,
canBeSend = amountState.canBeSend && addressState.canBeSend
canBeSend = amountState.canBeSend && addressState?.dataOrNull != null
)
)
private set
Expand Down Expand Up @@ -105,7 +106,7 @@ class WithdrawCexViewModel(
return FeeItem(coinAmount, currencyAmount)
}

private fun handleUpdatedAddressState(addressState: CexWithdrawAddressService.State) {
private fun handleUpdatedAddressState(addressState: DataState<Address>?) {
this.addressState = addressState

emitState()
Expand All @@ -117,10 +118,10 @@ class WithdrawCexViewModel(
networkName = network.networkName,
availableBalance = amountState.availableBalance,
amountCaution = amountState.amountCaution,
addressError = addressState.addressError,
addressState = addressState,
feeItem = feeItem,
feeFromAmount = feeFromAmount,
canBeSend = amountState.canBeSend && addressState.canBeSend
canBeSend = amountState.canBeSend && addressState?.dataOrNull != null
)
}
}
Expand All @@ -130,12 +131,17 @@ class WithdrawCexViewModel(
}

fun onEnterAddress(v: String) {
addressService.setAddress(Address(v))
viewModelScope.launch {
addressService.setAddress(v)
}
}

fun onSelectNetwork(network: CexWithdrawNetwork) {
this.network = network
amountService.setNetwork(network)
viewModelScope.launch {
addressService.setBlockchain(network.blockchain)
}
}

fun onSelectFeeFromAmount(feeFromAmount: Boolean) {
Expand All @@ -155,7 +161,7 @@ class WithdrawCexViewModel(
.getFormattedFull()
}

val address = addressState.address!!
val address = addressState?.dataOrNull!!
val contact = contactsRepository.getContactsFiltered(
blockchainType,
addressQuery = address.hex
Expand All @@ -178,7 +184,7 @@ class WithdrawCexViewModel(
return cexProvider.withdraw(
cexAsset.id,
network.id,
addressState.address!!.hex,
addressState?.dataOrNull!!.hex,
amountState.amount!!
)
}
Expand All @@ -202,6 +208,6 @@ data class WithdrawCexUiState(
val amountCaution: HSCaution?,
val feeItem: FeeItem,
val feeFromAmount: Boolean,
val addressError: Throwable?,
val addressState: DataState<Address>?,
val canBeSend: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ fun WithdrawCexScreen(
modifier = Modifier.padding(horizontal = 16.dp),
initial = null,
hint = stringResource(id = R.string.Watch_Address_Hint),
state = null,
state = uiState.addressState,
textPreprocessor = TextPreprocessorImpl,
onChangeFocus = {
//isFocused = it
Expand Down