Updated on 2026-08-14

This commit is contained in:
Tangem 2022-04-17 16:13:10 +03:00
parent 4fa8ad0f9b
commit d6d43479e2
9 changed files with 557 additions and 313 deletions

View file

@ -26,6 +26,8 @@ import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.R
private class Navigation
fun FragmentActivity.openFragment(
screen: AppScreen,
addToBackstack: Boolean,

View file

@ -71,7 +71,7 @@ fun TokenNetworkView(screenFieldData: ScreenFieldData, state: AddCustomTokenStat
itemList = networkField.itemList,
selectedItem = networkField.data,
isEnabled = screenFieldData.viewState.isEnabled,
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
textFieldConverter = { state.blockchainToName(it) ?: notSelected },
) { domainStore.dispatch(AddCustomTokenAction.OnTokenNetworkChanged(Field.Data(it))) }
SpacerH8()
}
@ -123,11 +123,11 @@ fun TokenDerivationPathView(screenFieldData: ScreenFieldData, state: AddCustomTo
itemList = networkField.itemList,
selectedItem = networkField.data,
isEnabled = screenFieldData.viewState.isEnabled,
textFieldConverter = { state.convertBlockchainName(it, notSelected) },
textFieldConverter = { state.blockchainToName(it) ?: notSelected },
dropdownItemView = { blockchain ->
val derivationPathLabel = state.convertDerivationPathLabel(blockchain, notSelected)
val blockchainName = state.convertBlockchainName(blockchain, notSelected)
TitleSubtitle(derivationPathLabel, blockchainName)
val derivationPathName = state.blockchainToName(blockchain, true) ?: notSelected
val blockchainName = state.blockchainToName(blockchain) ?: notSelected
TitleSubtitle(derivationPathName, blockchainName)
}
) { domainStore.dispatch(AddCustomTokenAction.OnTokenDerivationPathChanged(Field.Data(it))) }
SpacerH8()

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
@ -13,12 +14,12 @@ import com.tangem.domain.common.KeyWalletPublicKey
import com.tangem.domain.common.ScanResponse
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.features.addCustomToken.CompleteData
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenAction
import com.tangem.domain.features.addCustomToken.redux.AddedCurrencies
import com.tangem.domain.redux.domainStore
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
@ -28,19 +29,16 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tokens.BlockchainNetwork
import com.tangem.tap.features.wallet.redux.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
class TokensMiddleware {
val tokensMiddleware: Middleware<AppState> = { dispatch, state ->
val tokensMiddleware: Middleware<AppState> = { _, _ ->
{ next ->
{ action ->
when (action) {
@ -58,8 +56,10 @@ class TokensMiddleware {
val isTestcard = scanResponse?.card?.isTestCard ?: false
scope.launch {
val currencies = async { currenciesRepository.getSupportedTokens(isTestcard)
.filter(action.supportedBlockchains?.toSet()) }
val currencies = async {
currenciesRepository.getSupportedTokens(isTestcard)
.filter(action.supportedBlockchains?.toSet())
}
val delay = async { delay(600) }
delay.await()
store.dispatchOnMain(TokensAction.LoadCurrencies.Success(currencies.await()))
@ -69,6 +69,7 @@ class TokensMiddleware {
private fun handleSaveChanges(action: TokensAction.SaveChanges) {
val scanResponse = store.state.globalState.scanResponse ?: return
//TODO: bad things happens.
val currentTokens = store.state.tokensState.addedWallets.toTokens()
val currentBlockchains = store.state.tokensState.addedWallets.toBlockchains(
store.state.tokensState.derivationStyle
@ -83,44 +84,42 @@ class TokensMiddleware {
removeCurrenciesIfNeeded(blockchainsToRemove, tokensToRemove)
if (tokensToAdd.isEmpty() && blockchainsToAdd.isEmpty()) {
store.dispatchDebugErrorNotification("Nothing to save")
store.dispatch(NavigationAction.PopBackTo())
return
}
val derivationStyle = scanResponse.card.derivationStyle
val currencyList = blockchainsToAdd.map {
Currency.Blockchain(it, it.derivationPath(derivationStyle)?.rawPath)
} + tokensToAdd.map {
Currency.Token(it.token, it.blockchain, it.blockchain.derivationPath(derivationStyle)?.rawPath)
}
if (scanResponse.supportsHdWallet()) {
deriveMissingBlockchains(scanResponse, blockchainsToAdd, tokensToAdd)
deriveMissingBlockchains(scanResponse, currencyList) {
submitAdd(it, currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
} else {
submitAdd(blockchainsToAdd, tokensToAdd, scanResponse)
store.dispatch(NavigationAction.PopBackTo())
submitAdd(scanResponse, currencyList)
store.dispatchOnMain(NavigationAction.PopBackTo())
}
}
private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) {
val tokensState = store.state.tokensState
val addedTokensList = tokensState.addedTokens.map {
DomainWrapped.TokenWithBlockchain(it.token.copy(), it.blockchain)
}
val addedBlockchains = tokensState.addedBlockchains.map { it }
val addedCurrencies = AddedCurrencies(addedTokensList, addedBlockchains)
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
val callback = fun(data: CompleteData) {
Timber.e("Yoooohhhoooo")
}
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(callback))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
}
private fun deriveMissingBlockchains(
scanResponse: ScanResponse,
blockchains: List<Blockchain>,
tokens: List<TokenWithBlockchain>
currencyList: List<Currency>,
onSuccess: (ScanResponse) -> Unit,
) {
val derivationDataList = listOfNotNull(
getDerivations(EllipticCurve.Secp256k1, scanResponse, blockchains, tokens),
getDerivations(EllipticCurve.Ed25519, scanResponse, blockchains, tokens)
getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList)
)
val derivations = derivationDataList.map { it.derivations }.toMap()
val derivations = derivationDataList.associate { it.derivations }
if (derivations.isEmpty()) {
onSuccess(scanResponse)
return
}
scope.launch {
val result = tangemSdkManager.derivePublicKeys(
@ -145,10 +144,8 @@ class TokensMiddleware {
derivedKeys = updatedDerivedKeys
)
store.dispatchOnMain(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
submitAdd(blockchains, tokens, updatedScanResponse)
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(NavigationAction.PopBackTo())
onSuccess(updatedScanResponse)
}
is CompletionResult.Failure -> {
store.dispatchErrorNotification(TapError.CustomError("Error adding tokens"))
@ -160,20 +157,31 @@ class TokensMiddleware {
private fun getDerivations(
curve: EllipticCurve,
scanResponse: ScanResponse,
blockchains: List<Blockchain>,
tokens: List<TokenWithBlockchain>
currencyList: List<Currency>,
): DerivationData? {
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
val derivationPathsCandidates = (blockchains + tokens.map { it.blockchain }).distinct()
.mapNotNull { it.derivationPath(scanResponse.card.derivationStyle) }
val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter {
it.getSupportedCurves().contains(curve)
}.mapNotNull {
it.derivationPath(scanResponse.card.derivationStyle)
}
val customTokensCandidates = currencyList.filter {
it.blockchain.getSupportedCurves().contains(curve)
}.mapNotNull { it.derivationPath }.map { DerivationPath(it) }
val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct()
if (bothCandidates.isEmpty()) return null
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
val alreadyDerivedKeys: ExtendedPublicKeysMap =
scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) }
val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
if (toDerive.isEmpty()) return null
return DerivationData(
derivations = mapKeyOfWalletPublicKey to toDerive,
alreadyDerivedKeys = alreadyDerivedKeys,
@ -188,20 +196,48 @@ class TokensMiddleware {
)
private fun submitAdd(
blockchains: List<Blockchain>, tokens: List<TokenWithBlockchain>, scanResponse: ScanResponse,
scanResponse: ScanResponse,
currencyList: List<Currency>,
) {
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
val derivationStyle = scanResponse.card.derivationStyle
(blockchains.mapNotNull {
val walletManager = factory.makeWalletManagerForApp(
scanResponse, it,
scanResponse.card.derivationStyle?.let { DerivationParams.Default(it) }
) ?: return@mapNotNull null
WalletAction.MultiWallet.AddBlockchain(BlockchainNetwork.fromWalletManager(walletManager), walletManager)
} + tokens.map {
val blockchainNetwork = BlockchainNetwork(it.blockchain, scanResponse.card)
WalletAction.MultiWallet.AddToken(it.token, blockchainNetwork)
}).forEach { store.dispatchOnMain(it) }
val addActions = currencyList.mapNotNull { currency ->
when (currency) {
is Currency.Blockchain -> {
val derivationPath = currency.derivationPath?.let { DerivationPath(it) }
val derivationParams = if (derivationStyle == null) {
null
} else {
if (derivationPath == null) {
DerivationParams.Default(derivationStyle)
} else {
when (derivationStyle) {
DerivationStyle.LEGACY -> DerivationParams.Custom(derivationPath)
DerivationStyle.NEW -> DerivationParams.Default(derivationStyle)
}
}
}
val walletManager = factory.makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = currency.blockchain,
derivationParams = derivationParams
) ?: return@mapNotNull null
val blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager)
WalletAction.MultiWallet.AddBlockchain(blockchainNetwork, walletManager)
}
is Currency.Token -> {
val rawDerivationPath = currency.derivationPath
?: currency.blockchain.derivationPath(derivationStyle)?.rawPath
val blockchainNetwork = BlockchainNetwork(currency.blockchain, rawDerivationPath, emptyList())
WalletAction.MultiWallet.AddToken(currency.token, blockchainNetwork)
}
}
}
addActions.forEach { store.dispatchOnMain(it) }
}
private fun removeCurrenciesIfNeeded(blockchains: List<Blockchain>, tokens: List<Token>) {
@ -221,4 +257,45 @@ class TokensMiddleware {
}
}
private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean {
return currency.derivationPath?.let {
!scanResponse.hasDerivation(currency.blockchain, it)
} ?: false
}
private fun handleAddingCustomToken(action: TokensAction.PrepareAndNavigateToAddCustomToken) {
val onAddCustomToken = fun(customCurrency: CustomCurrency) {
val scanResponse = store.state.globalState.scanResponse ?: return
fun submitAndPopBack(scanResponse: ScanResponse, currencyList: List<Currency>) {
submitAdd(scanResponse, currencyList)
// pop from the AddCustomTokenScreen
store.dispatchOnMain(NavigationAction.PopBackTo())
store.dispatchOnMain(NavigationAction.PopBackTo())
}
val currency = Currency.fromCustomCurrency(customCurrency)
val isNeedToDerive = isNeedToDerive(scanResponse, currency)
val currencyList = listOf(currency)
if (isNeedToDerive) {
deriveMissingBlockchains(scanResponse, currencyList) {
submitAndPopBack(it, currencyList)
}
} else {
submitAndPopBack(scanResponse, currencyList)
}
}
val addedCurrencies = store.state.walletState.wallets.map { walletStore ->
walletStore.walletsData.map { walletData -> walletData.currency }
}.flatten().map {
when (it) {
is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(it.blockchain, it.derivationPath)
is Currency.Token -> DomainWrapped.Currency.Token(it.token, it.blockchain, it.derivationPath)
}
}
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(onAddCustomToken))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
}
}

View file

@ -1,59 +0,0 @@
package com.tangem.domain.features.addCustomToken
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.form.BaseFieldDataConverter
import com.tangem.domain.common.form.FieldId
/**
[REDACTED_AUTHOR]
*/
enum class CompleteDataType {
Blockchain, Token
}
sealed class CompleteData() {
class CustomBlockchain(
val network: Blockchain,
val derivationPath: String?
) : CompleteData() {
class Converter : BaseFieldDataConverter<CustomBlockchain>() {
override fun getConvertedData(): CustomBlockchain {
val network = collectedData[CustomTokenFieldId.Network] as Blockchain
val derivationPath = collectedData[CustomTokenFieldId.DerivationPath] as? String
return CustomBlockchain(network, derivationPath)
}
override fun getIdToCollect(): List<FieldId> = listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath)
}
}
class CustomToken(
val token: Token,
val network: Blockchain,
val derivationPath: String?,
) : CompleteData() {
class Converter(val tokenId: String?) : BaseFieldDataConverter<CustomToken>() {
override fun getConvertedData(): CustomToken {
val token = Token(
name = collectedData[CustomTokenFieldId.Name] as String,
symbol = collectedData[CustomTokenFieldId.Symbol] as String,
contractAddress = collectedData[CustomTokenFieldId.ContractAddress] as String,
decimals = (collectedData[CustomTokenFieldId.Decimals] as String).toInt(),
id = tokenId,
)
return CustomToken(
token,
collectedData[CustomTokenFieldId.Network] as Blockchain,
collectedData[CustomTokenFieldId.DerivationPath] as? String,
)
}
override fun getIdToCollect(): List<FieldId> = CustomTokenFieldId.values().toList()
}
}
}

View file

@ -0,0 +1,83 @@
package com.tangem.domain.features.addCustomToken
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.Token
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.form.BaseFieldDataConverter
import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.redux.AddCustomTokenState
/**
[REDACTED_AUTHOR]
*/
enum class CompleteDataType {
Blockchain, Token
}
sealed class CustomCurrency(
val network: Blockchain,
val derivationPath: DerivationPath?,
) {
class CustomBlockchain(
network: Blockchain,
derivationPath: DerivationPath?
) : CustomCurrency(network, derivationPath) {
class Converter(
private val derivationStyle: DerivationStyle?
) : BaseFieldDataConverter<CustomBlockchain>() {
override fun getConvertedData(): CustomBlockchain {
val mainNetwork = collectedData[CustomTokenFieldId.Network] as Blockchain
val derivationPathNetwork = collectedData[CustomTokenFieldId.DerivationPath] as Blockchain
val derivationPath = AddCustomTokenState.getDerivationPath(
mainNetwork,
derivationPathNetwork,
derivationStyle
)
return CustomBlockchain(mainNetwork, derivationPath)
}
override fun getIdToCollect(): List<FieldId> = listOf(CustomTokenFieldId.Network, CustomTokenFieldId.DerivationPath)
}
}
class CustomToken(
val token: Token,
network: Blockchain,
derivationPath: DerivationPath?
) : CustomCurrency(network, derivationPath) {
class Converter(
private val tokenId: String?,
private val derivationStyle: DerivationStyle?
) : BaseFieldDataConverter<CustomToken>() {
override fun getConvertedData(): CustomToken {
val mainNetwork = collectedData[CustomTokenFieldId.Network] as Blockchain
val derivationPathNetwork = collectedData[CustomTokenFieldId.DerivationPath] as Blockchain
val derivationPath = AddCustomTokenState.getDerivationPath(
mainNetwork,
derivationPathNetwork,
derivationStyle
)
val token = Token(
name = collectedData[CustomTokenFieldId.Name] as String,
symbol = collectedData[CustomTokenFieldId.Symbol] as String,
contractAddress = collectedData[CustomTokenFieldId.ContractAddress] as String,
decimals = (collectedData[CustomTokenFieldId.Decimals] as String).toInt(),
id = tokenId,
)
return CustomToken(
token,
collectedData[CustomTokenFieldId.Network] as Blockchain,
derivationPath,
)
}
override fun getIdToCollect(): List<FieldId> = CustomTokenFieldId.values().toList()
}
}
}

View file

@ -2,13 +2,13 @@ package com.tangem.domain.features.addCustomToken.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.form.Field
import com.tangem.domain.common.form.FieldId
import com.tangem.domain.features.addCustomToken.AddCustomTokenError
import com.tangem.domain.features.addCustomToken.AddCustomTokenWarning
import com.tangem.domain.features.addCustomToken.CompleteData
import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId
import com.tangem.network.api.tangemTech.Coins
import org.rekotlin.Action
/**
@ -16,9 +16,8 @@ import org.rekotlin.Action
*/
sealed class AddCustomTokenAction : Action {
sealed class Init : AddCustomTokenAction() {
data class SetAddedCurrencies(val addedCurrencies: AddedCurrencies) : AddCustomTokenAction()
data class SetOnAddTokenCallback(val callback: (CompleteData) -> Unit) : AddCustomTokenAction()
data class SetAddedCurrencies(val addedCurrencies: List<DomainWrapped.Currency>) : AddCustomTokenAction()
data class SetOnAddTokenCallback(val callback: (CustomCurrency) -> Unit) : AddCustomTokenAction()
}
object OnCreate : AddCustomTokenAction() {
@ -36,22 +35,16 @@ sealed class AddCustomTokenAction : Action {
data class OnTokenDecimalsChanged(val tokenDecimals: Field.Data<String>) : AddCustomTokenAction()
object OnAddCustomTokenClicked : AddCustomTokenAction()
data class SetFoundTokenId(val id: String?) : AddCustomTokenAction()
// form fields
data class UpdateForm(val state: AddCustomTokenState) : AddCustomTokenAction()
object ClearTokenFields : AddCustomTokenAction()
data class FillTokenFields(
val token: Coins.CheckAddressResponse.Token,
val contract: Coins.CheckAddressResponse.Token.Contract,
) : AddCustomTokenAction()
sealed class FieldError : AddCustomTokenAction() {
data class Add(val id: CustomTokenFieldId, val error: AddCustomTokenError) : FieldError()
data class Remove(val id: CustomTokenFieldId) : FieldError()
}
data class SetTokenId(val id: String) : AddCustomTokenAction()
// warnings
sealed class Warning : AddCustomTokenAction() {
data class Add(val warnings: Set<AddCustomTokenWarning>) : Warning()

View file

@ -3,9 +3,13 @@ package com.tangem.domain.features.addCustomToken.redux
import android.webkit.ValueCallback
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.DomainDialog
import com.tangem.domain.DomainException
import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.domain.common.form.*
@ -19,7 +23,7 @@ import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.network.api.tangemTech.Coins
import com.tangem.network.api.tangemTech.TangemTechService
import kotlinx.coroutines.cancel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
@ -39,17 +43,6 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
return storeState.copy(addCustomTokensState = newHubState)
}
private val contractAddressValidator: TokenContractAddressValidator
get() = hubState.getValidator(ContractAddress)
private val nameValidator: TokenNameValidator
get() = hubState.getValidator(Name)
private val symbolValidator: TokenSymbolValidator
get() = hubState.getValidator(Symbol)
private val decimalsValidator: TokenDecimalsValidator
get() = hubState.getValidator(Decimals)
val networkValidator: TokenNetworkValidator
get() = hubState.getValidator(Network)
override suspend fun handleAction(
action: Action,
storeState: DomainState,
@ -58,29 +51,29 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
if (action !is AddCustomTokenAction) return
when (action) {
is Init.SetAddedCurrencies -> {}
is Init.SetOnAddTokenCallback -> {}
is OnCreate -> {
// hubState.addedCurrencies.guard {
// return throwUnAppropriateInitialization("addedTokens")
// }
hubState.appSavedCurrencies.guard {
return throwUnAppropriateInitialization("addedTokens")
}
}
is OnDestroy -> hubScope.cancel()
is OnDestroy -> cancelAll()
is OnTokenContractAddressChanged -> {
updateAddButton()
val address = action.contractAddress.value
when (val error = ContractAddress.validate(address)) {
when (val error = ContractAddress.validateValue(address)) {
null -> {
ContractAddress.removeError()
dispatchOnMain(unlockTokenFieldsAction())
unlockTokenFields()
}
AddCustomTokenError.FieldIsEmpty -> {
ContractAddress.removeError()
dispatchOnMain(lockTokenFieldsAction())
lockTokenFields()
return
}
AddCustomTokenError.InvalidContractAddress -> {
ContractAddress.addError(error)
dispatchOnMain(unlockTokenFieldsAction())
unlockTokenFields()
return
}
else -> {}
@ -88,75 +81,89 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
if (!action.contractAddress.isUserInput) return
manageTokenChanges(requestInfoAboutContractAddress(address))
manageFoundTokenChanges(requestInfoAboutToken(address))
}
is OnTokenNetworkChanged -> {
if (!action.blockchainNetwork.isUserInput) return
updateAddButton()
// check the only ContractAddress without any side effects
val contractAddress = ContractAddress.getFieldValue<String>()
val error = ContractAddress.validate(contractAddress)
if (error == null) {
manageTokenChanges(requestInfoAboutContractAddress(contractAddress))
} else {
val error = ContractAddress.validateValue(contractAddress)
if (contractAddress.isNotEmpty() && error == null) {
manageFoundTokenChanges(requestInfoAboutToken(contractAddress))
}
}
is OnTokenNameChanged -> {
Name.addOrRemoveError(Name.validate(action.tokenName.value))
Name.validateField(action.tokenName.value)
updateAddButton()
}
is OnTokenSymbolChanged -> {
Symbol.addOrRemoveError(Symbol.validate(action.tokenSymbol.value))
Symbol.validateField(action.tokenSymbol.value)
updateAddButton()
}
is OnTokenDecimalsChanged -> {
Decimals.addOrRemoveError(Decimals.validate(action.tokenDecimals.value))
Decimals.validateField(action.tokenDecimals.value)
updateAddButton()
}
is ClearTokenFields -> {
Name.setFieldValue(Field.Data("", false))
Symbol.setFieldValue(Field.Data("", false))
Decimals.setFieldValue(Field.Data("", false))
dispatchOnMain(UpdateForm(hubState))
}
is FillTokenFields -> {
val token = action.token
val contract = action.contract
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: Blockchain.Unknown
Network.setFieldValue(Field.Data(blockchain, false))
Name.setFieldValue(Field.Data(token.name, false))
Symbol.setFieldValue(Field.Data(token.symbol, false))
Decimals.setFieldValue(Field.Data(contract.decimalCount.toString(), false))
dispatchOnMain(UpdateForm(hubState))
is OnTokenDerivationPathChanged -> {
DerivationPath.validateField(action.blockchainDerivationPath.value)
val alreadyAdded = isTokenPersistIntoAppSavedTokensList(
selectedDerivationBlockchain = DerivationPath.getFieldValue()
)
if (alreadyAdded) {
dispatchOnMain(Warning.Add(setOf(AddCustomTokenWarning.TokenAlreadyAdded)))
} else {
dispatchOnMain(Warning.Remove(setOf(AddCustomTokenWarning.TokenAlreadyAdded)))
}
updateAddButton()
}
is OnAddCustomTokenClicked -> {
// if (hubState.allFieldsIsEmpty()) {
dispatchOnMain(
DomainGlobalAction.ShowDialog(DomainDialog.DialogError(
AddCustomTokenError.InvalidDerivationPath
)))
return
// }
when {
!hubState.customTokensFieldsIsEmpty() && !hubState.networkIsEmpty() -> {
hubState.getCompleteData(CompleteDataType.Token)
val state = hubState
val completeData = when {
state.tokensFieldsIsFilled() && state.networkIsSelected() -> {
state.gatherUserToken()
}
!state.tokensFieldsIsFilled() && state.networkIsSelected() -> {
state.gatherBlockchain()
}
else -> null
}
if (completeData == null) {
// normally it can't be, because the AddButton must be blocked
} else {
hubScope.launch(Dispatchers.Main) {
state.onTokenAddCallback?.invoke(completeData)
}
// !hubState.customTokensFieldsIsEmpty() && -> {
// }
}
// if (true) {
// dispatchOnMain(NavigationAction.PopBackTo())
// hubState.onTokenAddCallback?.invoke()
// }
}
else -> {}
}
}
private suspend fun requestInfoAboutContractAddress(
private suspend fun updateAddButton() {
val state = hubState
if (state.warnings.contains(AddCustomTokenWarning.TokenAlreadyAdded)) {
lockAddButton()
return
}
when {
// token
state.tokensOneFieldsIsFilled() -> lockAddButton()
// token
state.tokensFieldsIsFilled() && state.networkIsSelected() -> unlockAddButton()
// blockchain
else -> if (state.networkIsSelected()) unlockAddButton() else lockAddButton()
}
}
private suspend fun requestInfoAboutToken(
contractAddress: String,
): List<Coins.CheckAddressResponse.Token> {
val tangemTechServiceManager = requireNotNull(hubState.tangemTechServiceManager)
dispatchOnMain(Screen.UpdateTokenFields(listOf(ContractAddress to ViewStates.TokenField(isLoading = true))))
val field = hubState.getField<TokenBlockchainField>(Network)
val selectedNetworkId: String? = field.data.value.let {
if (it == Blockchain.Unknown) null else it
@ -179,67 +186,72 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
return result
}
private suspend fun manageTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) {
val toAddWarnings = mutableSetOf<AddCustomTokenWarning>()
val toRemoveWarnings = mutableSetOf<AddCustomTokenWarning>()
private suspend fun manageFoundTokenChanges(foundTokens: List<Coins.CheckAddressResponse.Token>) {
val warningsAdd = mutableSetOf<AddCustomTokenWarning>()
val warningsRemove = mutableSetOf<AddCustomTokenWarning>()
when {
foundTokens.isEmpty() -> {
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(ClearTokenFields)
dispatchOnMain(unlockTokenFieldsAction())
// token not found - it's completely custom
dispatchOnMain(SetFoundTokenId(null))
warningsAdd.add(AddCustomTokenWarning.PotentialScamToken)
warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded)
clearTokenFields()
unlockTokenFields()
}
else -> {
val token = foundTokens[0]
val contracts = token.contracts
// foundToken - contains all info about the token
val foundToken = foundTokens[0]
dispatchOnMain(SetFoundTokenId(foundToken.id))
when {
contracts.isEmpty() -> {
// TODO: refactoring:
foundToken.contracts.isEmpty() -> {
Timber.e("Unexpected state -> throw to FB")
}
contracts.size == 1 -> {
val contract = contracts[0]
val isPersistIntoTheAppAddedTokenList = isPersistIntoTheAppAddedTokenList(token, contract)
foundToken.contracts.size == 1 -> {
// token with single contract address
val singleTokenContract = foundToken.contracts[0]
fillTokenFields(foundToken, singleTokenContract)
if (isPersistIntoTheAppAddedTokenList) {
toAddWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
dispatchOnMain(lockTokenFieldsAction())
val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList()
if (isInAppSavedTokens) {
lockTokenFields()
warningsAdd.add(AddCustomTokenWarning.TokenAlreadyAdded)
warningsRemove.add(AddCustomTokenWarning.PotentialScamToken)
} else {
toRemoveWarnings.add(AddCustomTokenWarning.TokenAlreadyAdded)
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
val isStandardDerivation = true
val tokenContract = token.contracts[0]
if (tokenContract.active && isStandardDerivation) {
toRemoveWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(FillTokenFields(token, contract))
dispatchOnMain(lockTokenFieldsAction())
// not in the saved tokens list
if (singleTokenContract.active) {
lockTokenFields()
if (hubState.derivationPathIsSelected()) {
warningsAdd.add(AddCustomTokenWarning.PotentialScamToken)
} else {
warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded)
warningsRemove.add(AddCustomTokenWarning.PotentialScamToken)
}
} else {
toAddWarnings.add(AddCustomTokenWarning.PotentialScamToken)
dispatchOnMain(ClearTokenFields)
dispatchOnMain(unlockTokenFieldsAction())
warningsAdd.add(AddCustomTokenWarning.PotentialScamToken)
}
unlockAddButton()
}
}
else -> {
warningsRemove.add(AddCustomTokenWarning.TokenAlreadyAdded)
warningsRemove.add(AddCustomTokenWarning.PotentialScamToken)
val dialog = DomainDialog.SelectTokenDialog(
items = contracts,
items = foundToken.contracts,
networkIdConverter = { networkId ->
val blockchain = Blockchain.fromNetworkId(networkId)
if (blockchain == null || blockchain == Blockchain.Unknown) {
throw DomainException.SelectTokeNetworkException(networkId)
}
hubState.convertBlockchainName(blockchain, "")
hubState.blockchainToName(blockchain) ?: ""
},
onSelect = { selectedContract ->
hubScope.launch {
// find how to connect to the upper coroutineContext and dispatch through them
dispatchOnMain(FillTokenFields(token, selectedContract))
dispatchOnMain(lockTokenFieldsAction())
fillTokenFields(foundToken, selectedContract)
lockTokenFields()
}
},
)
@ -249,15 +261,86 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
}
if (toAddWarnings.isNotEmpty() || toRemoveWarnings.isNotEmpty()) {
dispatchOnMain(Warning.Replace(toRemoveWarnings.toSet(), toAddWarnings.toSet()))
replaceWarnings(warningsAdd, warningsRemove)
}
private suspend fun replaceWarnings(
warningsAdd: MutableSet<AddCustomTokenWarning> = mutableSetOf(),
warningsRemove: MutableSet<AddCustomTokenWarning> = mutableSetOf(),
) {
if (warningsAdd.isNotEmpty() || warningsRemove.isNotEmpty()) {
dispatchOnMain(Warning.Replace(warningsRemove.toSet(), warningsAdd.toSet()))
}
}
private fun isPersistIntoTheAppAddedTokenList(
token: Coins.CheckAddressResponse.Token,
contract: Coins.CheckAddressResponse.Token.Contract
): Boolean = false
// private suspend fun validateUserTokenWithSavedTokensList(userToken: CustomCurrency.CustomToken) {
// val isInAppSavedTokens = isTokenPersistIntoAppSavedTokensList(
// userToken.token.id,
// userToken.token.contractAddress,
// userToken.network.toNetworkId(),
// )
// if (isInAppSavedTokens) {
// replaceWarnings(mutableSetOf(AddCustomTokenWarning.TokenAlreadyAdded))
// }
// }
/**
* These are helper functions.
*/
private fun isTokenPersistIntoAppSavedTokensList(
tokenId: String? = hubState.tokenId,
tokenContractAddress: String = ContractAddress.getFieldValue(),
tokenNetworkId: String = Network.getFieldValue<Blockchain>().toNetworkId(),
selectedDerivationBlockchain: Blockchain = DerivationPath.getFieldValue()
): Boolean {
val savedCurrencies = hubState.appSavedCurrencies ?: return false
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivationBlockchain)
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> {}
is DomainWrapped.Currency.Token -> {
val sameId = tokenId == wrappedCurrency.token.id
val sameAddress = tokenContractAddress == wrappedCurrency.token.contractAddress
val sameBlockchain = Blockchain.fromNetworkId(tokenNetworkId) == wrappedCurrency.blockchain
val sameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath
if (sameId && sameAddress && sameBlockchain && sameDerivationPath) {
return true
}
}
}
}
return false
}
private fun isBlockchainPersistIntoAppSavedTokensList(
selectedBlockchain: Blockchain,
selectedDerivationBlockchain: Blockchain,
): Boolean {
val state = hubState
val savedCurrencies = state.appSavedCurrencies ?: return false
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivationBlockchain)
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> {
val isSameBlockchain = selectedBlockchain == wrappedCurrency.blockchain
val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath
if (isSameBlockchain && isSameDerivationPath) return true
}
is DomainWrapped.Currency.Token -> {}
}
}
return false
}
private fun getDerivationPathFromSelectedBlockchain(
selectedDerivationBlockchain: Blockchain
): com.tangem.common.hdWallet.DerivationPath? = AddCustomTokenState.getDerivationPath(
mainNetwork = Network.getFieldValue(),
derivationNetwork = selectedDerivationBlockchain,
derivationStyle = hubState.cardDerivationStyle
)
private suspend fun CustomTokenFieldId.addError(error: AddCustomTokenError) {
dispatchOnMain(FieldError.Add(this, error))
@ -267,13 +350,6 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
dispatchOnMain(FieldError.Remove(this))
}
private suspend fun CustomTokenFieldId.addOrRemoveError(error: AddCustomTokenError?) {
when (error) {
null -> removeError()
else -> addError(error)
}
}
private inline fun <reified T> CustomTokenFieldId.getField(): T {
val state = hubState
val value = when (this) {
@ -310,66 +386,108 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
}
}
private fun CustomTokenFieldId.validate(value: Any): AddCustomTokenError? {
private fun CustomTokenFieldId.validateValue(value: Any): AddCustomTokenError? {
val state = hubState
val contractAddressValidator: TokenContractAddressValidator = state.getValidator(ContractAddress)
val nameValidator: TokenNameValidator = state.getValidator(Name)
val symbolValidator: TokenSymbolValidator = state.getValidator(Symbol)
val decimalsValidator: TokenDecimalsValidator = state.getValidator(Decimals)
val networkValidator: TokenNetworkValidator = state.getValidator(Network)
return when (this) {
ContractAddress -> contractAddressValidator.validate(value as String)
Network -> networkValidator.validate(value as Blockchain)
Network, DerivationPath -> networkValidator.validate(value as Blockchain)
Name -> nameValidator.validate(value as String)
Symbol -> symbolValidator.validate(value as String)
Decimals -> decimalsValidator.validate(value as String)
DerivationPath -> networkValidator.validate(value as Blockchain)
}
}
private fun lockTokenFieldsAction(): Action {
/**
* The field is being validated.
* 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? {
val error = this.validateValue(value)
when (error) {
null -> this.removeError()
else -> this.addError(error)
}
return error
}
private suspend fun fillTokenFields(
token: Coins.CheckAddressResponse.Token,
contract: Coins.CheckAddressResponse.Token.Contract,
) {
val blockchain = Blockchain.fromNetworkId(contract.networkId) ?: Blockchain.Unknown
Network.setFieldValue(Field.Data(blockchain, false))
Name.setFieldValue(Field.Data(token.name, false))
Symbol.setFieldValue(Field.Data(token.symbol, false))
Decimals.setFieldValue(Field.Data(contract.decimalCount.toString(), false))
dispatchOnMain(UpdateForm(hubState))
}
private suspend fun clearTokenFields() {
Name.setFieldValue(Field.Data("", false))
Symbol.setFieldValue(Field.Data("", false))
Decimals.setFieldValue(Field.Data("", false))
dispatchOnMain(UpdateForm(hubState))
}
private suspend fun lockTokenFields() {
val state = hubState
return Screen.UpdateTokenFields(listOf(
val action = Screen.UpdateTokenFields(listOf(
Network to state.screenState.network.copy(isEnabled = false),
Name to state.screenState.name.copy(isEnabled = false),
Symbol to state.screenState.symbol.copy(isEnabled = false),
Decimals to state.screenState.decimals.copy(isEnabled = false),
))
dispatchOnMain(action)
}
private fun unlockTokenFieldsAction(): Action {
private suspend fun unlockTokenFields() {
val state = hubState
return Screen.UpdateTokenFields(listOf(
val action = Screen.UpdateTokenFields(listOf(
Network to state.screenState.network.copy(isEnabled = true),
Name to state.screenState.name.copy(isEnabled = true),
Symbol to state.screenState.symbol.copy(isEnabled = true),
Decimals to state.screenState.decimals.copy(isEnabled = true),
))
dispatchOnMain(action)
}
private suspend fun toggleAddButtonAction(enable: Boolean) = when (enable) {
true -> unlockAddButtonAction()
else -> lockAddButtonAction()
}
private suspend fun lockAddButtonAction() {
private suspend fun lockAddButton() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(false)))
}
private suspend fun unlockAddButtonAction() {
private suspend fun unlockAddButton() {
dispatchOnMain(Screen.UpdateAddButton(ViewStates.AddButton(true)))
}
override fun reduceAction(action: Action, state: AddCustomTokenState): AddCustomTokenState {
return when (action) {
is Init.SetAddedCurrencies -> {
state.copy(addedCurrencies = action.addedCurrencies)
state.copy(appSavedCurrencies = action.addedCurrencies)
}
is Init.SetOnAddTokenCallback -> {
state.copy(onTokenAddCallback = action.callback)
}
is OnCreate -> {
// val card = requireNotNull(globalState.scanResponse?.card)
val card = requireNotNull(globalState.scanResponse?.card)
val tangemTechServiceManager = TangemTechServiceManager(TangemTechService())
// tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString())
tangemTechServiceManager.attachAuthKey(card.cardPublicKey.toHexString())
var derivationPathState = state.screenState.derivationPath
derivationPathState = when (card.derivationStyle) {
DerivationStyle.LEGACY -> derivationPathState.copy(isVisible = true)
null, DerivationStyle.NEW -> derivationPathState.copy(isVisible = false)
}
state.copy(
// derivationStyle = card.derivationStyle,
derivationStyle = DerivationStyle.LEGACY,
tangemTechServiceManager = tangemTechServiceManager
cardDerivationStyle = card.derivationStyle,
tangemTechServiceManager = tangemTechServiceManager,
screenState = state.screenState.copy(derivationPath = derivationPathState)
)
}
is OnDestroy -> state.reset()
@ -414,7 +532,7 @@ internal class AddCustomTokenHub : BaseStoreHub<AddCustomTokenState>("AddCustomT
val newMap = state.formErrors.toMutableMap().apply { remove(action.id) }
state.copy(formErrors = newMap)
}
is SetTokenId -> {
is SetFoundTokenId -> {
state.copy(tokenId = action.id)
}
is Warning.Add -> {

View file

@ -2,15 +2,16 @@ package com.tangem.domain.features.addCustomToken.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.domain.DomainWrapped
import com.tangem.domain.common.form.*
import com.tangem.domain.features.addCustomToken.*
import com.tangem.domain.features.addCustomToken.CustomTokenFieldId.*
import org.rekotlin.StateType
data class AddCustomTokenState(
val addedCurrencies: AddedCurrencies? = null,
val onTokenAddCallback: ((CompleteData) -> Unit)? = null,
val derivationStyle: DerivationStyle? = null,
val appSavedCurrencies: List<DomainWrapped.Currency>? = null,
val onTokenAddCallback: ((CustomCurrency) -> Unit)? = null,
val cardDerivationStyle: DerivationStyle? = null,
val form: Form = Form(createFormFields()),
val formValidators: Map<CustomTokenFieldId, CustomTokenValidator<out Any>> = createFormValidators(),
val formErrors: Map<CustomTokenFieldId, AddCustomTokenError> = emptyMap(),
@ -28,30 +29,74 @@ data class AddCustomTokenState(
fun hasError(id: FieldId): Boolean = formErrors[id] != null
fun getCompleteData(type: CompleteDataType): CompleteData = when (type) {
CompleteDataType.Token -> getToken()
CompleteDataType.Blockchain -> getBlockchain()
}
inline fun <reified T> visitDataConverter(converter: FieldDataConverter<T>): T {
form.visitDataConverter(converter)
return converter.getConvertedData()
}
fun convertBlockchainName(blockchain: Blockchain, unknown: String): String = when (blockchain) {
Blockchain.Unknown -> unknown
else -> blockchain.fullName
fun blockchainToName(blockchain: Blockchain, isDerivationPath: Boolean = false): String? {
return when {
isDerivationPath -> blockchain.derivationPath(cardDerivationStyle)?.rawPath
else -> {
when (blockchain) {
Blockchain.Unknown -> null
else -> blockchain.fullName
}
}
}
}
fun convertDerivationPathLabel(blockchain: Blockchain, unknown: String): String {
return blockchain.derivationPath(derivationStyle)?.rawPath ?: unknown
// except network
fun tokensFieldsIsFilled(): Boolean {
val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals)
val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) }
val validator = StringIsNotEmptyValidator()
fieldsToCheck.forEach { field ->
val error = validator.validate(field.data.value?.toString())
if (error != null) return false
}
return true
}
// except network
fun tokensOneFieldsIsFilled(): Boolean {
val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals)
val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) }
val validator = StringIsEmptyValidator()
fieldsToCheck.forEach { field ->
val error = validator.validate(field.data.value?.toString())
if (error != null) return false
}
return true
}
fun networkIsSelected(): Boolean {
val network = getField<TokenBlockchainField>(Network)
return network.data.value != Blockchain.Unknown
}
fun derivationPathIsSelected(): Boolean {
val network = getField<TokenDerivationPathField>(DerivationPath)
return network.data.value != Blockchain.Unknown
}
fun gatherUserToken(): CustomCurrency.CustomToken? = try {
getToken()
} catch (ex: Exception) {
null
}
fun gatherBlockchain(): CustomCurrency.CustomBlockchain? = try {
getBlockchain()
} catch (ex: Exception) {
null
}
fun reset(): AddCustomTokenState {
return this.copy(
addedCurrencies = null,
appSavedCurrencies = null,
onTokenAddCallback = null,
derivationStyle = null,
cardDerivationStyle = null,
form = Form(createFormFields()),
formErrors = emptyMap(),
tokenId = null,
@ -61,40 +106,33 @@ data class AddCustomTokenState(
)
}
fun networkIsEmpty(): Boolean {
val network = getField<TokenBlockchainField>(Network)
return network.data.value != Blockchain.Unknown
}
fun customTokensFieldsIsEmpty(): Boolean {
val idsToCheck = listOf(ContractAddress, Name, Symbol, Decimals)
val fieldsToCheck = form.fieldList.filter { idsToCheck.contains(it.id) }
val validator = StringIsEmptyValidator()
// val errors = mutableMapOf<>()
fieldsToCheck.forEach { field ->
val error = validator.validate(field.data.value?.toString())
if (error != null) return false
}
return true
}
fun allFieldsIsEmpty(): Boolean {
return networkIsEmpty() && customTokensFieldsIsEmpty()
}
private fun getToken(): CompleteData.CustomToken {
return CompleteData.CustomToken.Converter(tokenId)
private fun getToken(): CustomCurrency.CustomToken {
return CustomCurrency.CustomToken.Converter(tokenId, cardDerivationStyle)
.apply { visitDataConverter(this) }
.getConvertedData()
}
private fun getBlockchain(): CompleteData.CustomBlockchain {
return CompleteData.CustomBlockchain.Converter()
private fun getBlockchain(): CustomCurrency.CustomBlockchain {
return CustomCurrency.CustomBlockchain.Converter(cardDerivationStyle)
.apply { visitDataConverter(this) }
.getConvertedData()
}
companion object {
/**
* If an user select derivation path (derivationNetwork) as Blockchain.Unknown,
* then we should use a blockchain from the mainNetwork to determine a DerivationPath
*/
fun getDerivationPath(
mainNetwork: Blockchain,
derivationNetwork: Blockchain,
derivationStyle: DerivationStyle?
): com.tangem.common.hdWallet.DerivationPath? = when (derivationNetwork) {
Blockchain.Unknown -> mainNetwork
else -> derivationNetwork
}.derivationPath(derivationStyle)
private fun createFormFields(): List<DataField<*>> {
return listOf(
TokenField(ContractAddress),

View file

@ -1,8 +1,5 @@
package com.tangem.domain.features.addCustomToken.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.domain.DomainWrapped
/**
[REDACTED_AUTHOR]
*/
@ -27,9 +24,4 @@ sealed class ViewStates {
data class AddButton(
val isEnabled: Boolean = true
) : ViewStates()
}
data class AddedCurrencies(
val addedTokens: List<DomainWrapped.TokenWithBlockchain>,
val addedBlockchains: List<Blockchain>
)
}