Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-28 10:51:17 +03:00
parent cd02ce7bc0
commit e421ca8d32
11 changed files with 160 additions and 31 deletions

View file

@ -1,5 +1,7 @@
package com.tangem.tap.features.customtoken.impl.presentation.routers
import com.tangem.blockchain.common.Blockchain
/**
* Custom token feature router
*
@ -12,4 +14,10 @@ internal interface CustomTokenRouter {
/** Open wallet (main) screen */
fun openWalletScreen()
/** Open alert if solana network is unsupported
*
* @param blockchain blockchain to show alert
*/
fun openUnsupportedNetworkAlert(blockchain: Blockchain)
}

View file

@ -1,8 +1,12 @@
package com.tangem.tap.features.customtoken.impl.presentation.routers
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.navigation.AppScreen
import com.tangem.core.navigation.NavigationAction
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.store
import com.tangem.wallet.R
/** Default implementation of custom token feature router */
internal class DefaultCustomTokenRouter : CustomTokenRouter {
@ -14,4 +18,13 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
override fun openWalletScreen() {
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
}
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
val alert = AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_curve_message,
args = listOf(blockchain.fullName),
)
store.dispatchDialogShow(alert)
}
}

View file

@ -635,6 +635,15 @@ internal class AddCustomTokenViewModel @Inject constructor(
return derivationNetwork.derivationPath(derivationStyle)
}
private fun isUnsupportedBlockchain(blockchain: Blockchain): Boolean {
val scanResponse = reduxStateHolder.scanResponse
val canHandleToken = scanResponse?.card?.canHandleBlockchain(
blockchain = blockchain,
cardTypesResolver = scanResponse.cardTypesResolver,
) ?: false
return !canHandleToken
}
private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) {
fun onBackButtonClick() {
@ -747,6 +756,11 @@ internal class AddCustomTokenViewModel @Inject constructor(
fun onAddCustomTokenClick() {
if (!isNetworkSelected()) return
val blockchain = uiState.form.networkSelectorField.selectedItem.blockchain
if (isUnsupportedBlockchain(blockchain)) {
featureRouter.openUnsupportedNetworkAlert(blockchain)
return
}
val currency = when (getCustomTokenType()) {
CustomTokenType.TOKEN -> {
@ -758,13 +772,13 @@ internal class AddCustomTokenViewModel @Inject constructor(
decimals = requireNotNull(uiState.form.decimalsInputField.value.toIntOrNull()),
id = foundToken?.id,
),
network = uiState.form.networkSelectorField.selectedItem.blockchain,
network = blockchain,
derivationPath = getDerivationPath(),
)
}
CustomTokenType.BLOCKCHAIN -> {
CustomCurrency.CustomBlockchain(
network = uiState.form.networkSelectorField.selectedItem.blockchain,
network = blockchain,
derivationPath = getDerivationPath(),
)
}

View file

@ -0,0 +1,11 @@
package com.tangem.tap.features.tokens.impl.presentation.models
/**
* State that shows is token support by given card
*/
sealed class SupportTokensState {
object SolanaNetworkUnsupported : SupportTokensState()
object UnsupportedCurve : SupportTokensState()
object SupportedToken : SupportTokensState()
}

View file

@ -51,17 +51,20 @@ internal class DefaultTokensListRouter(
}
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
val alert = when (blockchain) {
Blockchain.Solana -> AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_message,
)
else -> AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_curve_message,
args = listOf(blockchain.fullName),
)
}
val alert = AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_curve_message,
args = listOf(blockchain.fullName),
)
store.dispatchDialogShow(alert)
}
override fun openSolanaTokensNotSupportAlert() {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
headerId = R.string.common_warning,
messageId = R.string.alert_manage_tokens_unsupported_message,
),
)
}
}

View file

@ -35,4 +35,9 @@ internal interface TokensListRouter {
* @param blockchain blockchain to show alert
*/
fun openUnsupportedNetworkAlert(blockchain: Blockchain)
/**
* Open alert with Solana tokens error
*/
fun openSolanaTokensNotSupportAlert()
}

View file

