Skip to content

Commit

Permalink
Combine address parsers in single place
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelekol committed Nov 16, 2023
1 parent 259765f commit 141e58e
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package io.horizontalsystems.bankwallet.modules.address

import io.horizontalsystems.bankwallet.entities.Address

class AddressParserChain {
val handlers = mutableListOf<IAddressHandler>()
class AddressParserChain(
handlers: List<IAddressHandler> = emptyList(),
domainHandlers: List<IAddressHandler> = emptyList()
) {

fun addHandlers(handlers: List<IAddressHandler>) {
this.handlers.addAll(handlers)
}
private val domainHandlers = domainHandlers.toMutableList()
private val addressHandlers = handlers.toMutableList()

fun handlers(address: String): List<IAddressHandler> {
return handlers.filter {
fun supportedAddressHandlers(address: String): List<IAddressHandler> {
return addressHandlers.filter {
try {
it.isSupported(address)
} catch (t: Throwable) {
Expand All @@ -20,7 +21,11 @@ class AddressParserChain {
}

fun handle(address: String): Address? {
val handler = handlers(address).firstOrNull()
return handler?.parseAddress(address)
return supportedAddressHandlers(address).firstOrNull()?.parseAddress(address )
}

fun getAddressFromDomain(address: String): Address? {
return domainHandlers.firstOrNull { it.isSupported(address) }?.parseAddress(address)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ class AddressParserFactory(
private val udnApiKey: String,
) {

private fun parserChainHandlers(blockchainType: BlockchainType, withEns: Boolean = true): List<IAddressHandler> {
val udnHandler = AddressHandlerUdn(TokenQuery(blockchainType, TokenType.Native), "", udnApiKey)
val domainAddressHandlers = mutableListOf<IAddressHandler>(udnHandler)
private fun parserChainHandlers(blockchainType: BlockchainType): List<IAddressHandler> {
val addressHandlers = mutableListOf<IAddressHandler>()
when (blockchainType) {
BlockchainType.Bitcoin -> {
Expand Down Expand Up @@ -64,7 +62,6 @@ class AddressParserFactory(
BlockchainType.Gnosis,
BlockchainType.Fantom,
BlockchainType.ArbitrumOne -> {
domainAddressHandlers.add(AddressHandlerEns(blockchainType, EnsResolverHolder.resolver))
addressHandlers.add(AddressHandlerEvm(blockchainType))
}

Expand All @@ -79,25 +76,47 @@ class AddressParserFactory(
is BlockchainType.Unsupported -> {
}
}
if (withEns) {
addressHandlers.addAll(domainAddressHandlers)
}
return addressHandlers
}

fun parserChain(blockchainType: BlockchainType?, withEns: Boolean = true): AddressParserChain {
blockchainType?.let {
return AddressParserChain().apply {
addHandlers(parserChainHandlers(it, withEns))
private fun domainHandlers(blockchainType: BlockchainType): List<IAddressHandler> {
val udnHandler = AddressHandlerUdn(TokenQuery(blockchainType, TokenType.Native), "", udnApiKey)
val domainAddressHandlers = mutableListOf<IAddressHandler>(udnHandler)
when (blockchainType) {
BlockchainType.Ethereum,
BlockchainType.BinanceSmartChain,
BlockchainType.Polygon,
BlockchainType.Avalanche,
BlockchainType.Optimism,
BlockchainType.Gnosis,
BlockchainType.Fantom,
BlockchainType.ArbitrumOne -> {
domainAddressHandlers.add(AddressHandlerEns(blockchainType, EnsResolverHolder.resolver))
}

else -> {}
}
return domainAddressHandlers
}

val handlers = BlockchainType.supported.map {
parserChainHandlers(it, withEns)
}.flatten()
fun parserChain(blockchainType: BlockchainType?, withEns: Boolean = false): AddressParserChain {
val addressHandlers = mutableListOf<IAddressHandler>()
val domainHandlers = mutableListOf<IAddressHandler>()

return AddressParserChain().apply {
addHandlers(handlers)
blockchainType?.let {
addressHandlers.addAll(parserChainHandlers(it))
if (withEns) {
domainHandlers.addAll(domainHandlers(it))
}
} ?: run {
BlockchainType.supported.forEach {
addressHandlers.addAll(parserChainHandlers(it))
if (withEns) {
domainHandlers.addAll(domainHandlers(it))
}
}
}

return AddressParserChain(addressHandlers, domainHandlers)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,8 @@ class BalanceViewModel(
connectionResult = WalletConnectListViewModel.ConnectionResult.Error
})
} else {
val addressParserChain = addressParserFactory.parserChain(null, false)
val supportedHandlers = addressParserChain.handlers.filter { handler ->
handler.isSupported(scannedText)
}
val addressParserChain = addressParserFactory.parserChain(null)
val supportedHandlers = addressParserChain.supportedAddressHandlers(scannedText)
if (supportedHandlers.isNotEmpty()) {
val address = supportedHandlers.first().parseAddress(scannedText)
openSendTokenSelect = OpenSendTokenSelect(supportedHandlers.map { it.blockchainType }, address.hex)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModelProvider
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.providers.Translator
import io.horizontalsystems.bankwallet.modules.address.AddressParserFactory
import io.horizontalsystems.bankwallet.modules.contacts.model.Contact
import io.horizontalsystems.bankwallet.modules.contacts.model.ContactAddress
import io.horizontalsystems.bankwallet.modules.contacts.viewmodel.AddressViewModel
Expand Down Expand Up @@ -38,7 +39,15 @@ object ContactsModule {
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return AddressViewModel(contactUid, App.contactsRepository, App.appConfigProvider.udnApiKey, App.evmBlockchainManager, App.marketKit, contactAddress, definedAddresses) as T
return AddressViewModel(
contactUid,
App.contactsRepository,
AddressParserFactory(App.appConfigProvider.udnApiKey),
App.evmBlockchainManager,
App.marketKit,
contactAddress,
definedAddresses
) as T
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,25 @@ import io.horizontalsystems.bankwallet.core.managers.MarketKitWrapper
import io.horizontalsystems.bankwallet.core.order
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.AddressParserChain
import io.horizontalsystems.bankwallet.modules.address.AddressParserFactory
import io.horizontalsystems.bankwallet.modules.address.AddressValidationException
import io.horizontalsystems.bankwallet.modules.address.IAddressHandler
import io.horizontalsystems.bankwallet.modules.contacts.ContactAddressParser
import io.horizontalsystems.bankwallet.modules.contacts.ContactsRepository
import io.horizontalsystems.bankwallet.modules.contacts.model.ContactAddress
import io.horizontalsystems.bankwallet.ui.compose.TranslatableString
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.launch
import kotlinx.coroutines.withContext

class AddressViewModel(
private val contactUid: String?,
private val contactsRepository: ContactsRepository,
private val udnApiKey: String,
private val addressParserFactory: AddressParserFactory,
evmBlockchainManager: EvmBlockchainManager,
marketKit: MarketKitWrapper,
contactAddress: ContactAddress?,
Expand Down Expand Up @@ -82,7 +68,7 @@ class AddressViewModel(
}

private var blockchain = contactAddress?.blockchain ?: availableBlockchains.first()
private var addressParser: ContactAddressParser = addressParser(blockchain)
private var addressParser: AddressParserChain = addressParserFactory.parserChain(blockchain.type, true)

var uiState by mutableStateOf(uiState())
private set
Expand All @@ -97,7 +83,7 @@ class AddressViewModel(

fun onEnterBlockchain(blockchain: Blockchain) {
this.blockchain = blockchain
this.addressParser = addressParser(blockchain)
this.addressParser = addressParserFactory.parserChain(blockchain.type, true)

emitUiState()

Expand All @@ -120,7 +106,7 @@ class AddressViewModel(
emitUiState()

addressState = try {
val parsedAddress = addressParser.parseAddress(address)
val parsedAddress = parseAddress(addressParser, address.trim())
ensureActive()
contactsRepository.validateAddress(contactUid, ContactAddress(blockchain, parsedAddress.hex))
DataState.Success(parsedAddress)
Expand All @@ -132,66 +118,6 @@ class AddressViewModel(
}
}

private fun addressParser(blockchain: Blockchain): ContactAddressParser {
val udnHandler = AddressHandlerUdn(TokenQuery(blockchain.type, TokenType.Native), "", udnApiKey)
val domainAddressHandlers = mutableListOf<IAddressHandler>(udnHandler)
val rawAddressHandlers = mutableListOf<IAddressHandler>()
when (blockchain.type) {
BlockchainType.Bitcoin -> {
val network = MainNet()
rawAddressHandlers.add(AddressHandlerBase58(network, blockchain.type))
rawAddressHandlers.add(AddressHandlerBech32(network, blockchain.type))
}
BlockchainType.BitcoinCash -> {
val network = MainNetBitcoinCash()
rawAddressHandlers.add(AddressHandlerBase58(network, blockchain.type))
rawAddressHandlers.add(AddressHandlerBitcoinCash(network))
}
BlockchainType.ECash -> {
val network = MainNetECash()
rawAddressHandlers.add(AddressHandlerBase58(network, blockchain.type))
rawAddressHandlers.add(AddressHandlerBitcoinCash(network))
}
BlockchainType.Litecoin -> {
val network = MainNetLitecoin()
rawAddressHandlers.add(AddressHandlerBase58(network, blockchain.type))
rawAddressHandlers.add(AddressHandlerBech32(network, blockchain.type))
}
BlockchainType.Dash -> {
val network = MainNetDash()
rawAddressHandlers.add(AddressHandlerBase58(network, blockchain.type))
}
BlockchainType.BinanceChain -> {
rawAddressHandlers.add(AddressHandlerBinanceChain())
}
BlockchainType.Zcash -> {
//No validation
rawAddressHandlers.add(AddressHandlerPure(blockchain.type))
}
BlockchainType.Ethereum,
BlockchainType.BinanceSmartChain,
BlockchainType.Polygon,
BlockchainType.Avalanche,
BlockchainType.Optimism,
BlockchainType.Gnosis,
BlockchainType.Fantom,
BlockchainType.ArbitrumOne -> {
domainAddressHandlers.add(AddressHandlerEns(blockchain.type, EnsResolverHolder.resolver))
rawAddressHandlers.add(AddressHandlerEvm(blockchain.type))
}
BlockchainType.Solana -> {
rawAddressHandlers.add(AddressHandlerSolana())
}
BlockchainType.Tron -> {
rawAddressHandlers.add(AddressHandlerTron())
}
is BlockchainType.Unsupported -> {
}
}

return ContactAddressParser(domainAddressHandlers, rawAddressHandlers, blockchain)
}

private fun uiState() = UiState(
headerTitle = title,
editingAddress = editingAddress,
Expand All @@ -208,6 +134,27 @@ class AddressViewModel(
uiState = uiState()
}

private suspend fun parseAddress(addressParser: AddressParserChain, value: String): Address = withContext(Dispatchers.IO) {
try {
val resolvedAddress = addressParser.getAddressFromDomain(value)?.hex ?: value
parse(resolvedAddress, addressParser.supportedAddressHandlers(resolvedAddress))
} catch (error: Throwable) {
throw AddressValidationException.Invalid(error, blockchain.name)
}
}

private fun parse(value: String, supportedHandlers: List<IAddressHandler>): Address {
if (supportedHandlers.isEmpty()) {
throw AddressValidationException.Unsupported(blockchain.name)
}

try {
return supportedHandlers.first().parseAddress(value)
} catch (t: Throwable) {
throw AddressValidationException.Invalid(t, blockchain.name)
}
}

data class UiState(
val headerTitle: TranslatableString,
val editingAddress: ContactAddress?,
Expand Down

0 comments on commit 141e58e

Please sign in to comment.