Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-18 12:30:14 +03:00
commit ff282a1d7d
4 changed files with 171 additions and 144 deletions

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.Token
import com.tangem.common.CompletionResult import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve import com.tangem.common.card.EllipticCurve
@ -207,16 +206,10 @@ class TokensMiddleware {
is Currency.Blockchain -> { is Currency.Blockchain -> {
val derivationPath = currency.derivationPath?.let { DerivationPath(it) } val derivationPath = currency.derivationPath?.let { DerivationPath(it) }
val derivationParams = if (derivationStyle == null) { val derivationParams = derivationStyle?.let {
null when (derivationPath) {
} else { null -> DerivationParams.Default(derivationStyle)
if (derivationPath == null) { else -> DerivationParams.Custom(derivationPath)
DerivationParams.Default(derivationStyle)
} else {
when (derivationStyle) {
DerivationStyle.LEGACY -> DerivationParams.Custom(derivationPath)
DerivationStyle.NEW -> DerivationParams.Default(derivationStyle)
}
} }
} }

View file

@ -236,8 +236,7 @@ class MultiWalletMiddleware {
token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath token, blockchainNetwork.blockchain, blockchainNetwork.derivationPath
) )
})) }))
// we can't add Solana token's - they are not supported now if (tokens.isNotEmpty()) walletManager.addTokens(tokens)
if (walletManager !is SolanaWalletManager) walletManager.addTokens(tokens)
currenciesRepository.saveUpdatedCurrency( currenciesRepository.saveUpdatedCurrency(
cardId = scanResponse.card.cardId, cardId = scanResponse.card.cardId,

View file

@ -56,9 +56,8 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
is OnDestroy -> cancelAll() is OnDestroy -> cancelAll()
is OnTokenContractAddressChanged -> { is OnTokenContractAddressChanged -> {
updateAddButton()
val address = action.contractAddress.value val address = action.contractAddress.value
when (val error = ContractAddress.validateValue(address)) { when (val error = ContractAddress.validateValue(address)) {
null -> { null -> {
ContractAddress.removeError() ContractAddress.removeError()
@ -66,7 +65,6 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
AddCustomTokenError.FieldIsEmpty -> { AddCustomTokenError.FieldIsEmpty -> {
ContractAddress.removeError() ContractAddress.removeError()
lockTokenFields()
return return
} }
AddCustomTokenError.InvalidContractAddress -> { AddCustomTokenError.InvalidContractAddress -> {
@ -78,42 +76,47 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
if (!action.contractAddress.isUserInput) return if (!action.contractAddress.isUserInput) return
manageFoundTokenChanges(requestInfoAboutToken(address)) manageFoundTokenChanges(requestInfoAboutToken(address))
} }
is OnTokenNameChanged -> {
updateAddButton()
}
is OnTokenSymbolChanged -> {
updateAddButton()
}
is OnTokenDecimalsChanged -> {
updateAddButton()
}
is OnTokenNetworkChanged -> { is OnTokenNetworkChanged -> {
if (!action.blockchainNetwork.isUserInput) return if (!action.blockchainNetwork.isUserInput) return
updateAddButton()
// check the only ContractAddress without any side effects
val contractAddress = ContractAddress.getFieldValue<String>() val contractAddress = ContractAddress.getFieldValue<String>()
val error = ContractAddress.validateValue(contractAddress) val error = ContractAddress.validateValue(contractAddress)
if (contractAddress.isNotEmpty() && error == null) { if (error == null && contractAddress.isNotEmpty()) {
// token branch
manageFoundTokenChanges(requestInfoAboutToken(contractAddress)) manageFoundTokenChanges(requestInfoAboutToken(contractAddress))
} else {
// blockchain branch
val isAlreadyAdded = isBlockchainPersistIntoAppSavedTokensList(
selectedNetwork = action.blockchainNetwork.value
)
updateWarningAlreadyAdded(isAlreadyAdded)
updateAddButton()
} }
} }
is OnTokenNameChanged -> {
Name.validateField(action.tokenName.value)
updateAddButton()
}
is OnTokenSymbolChanged -> {
Symbol.validateField(action.tokenSymbol.value)
updateAddButton()
}
is OnTokenDecimalsChanged -> {
Decimals.validateField(action.tokenDecimals.value)
updateAddButton()
}
is OnTokenDerivationPathChanged -> { is OnTokenDerivationPathChanged -> {
DerivationPath.validateField(action.blockchainDerivationPath.value) val isAlreadyAdded = if (ContractAddress.isFilled()) {
val alreadyAdded = isTokenPersistIntoAppSavedTokensList( // token branch
selectedDerivationBlockchain = DerivationPath.getFieldValue() isTokenPersistIntoAppSavedTokensList(
) selectedDerivation = action.blockchainDerivationPath.value
if (alreadyAdded) { )
dispatchOnMain(Warning.Add(setOf(AddCustomTokenWarning.TokenAlreadyAdded)))
} else { } else {
dispatchOnMain(Warning.Remove(setOf(AddCustomTokenWarning.TokenAlreadyAdded))) // blockchain branch
isBlockchainPersistIntoAppSavedTokensList(
selectedDerivation = action.blockchainDerivationPath.value
)
} }
updateWarningAlreadyAdded(isAlreadyAdded)
updateAddButton() updateAddButton()
} }
is OnAddCustomTokenClicked -> { is OnAddCustomTokenClicked -> {
@ -140,19 +143,11 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
} }
private suspend fun updateAddButton() { private suspend fun updateWarningAlreadyAdded(isInAppSavedList: Boolean) {
val state = hubState if (isInAppSavedList) {
if (state.warnings.contains(AddCustomTokenWarning.TokenAlreadyAdded)) { AddCustomTokenWarning.TokenAlreadyAdded.add()
lockAddButton() } else {
return AddCustomTokenWarning.TokenAlreadyAdded.remove()
}
when {
// token
state.tokensOneFieldsIsFilled() -> lockAddButton()
// token
state.tokensFieldsIsFilled() && state.networkIsSelected() -> unlockAddButton()
// blockchain
else -> if (state.networkIsSelected()) unlockAddButton() else lockAddButton()
} }
} }
@ -185,81 +180,76 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
private suspend fun manageFoundTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) { private suspend fun manageFoundTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) {
val warningsAdd = mutableSetOf<AddCustomTokenWarning>() if (foundTokens.isEmpty()) {
val warningsRemove = mutableSetOf<AddCustomTokenWarning>() // token not found - it's completely custom
AddCustomTokenWarning.TokenAlreadyAdded.remove()
AddCustomTokenWarning.PotentialScamToken.add()
dispatchOnMain(SetFoundTokenId(null))
clearTokenFields()
unlockTokenFields()
updateAddButton()
return
}
// foundToken - contains all info about the token
val foundToken = foundTokens[0]
dispatchOnMain(SetFoundTokenId(foundToken.id))
when { when {
foundTokens.isEmpty() -> { foundToken.contracts.isEmpty() -> {
// token not found - it's completely custom Timber.e("Unexpected state -> throw to FB")
dispatchOnMain(SetFoundTokenId(null))
warningsAdd.add(AddCustomTokenWarning.PotentialScamToken)
warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded)
clearTokenFields()
unlockTokenFields()
} }
else -> { foundToken.contracts.size == 1 -> {
// foundToken - contains all info about the token // token with single contract address
val foundToken = foundTokens[0] val singleTokenContract = foundToken.contracts[0]
dispatchOnMain(SetFoundTokenId(foundToken.id)) fillTokenFields(foundToken, singleTokenContract)
when { val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList()
foundToken.contracts.isEmpty() -> { if (isInAppSavedTokens) {
Timber.e("Unexpected state -> throw to FB") lockTokenFields()
} lockAddButton()
foundToken.contracts.size == 1 -> { AddCustomTokenWarning.PotentialScamToken.replace(AddCustomTokenWarning.TokenAlreadyAdded)
// token with single contract address } else {
val singleTokenContract = foundToken.contracts[0] // not in the saved tokens list
fillTokenFields(foundToken, singleTokenContract) if (singleTokenContract.active) {
lockTokenFields()
val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList() unlockAddButton()
if (isInAppSavedTokens) { if (hubState.derivationPathIsSelected()) {
lockTokenFields() AddCustomTokenWarning.PotentialScamToken.add()
warningsAdd.add(AddCustomTokenWarning.TokenAlreadyAdded)
warningsRemove.add(AddCustomTokenWarning.PotentialScamToken)
} else { } else {
// not in the saved tokens list AddCustomTokenWarning.TokenAlreadyAdded.remove()
if (singleTokenContract.active) { AddCustomTokenWarning.PotentialScamToken.remove()
lockTokenFields()
if (hubState.derivationPathIsSelected()) {
warningsAdd.add(AddCustomTokenWarning.PotentialScamToken)
} else {
warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded)
warningsRemove.add(AddCustomTokenWarning.PotentialScamToken)
}
} else {
warningsAdd.add(AddCustomTokenWarning.PotentialScamToken)
}
unlockAddButton()
} }
} } else {
else -> { unlockAddButton()
warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded) AddCustomTokenWarning.PotentialScamToken.add()
warningsRemove.add(AddCustomTokenWarning.PotentialScamToken)
val dialog = DomainDialog.SelectTokenDialog(
items = foundToken.contracts,
networkIdConverter = { networkId ->
val blockchain = Blockchain.fromNetworkId(networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) {
throw DomainException.SelectTokeNetworkException(networkId)
}
hubState.blockchainToName(blockchain) ?: ""
},
onSelect = { selectedContract ->
hubScope.launch {
// find how to connect to the upper coroutineContext and dispatch through them
fillTokenFields(foundToken, selectedContract)
lockTokenFields()
}
},
)
dispatchOnMain(DomainGlobalAction.ShowDialog(dialog))
} }
} }
} }
} else -> {
AddCustomTokenWarning.PotentialScamToken.replace(AddCustomTokenWarning.TokenAlreadyAdded)
replaceWarnings(warningsAdd, warningsRemove) val dialog = DomainDialog.SelectTokenDialog(
items = foundToken.contracts,
networkIdConverter = { networkId ->
val blockchain = Blockchain.fromNetworkId(networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) {
throw DomainException.SelectTokeNetworkException(networkId)
}
hubState.blockchainToName(blockchain) ?: ""
},
onSelect = { selectedContract ->
hubScope.launch {
// find how to connect to the upper coroutineContext and dispatch through them
fillTokenFields(foundToken, selectedContract)
lockTokenFields()
unlockAddButton()
}
},
)
dispatchOnMain(DomainGlobalAction.ShowDialog(dialog))
}
}
} }
private suspend fun replaceWarnings( private suspend fun replaceWarnings(
@ -271,16 +261,44 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
} }
// private suspend fun validateUserTokenWithSavedTokensList(userToken: CustomCurrency.CustomToken) { private suspend fun updateAddButton() {
// val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList( val state = hubState
// userToken.token.id, if (state.warnings.contains(AddCustomTokenWarning.TokenAlreadyAdded)) {
// userToken.token.contractAddress, lockAddButton()
// userToken.network.toNetworkId(), return
// ) }
// if (isInAppSavedTokens) { when {
// replaceWarnings(mutableSetOf(AddCustomTokenWarning.TokenAlreadyAdded)) // token
// } state.tokensOneFieldsIsFilled() -> {
// } lockAddButton()
}
// token
state.tokensFieldsIsFilled() && state.networkIsSelected() -> {
unlockAddButton()
}
// blockchain
else -> {
if (state.networkIsSelected()) {
val alreadyAdded = isBlockchainPersistIntoAppSavedTokensList()
if (alreadyAdded) {
lockAddButton()
} else {
unlockAddButton()
}
} else {
lockAddButton()
}
}
}
}
private suspend fun lockAddButton() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
}
private suspend fun unlockAddButton() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
}
/** /**
* These are helper functions. * These are helper functions.
@ -289,11 +307,11 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
tokenId: String? = hubState.tokenId, tokenId: String? = hubState.tokenId,
tokenContractAddress: String = ContractAddress.getFieldValue(), tokenContractAddress: String = ContractAddress.getFieldValue(),
tokenNetworkId: String = Network.getFieldValue<Blockchain>().toNetworkId(), tokenNetworkId: String = Network.getFieldValue<Blockchain>().toNetworkId(),
selectedDerivationBlockchain: Blockchain = DerivationPath.getFieldValue() selectedDerivation: Blockchain = DerivationPath.getFieldValue()
): Boolean { ): Boolean {
val savedCurrencies = hubState.appSavedCurrencies ?: return false val savedCurrencies = hubState.appSavedCurrencies ?: return false
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivationBlockchain) val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
savedCurrencies.forEach { wrappedCurrency -> savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) { when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> {} is DomainWrapped.Currency.Blockchain -> {}
@ -312,17 +330,17 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
private fun isBlockchainPersistIntoAppSavedTokensList( private fun isBlockchainPersistIntoAppSavedTokensList(
selectedBlockchain: Blockchain, selectedNetwork: Blockchain = Network.getFieldValue(),
selectedDerivationBlockchain: Blockchain, selectedDerivation: Blockchain = DerivationPath.getFieldValue()
): Boolean { ): Boolean {
val state = hubState val state = hubState
val savedCurrencies = state.appSavedCurrencies ?: return false val savedCurrencies = state.appSavedCurrencies ?: return false
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivationBlockchain) val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
savedCurrencies.forEach { wrappedCurrency -> savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) { when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> { is DomainWrapped.Currency.Blockchain -> {
val isSameBlockchain = selectedBlockchain == wrappedCurrency.blockchain val isSameBlockchain = selectedNetwork == wrappedCurrency.blockchain
val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath
if (isSameBlockchain && isSameDerivationPath) return true if (isSameBlockchain && isSameDerivationPath) return true
} }
@ -400,13 +418,22 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
} }
} }
private fun CustomTokenFieldId.isFilled(): Boolean {
return when (this) {
ContractAddress -> getFieldValue<String>().isNotEmpty()
Network -> getFieldValue<Blockchain>() != Blockchain.Unknown
Name -> getFieldValue<String>().isNotEmpty()
Symbol -> getFieldValue<String>().isNotEmpty()
Decimals -> getFieldValue<String>().isNotEmpty()
DerivationPath -> getFieldValue<Blockchain>() != Blockchain.Unknown
}
}
/** /**
* The field is being validated. * The field is being validated.
* If there is an error, then it adds it to the field. * If there is an error, then it adds it to the field.
* If not, then data is collected from other user token fields, a token is generated and checked
* for content in saved tokens and app tokens list
*/ */
private suspend fun CustomTokenFieldId.validateField(value: Any): AddCustomTokenError? { private suspend fun CustomTokenFieldId.validateAndUpdateError(value: Any): AddCustomTokenError? {
val error = this.validateValue(value) val error = this.validateValue(value)
when (error) { when (error) {
null -> this.removeError() null -> this.removeError()
@ -456,14 +483,23 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
dispatchOnMain(action) dispatchOnMain(action)
} }
private suspend fun lockAddButton() { private suspend fun AddCustomTokenWarning.add() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false))) dispatchOnMain(Warning.Add(setOf(this)))
} }
private suspend fun unlockAddButton() { private suspend fun AddCustomTokenWarning.remove() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true))) dispatchOnMain(Warning.Remove(setOf(this)))
} }
private suspend fun AddCustomTokenWarning.replace(to: AddCustomTokenWarning) {
dispatchOnMain(Warning.Replace(setOf(this), setOf(to)))
}
// private suspend fun AddCustomTokenWarning.replace(replace: Boolean, to: AddCustomTokenWarning) {
// if (replace) dispatchOnMain(Warning.Replace(setOf(this), setOf(to)))
// }
override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState { override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState {
return when (action) { return when (action) {
is Init.SetAddedCurrencies -> { is Init.SetAddedCurrencies -> {

View file

@ -63,11 +63,10 @@ data class AddCustomTokenState(
val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals) val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals)
val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) } val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) }
val validator = StringIsEmptyValidator() val validator = StringIsEmptyValidator()
fieldsToCheck.forEach { field -> val errorsList = fieldsToCheck.mapNotNull { field ->
val error = validator.validate(field.data.value?.toString()) validator.validate(field.data.value?.toString())
if (error != null) return false
} }
return true return errorsList.size == 1
} }
fun networkIsSelected(): Boolean { fun networkIsSelected(): Boolean {