From 65ace6c5f2ff48ca098d5f7918a5b32b8a6ed975 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 19 Aug 2025 14:15:30 +0700 Subject: [PATCH] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 6 +++ .../ethereum/WcEthAddNetworkUseCase.kt | 44 ++++++++++++++++ .../network/ethereum/WcEthNetwork.kt | 35 ++++++------- .../ethereum/WcEthSwitchNetworkUseCase.kt | 51 +++++++++++++++++++ .../request/DefaultWcRequestService.kt | 1 + .../utils/WcNetworksConverter.kt | 5 ++ .../domain/walletconnect/model/WcEthMethod.kt | 7 +-- .../walletconnect/model/WcMethodName.kt | 1 + .../walletconnect/model/WcRequestError.kt | 5 ++ .../usecase/method/WcAddNetworkUseCase.kt | 7 +++ .../usecase/method/WcSwitchNetworkUseCase.kt | 17 +++++++ .../connections/components/AlertsComponent.kt | 18 +++++++ .../routing/DefaultWcRoutingComponent.kt | 28 +++++++++- .../connections/routing/WcInnerRoute.kt | 12 +++++ .../connections/routing/WcRoutingModel.kt | 2 + .../connections/ui/AlertsModalBottomSheet.kt | 42 +++++++++++++-- .../di/WalletConnectModelModule.kt | 6 +++ .../chain/WcSwitchNetworkComponent.kt | 21 ++++++++ .../converter/WcHandleMethodErrorConverter.kt | 4 ++ .../transaction/model/WcAddNetworkModel.kt | 31 ++++++----- .../transaction/model/WcSwitchNetworkModel.kt | 45 ++++++++++++++++ 21 files changed, 349 insertions(+), 39 deletions(-) create mode 100644 data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt create mode 100644 domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt create mode 100644 features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/chain/WcSwitchNetworkComponent.kt create mode 100644 features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index c81754117c..ec20bf2c8f 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1505,12 +1505,16 @@ Enable Trustline A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve. Trustline Required + The required network %s is not added to your portfolio. Add it first, then proceed with the connection. + Add network to portfolio Malicious domain Unknown domain Connect anyway Timeout error. Please, try again later. Failed to establish WalletConnect This domain cannot be verified. Check the request carefully approving. + To continue, please reconnect your dApp session with the required network %s. + Network not connected Please return to your browser and reconnect via WalletConnect. Wallet Connect session was disconnected Sign anyway @@ -1521,6 +1525,8 @@ Unsupported dApp Error code: 8 005. If the problem persists — feel free to contact our support. We\'ve encountered unknown error + This network %s is not supported by Tangem Wallet and cannot be connected. + Unsupported network Tangem does not currently support a required network by %s. Unsupported networks This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt index 0bc201fd7a..5c988f8a8e 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthAddNetworkUseCase.kt @@ -1,9 +1,17 @@ package com.tangem.data.walletconnect.network.ethereum import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.extensions.hexToInt +import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork.NamespaceConverter.Companion.ETH_NAMESPACE_KEY import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext +import com.tangem.data.walletconnect.utils.WcNetworksConverter import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.walletconnect.model.HandleMethodError import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.WcSession @@ -16,6 +24,7 @@ import dagger.assisted.AssistedInject internal class WcEthAddNetworkUseCase @AssistedInject constructor( private val respondService: WcRespondService, + addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory, @Assisted val context: WcMethodUseCaseContext, @Assisted override val method: WcEthMethod.AddEthereumChain, ) : WcAddNetworkUseCase { @@ -31,6 +40,14 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor( else -> WcNetworkDerivationState.Single } + private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context) + + override suspend fun invoke(): Either { + return addSwitchCommonDelegate + .commonChecks(method.rawChain.chainId) + .map { addedNetwork -> WcAddNetworkUseCase.AddNetwork(addedNetwork) } + } + override suspend fun approve(): Either { return respondService.respond(rawSdkRequest, "") } @@ -43,4 +60,31 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor( interface Factory { fun create(context: WcMethodUseCaseContext, method: WcEthMethod.AddEthereumChain): WcEthAddNetworkUseCase } +} + +internal class WcEthAddSwitchCommonDelegate @AssistedInject constructor( + private val networksConverter: WcNetworksConverter, + @Assisted val context: WcMethodUseCaseContext, +) { + + private val wallet: UserWallet get() = context.session.wallet + + suspend fun commonChecks(hexChainId: String): Either { + val caip2 = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${hexChainId.hexToInt()}") + ?: return HandleMethodError.UnknownError("Failed to parse CAIP2").left() + val generalNetwork = networksConverter.createNetwork(caip2.raw, wallet) + if (generalNetwork == null) { + return HandleMethodError.TangemUnsupportedNetwork(caip2.raw).left() + } + val addedNetwork = networksConverter.mainOrAnyWalletNetworkForRequest(caip2.raw, wallet) + if (addedNetwork == null) { + return HandleMethodError.NotAddedNetwork(generalNetwork.name).left() + } + return addedNetwork.right() + } + + @AssistedFactory + interface Factory { + fun create(context: WcMethodUseCaseContext): WcEthAddSwitchCommonDelegate + } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt index fe375b57d6..a14460a566 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthNetwork.kt @@ -6,17 +6,14 @@ import arrow.core.left import arrow.core.right import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.extensions.hexToInt import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey -import com.tangem.data.walletconnect.network.ethereum.WcEthNetwork.NamespaceConverter.Companion.ETH_NAMESPACE_KEY import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.data.walletconnect.utils.WcNetworksConverter -import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.walletconnect.model.* import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager @@ -46,7 +43,7 @@ internal class WcEthNetwork( ?: return HandleMethodError.UnknownSession.left() val wallet = session.wallet val chainId = request.chainId.orEmpty() - val method: WcEthMethod = name.toMethod(request, wallet) + val method: WcEthMethod = name.toMethod(request) .getOrElse { return error(it.message.orEmpty()) } ?: return error("Failed to parse $name") suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) @@ -56,7 +53,9 @@ internal class WcEthNetwork( is WcEthMethod.SendTransaction -> method.transaction.from is WcEthMethod.SignTransaction -> method.transaction.from is WcEthMethod.SignTypedData -> method.account - is WcEthMethod.AddEthereumChain -> + is WcEthMethod.AddEthereumChain, + is WcEthMethod.SwitchEthereumChain, + -> anyExistNetwork() ?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() } .orEmpty() @@ -67,7 +66,9 @@ internal class WcEthNetwork( is WcEthMethod.SendTransaction, is WcEthMethod.SignTransaction, -> networksConverter.findWalletNetworkForRequest(request, session, accountAddress) - is WcEthMethod.AddEthereumChain -> anyExistNetwork() + is WcEthMethod.AddEthereumChain, + is WcEthMethod.SwitchEthereumChain, + -> anyExistNetwork() } ?: return error("Failed to find walletNetwork for accountAddress $accountAddress") val context = WcMethodUseCaseContext( @@ -83,13 +84,11 @@ internal class WcEthNetwork( is WcEthMethod.SignTransaction -> factories.signTransaction.create(context, method) is WcEthMethod.SignTypedData -> factories.signTypedData.create(context, method) is WcEthMethod.AddEthereumChain -> factories.addNetwork.create(context, method) + is WcEthMethod.SwitchEthereumChain -> factories.switchNetwork.create(context, method) }.right() } - private suspend fun WcEthMethodName.toMethod( - request: WcSdkSessionRequest, - wallet: UserWallet, - ): Either { + private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest): Either { val rawParams = request.request.params return when (this) { WcEthMethodName.EthSign, @@ -111,16 +110,17 @@ internal class WcEthNetwork( } } ?: return null.right() - WcEthMethodName.AddEthereumChain -> moshi.fromJson>(rawParams) + WcEthMethodName.AddEthereumChain, + WcEthMethodName.SwitchEthereumChain, + -> moshi.fromJson>(rawParams) .getOrElse { return it.left() } ?.firstOrNull() ?.let { - val caip2 = CAIP2.fromRaw("$ETH_NAMESPACE_KEY:${it.chainId.hexToInt()}") - ?: return null.right() - val newNetwork = networksConverter - .mainOrAnyWalletNetworkForRequest(caip2.raw, wallet) - ?: return null.right() - WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork).right() + if (this == WcEthMethodName.AddEthereumChain) { + WcEthMethod.AddEthereumChain(rawChain = it).right() + } else { + WcEthMethod.SwitchEthereumChain(rawChain = it).right() + } } ?: null.right() } @@ -170,5 +170,6 @@ internal class WcEthNetwork( val sendTransaction: WcEthSendTransactionUseCase.Factory, val signTransaction: WcEthSignTransactionUseCase.Factory, val addNetwork: WcEthAddNetworkUseCase.Factory, + val switchNetwork: WcEthSwitchNetworkUseCase.Factory, ) } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt new file mode 100644 index 0000000000..0835594dfe --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSwitchNetworkUseCase.kt @@ -0,0 +1,51 @@ +package com.tangem.data.walletconnect.network.ethereum + +import arrow.core.Either +import com.tangem.data.walletconnect.respond.WcRespondService +import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext +import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.model.HandleMethodError +import com.tangem.domain.walletconnect.model.WcEthMethod +import com.tangem.domain.walletconnect.model.WcSession +import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest +import com.tangem.domain.walletconnect.usecase.method.WcNetworkDerivationState +import com.tangem.domain.walletconnect.usecase.method.WcSwitchNetworkUseCase +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class WcEthSwitchNetworkUseCase @AssistedInject constructor( + private val respondService: WcRespondService, + @Assisted val context: WcMethodUseCaseContext, + @Assisted override val method: WcEthMethod.SwitchEthereumChain, + addSwitchCommonDelegateFactory: WcEthAddSwitchCommonDelegate.Factory, +) : WcSwitchNetworkUseCase { + + override val session: WcSession + get() = context.session + override val rawSdkRequest: WcSdkSessionRequest + get() = context.rawSdkRequest + override val network: Network + get() = context.network + override val derivationState: WcNetworkDerivationState = when { + context.networkDerivationsCount > 1 -> WcNetworkDerivationState.Multiple(walletAddress = context.accountAddress) + else -> WcNetworkDerivationState.Single + } + + private val addSwitchCommonDelegate = addSwitchCommonDelegateFactory.create(context) + + override suspend fun invoke(): Either { + return addSwitchCommonDelegate + .commonChecks(method.rawChain.chainId) + .map { addedNetwork -> WcSwitchNetworkUseCase.SwitchNetwork(addedNetwork) } + } + + override fun reject() { + respondService.rejectRequestNonBlock(rawSdkRequest) + } + + @AssistedFactory + interface Factory { + fun create(context: WcMethodUseCaseContext, method: WcEthMethod.SwitchEthereumChain): WcEthSwitchNetworkUseCase + } +} \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt index 4c50b189fd..db8788c05a 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestService.kt @@ -33,6 +33,7 @@ internal class DefaultWcRequestService( Timber.tag(WC_TAG).i("handle request name $name") if (name is WcMethodName.Unsupported) { respondService.rejectRequestNonBlock(sr) + if (name.raw.startsWith("wallet_")) return } _wcRequest.trySend(name to sr) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt index 6a80763cc9..4b49445c7f 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/utils/WcNetworksConverter.kt @@ -25,6 +25,11 @@ internal class WcNetworksConverter @Inject constructor( private val tokensFeatureToggles: TokensFeatureToggles, ) { + fun createNetwork(chainId: String, wallet: UserWallet): Network? { + return namespaceConverters + .firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } + } + suspend fun findWalletNetworkForRequest( request: WcSdkSessionRequest, session: WcSession, diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt index afb348e77e..34b43e11bb 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt @@ -1,7 +1,5 @@ package com.tangem.domain.walletconnect.model -import com.tangem.domain.models.network.Network - sealed interface WcEthMethod : WcMethod { data class MessageSign( @@ -28,6 +26,9 @@ sealed interface WcEthMethod : WcMethod { data class AddEthereumChain( val rawChain: WcEthAddChain, - val network: Network, + ) : WcEthMethod + + data class SwitchEthereumChain( + val rawChain: WcEthAddChain, ) : WcEthMethod } \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt index 6ae4b431ac..982c1cacf1 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethodName.kt @@ -14,6 +14,7 @@ enum class WcEthMethodName(override val raw: String) : WcMethodName { SignTransaction("eth_signTransaction"), SendTransaction("eth_sendTransaction"), AddEthereumChain("wallet_addEthereumChain"), + SwitchEthereumChain("wallet_switchEthereumChain"), } enum class WcSolanaMethodName(override val raw: String) : WcMethodName { diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt index 3c1cfe610a..e0ad853f77 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcRequestError.kt @@ -69,4 +69,9 @@ sealed class HandleMethodError( data object UnknownSession : HandleMethodError(message = "WalletConnect session was disconnected") data class UnknownError(override val message: String) : HandleMethodError(message) + data class TangemUnsupportedNetwork(val unsupportedNetwork: String) : + HandleMethodError("TangemUnsupportedNetwork $unsupportedNetwork") + + data class NotAddedNetwork(val networkName: String) : HandleMethodError("NotAddedNetwork $networkName") + data class RequiredNetwork(val networkName: String) : HandleMethodError("RequiredNetwork $networkName") } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt index 787bea6e93..35223ba040 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcAddNetworkUseCase.kt @@ -1,12 +1,19 @@ package com.tangem.domain.walletconnect.usecase.method import arrow.core.Either +import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.model.HandleMethodError import com.tangem.domain.walletconnect.model.WcRequestError interface WcAddNetworkUseCase : WcMethodUseCase, WcMethodContext { + suspend operator fun invoke(): Either suspend fun approve(): Either fun reject() + + data class AddNetwork( + val network: Network, + ) } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt new file mode 100644 index 0000000000..5c2066049a --- /dev/null +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/method/WcSwitchNetworkUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.walletconnect.usecase.method + +import arrow.core.Either +import com.tangem.domain.models.network.Network +import com.tangem.domain.walletconnect.model.HandleMethodError + +interface WcSwitchNetworkUseCase : + WcMethodUseCase, + WcMethodContext { + + suspend operator fun invoke(): Either + fun reject() + + data class SwitchNetwork( + val network: Network, + ) +} \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt index 6fd6fde73b..fd682083d3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/components/AlertsComponent.kt @@ -38,5 +38,23 @@ internal class AlertsComponent( @Serializable data class WcDisconnected(override val onDismiss: () -> Unit) : AlertType() + + @Serializable + data class TangemUnsupportedNetwork( + val network: String, + override val onDismiss: () -> Unit, + ) : AlertType() + + @Serializable + data class RequiredAddNetwork( + val network: String, + override val onDismiss: () -> Unit, + ) : AlertType() + + @Serializable + data class RequiredReconnectWithNetwork( + val network: String, + override val onDismiss: () -> Unit, + ) : AlertType() } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt index 42b336932f..30c6d10c14 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/DefaultWcRoutingComponent.kt @@ -18,8 +18,10 @@ import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import com.tangem.features.send.v2.api.FeeSelectorComponent import com.tangem.features.walletconnect.components.WcRoutingComponent import com.tangem.features.walletconnect.connections.components.AlertsComponent +import com.tangem.features.walletconnect.connections.components.AlertsComponent.AlertType.* import com.tangem.features.walletconnect.connections.components.WcPairComponent import com.tangem.features.walletconnect.transaction.components.chain.WcAddNetworkContainerComponent +import com.tangem.features.walletconnect.transaction.components.chain.WcSwitchNetworkComponent import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams import com.tangem.features.walletconnect.transaction.components.send.WcSendTransactionContainerComponent import com.tangem.features.walletconnect.transaction.components.sign.WcSignTransactionContainerComponent @@ -71,6 +73,10 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor( appComponentContext = childContext, params = WcTransactionModelParams(config.rawRequest), ) + is WcInnerRoute.SwitchNetwork -> WcSwitchNetworkComponent( + appComponentContext = childContext, + params = WcTransactionModelParams(config.rawRequest), + ) is WcInnerRoute.Send -> WcSendTransactionContainerComponent( appComponentContext = childContext, params = WcTransactionModelParams(config.rawRequest), @@ -88,13 +94,31 @@ internal class DefaultWcRoutingComponent @AssistedInject constructor( is WcInnerRoute.UnsupportedMethodAlert -> AlertsComponent( childContext, AlertsComponent.Params( - alertType = AlertsComponent.AlertType.UnsupportedMethod { model.innerRouter.pop() }, + alertType = UnsupportedMethod { model.innerRouter.pop() }, ), ) is WcInnerRoute.WcDappDisconnected -> AlertsComponent( childContext, AlertsComponent.Params( - alertType = AlertsComponent.AlertType.WcDisconnected { model.innerRouter.pop() }, + alertType = WcDisconnected { model.innerRouter.pop() }, + ), + ) + is WcInnerRoute.TangemUnsupportedNetwork -> AlertsComponent( + childContext, + AlertsComponent.Params( + alertType = TangemUnsupportedNetwork(config.networkName) { model.innerRouter.pop() }, + ), + ) + is WcInnerRoute.RequiredAddNetwork -> AlertsComponent( + childContext, + AlertsComponent.Params( + alertType = RequiredAddNetwork(config.networkName) { model.innerRouter.pop() }, + ), + ) + is WcInnerRoute.RequiredReconnectWithNetwork -> AlertsComponent( + childContext, + AlertsComponent.Params( + alertType = RequiredReconnectWithNetwork(config.networkName) { model.innerRouter.pop() }, ), ) } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt index 4cabfc0547..bc9f386e88 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcInnerRoute.kt @@ -22,6 +22,9 @@ internal sealed interface WcInnerRoute : Route { @Serializable data class AddNetwork(override val rawRequest: WcSdkSessionRequest) : Method + @Serializable + data class SwitchNetwork(override val rawRequest: WcSdkSessionRequest) : Method + @Serializable data class Pair(val request: WcPairRequest) : WcInnerRoute @@ -30,4 +33,13 @@ internal sealed interface WcInnerRoute : Route { @Serializable data object WcDappDisconnected : WcInnerRoute + + @Serializable + data class TangemUnsupportedNetwork(val networkName: String) : WcInnerRoute + + @Serializable + data class RequiredAddNetwork(val networkName: String) : WcInnerRoute + + @Serializable + data class RequiredReconnectWithNetwork(val networkName: String) : WcInnerRoute } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt index 3c320faab7..5524f954e3 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/routing/WcRoutingModel.kt @@ -51,6 +51,8 @@ internal class WcRoutingModel @Inject constructor( -> WcInnerRoute.SignMessage(rawRequest) WcEthMethodName.AddEthereumChain, -> WcInnerRoute.AddNetwork(rawRequest) + WcEthMethodName.SwitchEthereumChain, + -> WcInnerRoute.SwitchNetwork(rawRequest) WcEthMethodName.SignTransaction, WcEthMethodName.SendTransaction, WcSolanaMethodName.SignTransaction, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt index 7c93a184f5..c7ff306829 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/ui/AlertsModalBottomSheet.kt @@ -87,7 +87,11 @@ private fun ButtonsContainer(alert: AlertsComponent.AlertType, modifier: Modifie ) { val buttonsModifier = Modifier.fillMaxWidth() when (alert) { - is AlertsComponent.AlertType.UnsupportedMethod -> SecondaryButton( + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> SecondaryButton( modifier = buttonsModifier, onClick = alert.onDismiss, text = stringResourceSafe(R.string.balance_hidden_got_it_button), @@ -105,16 +109,28 @@ private fun ButtonsContainer(alert: AlertsComponent.AlertType, modifier: Modifie private fun AlertIcon(alert: AlertsComponent.AlertType, modifier: Modifier = Modifier) { val color = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> TangemTheme.colors.icon.informative - is AlertsComponent.AlertType.UnsupportedMethod -> TangemTheme.colors.icon.attention + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> TangemTheme.colors.icon.attention } @DrawableRes val drawableId = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> R.drawable.ic_wallet_connect_24 - is AlertsComponent.AlertType.UnsupportedMethod -> R.drawable.img_attention_20 + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> R.drawable.img_attention_20 } val iconTint = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> color - is AlertsComponent.AlertType.UnsupportedMethod -> Color.Unspecified + is AlertsComponent.AlertType.UnsupportedMethod, + is AlertsComponent.AlertType.RequiredAddNetwork, + is AlertsComponent.AlertType.RequiredReconnectWithNetwork, + is AlertsComponent.AlertType.TangemUnsupportedNetwork, + -> Color.Unspecified } Box( modifier = modifier @@ -138,6 +154,9 @@ private fun AlertContentTitle(alert: AlertsComponent.AlertType, modifier: Modifi @StringRes val titleRes: Int = when (alert) { is AlertsComponent.AlertType.WcDisconnected -> R.string.wc_alert_session_disconnected_title is AlertsComponent.AlertType.UnsupportedMethod -> R.string.wc_alert_unsupported_method_title + is AlertsComponent.AlertType.RequiredAddNetwork -> R.string.wc_alert_add_network_to_portfolio_title + is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> R.string.wc_alert_network_not_connected_title + is AlertsComponent.AlertType.TangemUnsupportedNetwork -> R.string.wc_alert_unsupported_network_title } Text( modifier = modifier, @@ -157,6 +176,18 @@ private fun AlertContentDescription(alert: AlertsComponent.AlertType, modifier: is AlertsComponent.AlertType.UnsupportedMethod -> stringResourceSafe( R.string.wc_alert_unsupported_method_description, ) + is AlertsComponent.AlertType.RequiredAddNetwork -> stringResourceSafe( + R.string.wc_alert_unsupported_method_description, + alert.network, + ) + is AlertsComponent.AlertType.RequiredReconnectWithNetwork -> stringResourceSafe( + R.string.wc_alert_network_not_connected_description, + alert.network, + ) + is AlertsComponent.AlertType.TangemUnsupportedNetwork -> stringResourceSafe( + R.string.wc_alert_unsupported_network_description, + alert.network, + ) } Text( modifier = modifier, @@ -188,5 +219,8 @@ private class AlertTypesProvider : CollectionPreviewParameterProvider WcInnerRoute.UnsupportedMethodAlert HandleMethodError.UnknownSession -> WcInnerRoute.WcDappDisconnected + + is HandleMethodError.NotAddedNetwork -> WcInnerRoute.RequiredAddNetwork(value.networkName) + is HandleMethodError.RequiredNetwork -> WcInnerRoute.RequiredReconnectWithNetwork(value.networkName) + is HandleMethodError.TangemUnsupportedNetwork -> WcInnerRoute.TangemUnsupportedNetwork(value.unsupportedNetwork) } } \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt index 107e983abc..f85c7a6003 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcAddNetworkModel.kt @@ -57,22 +57,27 @@ internal class WcAddNetworkModel @Inject constructor( .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } .getOrNull() ?: return@launch sendSignatureReceivedAnalytics(useCase) - _uiState.emit( - wcAddEthereumChainUMConverter.convert( - WcAddEthereumChainUMConverter.Input( - useCase = useCase, - actions = WcTransactionActionsUM( - onShowVerifiedAlert = ::showVerifiedAlert, - onDismiss = { cancel(useCase) }, - onSign = { sign(useCase) }, - onCopy = { copyData(useCase.rawSdkRequest.request.params) }, - ), - ), - ), - ) + val either = useCase.invoke() + either + .onLeft { router.push(WcHandleMethodErrorConverter.convert(it)) } + .map { showUI() } } } + private fun showUI() { + _uiState.value = wcAddEthereumChainUMConverter.convert( + WcAddEthereumChainUMConverter.Input( + useCase = useCase, + actions = WcTransactionActionsUM( + onShowVerifiedAlert = ::showVerifiedAlert, + onDismiss = { cancel(useCase) }, + onSign = { sign(useCase) }, + onCopy = { copyData(useCase.rawSdkRequest.request.params) }, + ), + ), + ) + } + override fun dismiss() { _uiState.value?.transaction?.onDismiss?.invoke() ?: router.pop() } diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt new file mode 100644 index 0000000000..65b63ae8b6 --- /dev/null +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSwitchNetworkModel.kt @@ -0,0 +1,45 @@ +package com.tangem.features.walletconnect.transaction.model + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.domain.walletconnect.WcRequestUseCaseFactory +import com.tangem.domain.walletconnect.model.HandleMethodError +import com.tangem.domain.walletconnect.usecase.method.WcSwitchNetworkUseCase +import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams +import com.tangem.features.walletconnect.transaction.converter.WcHandleMethodErrorConverter +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class WcSwitchNetworkModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val router: Router, + private val useCaseFactory: WcRequestUseCaseFactory, +) : Model() { + + private val params = paramsContainer.require() + + init { + modelScope.launch { + val useCase = useCaseFactory.createUseCase(params.rawRequest) + .onLeft { showErrorDialog(it) } + .getOrNull() ?: return@launch + val either = useCase.invoke() + useCase.reject() + either + .onLeft { showErrorDialog(it) } + .map { showErrorDialog(HandleMethodError.RequiredNetwork(it.network.name)) } + } + } + + private fun showErrorDialog(error: HandleMethodError) { + router.push(WcHandleMethodErrorConverter.convert(error)) + } +} \ No newline at end of file