@ -17,6 +17,7 @@ import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.extensions.canHandleBlockchain
import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.supportedTokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
import com.tangem.tap.common.extensions.getGreyedOutIconRes
@ -24,6 +25,7 @@ import com.tangem.tap.common.extensions.getNetworkName
import com.tangem.tap.features.tokens.impl.domain.TokensListInteractor
import com.tangem.tap.features.tokens.impl.domain.models.Token
import com.tangem.tap.features.tokens.impl.domain.models.Token.Network
import com.tangem.tap.features.tokens.impl.presentation.models.SupportTokensState
import com.tangem.tap.features.tokens.impl.presentation.models.TokensListArgs
import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter
import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState
@ -42,6 +44,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.plus
import timber.log.Timber
import javax.inject.Inject
import com.tangem.blockchain.common.Token as BlockchainToken
@ -355,24 +358,38 @@ internal class TokensListViewModel @Inject constructor(
toggledNetwork.changeToggleState()
}
} else {
if (isUnsupportedToken(token.blockchain)) {
router.openUnsupportedNetworkAlert(token.blockchain)
} else {
analyticsSender.sendWhenTokenAdded(token.token)
changedTokensList.add(token)
toggledNetwork.changeToggleState()
when (isUnsupportedToken(token.blockchain)) {
SupportTokensState.SolanaNetworkUnsupported -> router.openSolanaTokensNotSupportAlert()
SupportTokensState.SupportedToken -> {
analyticsSender.sendWhenTokenAdded(token.token)
changedTokensList.add(token)
toggledNetwork.changeToggleState()
}
SupportTokensState.UnsupportedCurve -> router.openUnsupportedNetworkAlert(token.blockchain)
null -> Timber.e("Something went wrong in isUnsupportedToken (no scanResponse found)")
}
}
}
}
private fun isUnsupportedToken(blockchain: Blockchain): Boolean {
private fun isUnsupportedToken(blockchain: Blockchain): SupportTokensState? {
val scanResponse = reduxStateHolder.scanResponse
val canHandleToken = scanResponse?.card?.canHandleToken(
val cardTypesResolver = scanResponse?.cardTypesResolver ?: return null
val supportedTokens = scanResponse.card.supportedTokens(cardTypesResolver)
// refactor this later by moving all this logic in card config
if (!supportedTokens.contains(Blockchain.Solana)) {
return SupportTokensState.SolanaNetworkUnsupported
}
val canHandleToken = scanResponse.card.canHandleToken(
supportedTokens = supportedTokens,
blockchain = blockchain,
cardTypesResolver = scanResponse.cardTypesResolver,
) ?: false
return !canHandleToken
cardTypesResolver = cardTypesResolver,
)
if (!canHandleToken) {
return SupportTokensState.UnsupportedCurve
}
return SupportTokensState.SupportedToken
}
private fun isUnsupportedBlockchain(blockchain: Blockchain): Boolean {

View file

@ -20,7 +20,7 @@ sealed interface CardConfig {
if (cardDTO.settings.isBackupAllowed && cardDTO.settings.isHDWalletAllowed &&
cardDTO.firmwareVersion >= FirmwareVersion.MultiWalletAvailable
) {
return GenericCardConfig
return MultiWalletCardConfig
}
return GenericCardConfig
}

View file

@ -9,11 +9,10 @@ object GenericCardConfig : CardConfig {
get() = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
)
/**
* Old logic to determine primary curve for blockchain in TangemWallet
* Old logic to determine primary curve for blockchain
*/
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
return when {

View file

@ -0,0 +1,35 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import timber.log.Timber
object MultiWalletCardConfig : CardConfig {
override val mandatoryCurves: List<EllipticCurve>
get() = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
)
/**
* Old logic to determine primary curve for blockchain in TangemWallet
*/
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
return when {
blockchain.getSupportedCurves().contains(EllipticCurve.Secp256k1) -> {
EllipticCurve.Secp256k1
}
blockchain.getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
EllipticCurve.Ed25519
}
blockchain.getSupportedCurves().contains(EllipticCurve.Bls12381G2Aug) -> {
EllipticCurve.Bls12381G2Aug
}
else -> {
Timber.e("Unsupported blockchain, curve not found")
null
}
}
}
}

View file

@ -5,6 +5,7 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.models.scan.CardDTO
/**
@ -52,10 +53,32 @@ fun CardDTO.supportedTokens(cardTypesResolver: CardTypesResolver): List<Blockcha
return tokensSupportedByCard.filter { isTestCard == it.isTestnet() }
}
/**
* The same as [CardDTO.supportedTokens] but with supportedTokens input, if previously calculated
*/
fun CardDTO.canHandleToken(
supportedTokens: List<Blockchain>,
blockchain: Blockchain,
cardTypesResolver: CardTypesResolver,
): Boolean {
val cardConfig = CardConfig.createConfig(this)
val primaryCurveForBlockchain = cardConfig.primaryCurve(blockchain)
val isContainsBlockchain = supportedTokens.contains(blockchain)
val isWalletForCurveExists = wallets.any { it.curve == primaryCurveForBlockchain }
// fixme: check for first wallets with 1 curve and remove condition
return if (cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()) {
// if there's no wallet on card for blockchain with given curve
isContainsBlockchain && isWalletForCurveExists
} else {
isContainsBlockchain
}
}
fun CardDTO.canHandleToken(blockchain: Blockchain, cardTypesResolver: CardTypesResolver): Boolean {
val cardConfig = CardConfig.createConfig(this)
val primaryCurveForBlockchain = cardConfig.primaryCurve(blockchain)
val isContainsBlockchain = this.supportedTokens(cardTypesResolver).contains(blockchain)
val isWalletForCurveExists =
wallets.map { it.curve }.intersect(blockchain.getSupportedCurves().toSet()).isNotEmpty()
val isWalletForCurveExists = wallets.any { it.curve == primaryCurveForBlockchain }
// fixme: check for first wallets with 1 curve and remove condition
return if (cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()) {
// if there's no wallet on card for blockchain with given curve
@ -66,9 +89,10 @@ fun CardDTO.canHandleToken(blockchain: Blockchain, cardTypesResolver: CardTypesR
}
fun CardDTO.canHandleBlockchain(blockchain: Blockchain, cardTypesResolver: CardTypesResolver): Boolean {
val cardConfig = CardConfig.createConfig(this)
val primaryCurveForBlockchain = cardConfig.primaryCurve(blockchain)
val isContainsBlockchain = this.supportedBlockchains(cardTypesResolver).contains(blockchain)
val isWalletForCurveExists =
wallets.map { it.curve }.intersect(blockchain.getSupportedCurves().toSet()).isNotEmpty()
val isWalletForCurveExists = wallets.any { it.curve == primaryCurveForBlockchain }
// fixme: check for first wallets with 1 curve and remove condition
return if (cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()) {
// if there's no wallet on card for blockchain with given curve