Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-19 14:15:30 +07:00
parent c8a163fd0a
commit 65ace6c5f2
21 changed files with 349 additions and 39 deletions

View file

@ -1505,12 +1505,16 @@
<string name="warning_token_trustline_button_title">Enable Trustline</string>
<string name="warning_token_trustline_subtitle">A Trustline must be enabled to receive this token. The network requires a %1$s %2$s reserve.</string>
<string name="warning_token_trustline_title">Trustline Required</string>
<string name="wc_alert_add_network_to_portfolio_description">The required network %s is not added to your portfolio. Add it first, then proceed with the connection.</string>
<string name="wc_alert_add_network_to_portfolio_title">Add network to portfolio</string>
<string name="wc_alert_audit_malicious_domain">Malicious domain</string>
<string name="wc_alert_audit_unknown_domain">Unknown domain</string>
<string name="wc_alert_connect_anyway">Connect anyway</string>
<string name="wc_alert_connection_timeout_description">Timeout error. Please, try again later.</string>
<string name="wc_alert_connection_timeout_title">Failed to establish WalletConnect</string>
<string name="wc_alert_domain_issues_description">This domain cannot be verified. Check the request carefully approving.</string>
<string name="wc_alert_network_not_connected_description">To continue, please reconnect your dApp session with the required network %s.</string>
<string name="wc_alert_network_not_connected_title">Network not connected</string>
<string name="wc_alert_session_disconnected_description">Please return to your browser and reconnect via WalletConnect.</string>
<string name="wc_alert_session_disconnected_title">Wallet Connect session was disconnected</string>
<string name="wc_alert_sign_anyway">Sign anyway</string>
@ -1521,6 +1525,8 @@
<string name="wc_alert_unsupported_dapps_title">Unsupported dApp</string>
<string name="wc_alert_unsupported_method_description">Error code: 8 005. If the problem persists — feel free to contact our support.</string>
<string name="wc_alert_unsupported_method_title">We\'ve encountered unknown error</string>
<string name="wc_alert_unsupported_network_description">This network %s is not supported by Tangem Wallet and cannot be connected.</string>
<string name="wc_alert_unsupported_network_title">Unsupported network</string>
<string name="wc_alert_unsupported_networks_description">Tangem does not currently support a required network by %s.</string>
<string name="wc_alert_unsupported_networks_title">Unsupported networks</string>
<string name="wc_alert_verified_domain_description">This domain has passed verification checks and is considered safe, reputable, and free from known threats or suspicious activity. %s</string>

View file

@ -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<HandleMethodError, WcAddNetworkUseCase.AddNetwork> {
return addSwitchCommonDelegate
.commonChecks(method.rawChain.chainId)
.map { addedNetwork -> WcAddNetworkUseCase.AddNetwork(addedNetwork) }
}
override suspend fun approve(): Either<WcRequestError, String> {
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<HandleMethodError, Network> {
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
}
}

View file

@ -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<Throwable, WcEthMethod?> {
private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest): Either<Throwable, WcEthMethod?> {
val rawParams = request.request.params
return when (this) {
WcEthMethodName.EthSign,
@ -111,16 +110,17 @@ internal class WcEthNetwork(
}
}
?: return null.right()
WcEthMethodName.AddEthereumChain -> moshi.fromJson<List<WcEthAddChain>>(rawParams)
WcEthMethodName.AddEthereumChain,
WcEthMethodName.SwitchEthereumChain,
-> moshi.fromJson<List<WcEthAddChain>>(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,
)
}

View file

@ -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<HandleMethodError, WcSwitchNetworkUseCase.SwitchNetwork> {
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
}
}

View file

@ -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)
}

View file

@ -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,

View file

@ -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
}

View file

@ -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 {

View file

@ -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")
}

View file

@ -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<HandleMethodError, AddNetwork>
suspend fun approve(): Either<WcRequestError, String>
fun reject()
data class AddNetwork(
val network: Network,
)
}

View file

@ -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<HandleMethodError, SwitchNetwork>
fun reject()
data class SwitchNetwork(
val network: Network,
)
}

View file

@ -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()
}
}

View file

@ -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() },
),
)
}

View file

@ -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
}

View file

@ -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,

View file

@ -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<AlertsComp
collection = listOf(
AlertsComponent.AlertType.WcDisconnected(onDismiss = {}),
AlertsComponent.AlertType.UnsupportedMethod(onDismiss = {}),
AlertsComponent.AlertType.TangemUnsupportedNetwork(network = "Solana", onDismiss = {}),
AlertsComponent.AlertType.RequiredAddNetwork(network = "Solana", onDismiss = {}),
AlertsComponent.AlertType.RequiredReconnectWithNetwork(network = "Solana", onDismiss = {}),
),
)

View file

@ -7,6 +7,7 @@ import com.tangem.features.walletconnect.connections.routing.WcRoutingModel
import com.tangem.features.walletconnect.transaction.model.WcAddNetworkModel
import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel
import com.tangem.features.walletconnect.transaction.model.WcSignTransactionModel
import com.tangem.features.walletconnect.transaction.model.WcSwitchNetworkModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -57,6 +58,11 @@ internal interface WalletConnectModelModule {
@ClassKey(WcAddNetworkModel::class)
fun bindWcAddNetworkModel(model: WcAddNetworkModel): Model
@Binds
@IntoMap
@ClassKey(WcSwitchNetworkModel::class)
fun bindWcSwitchNetworkModel(model: WcSwitchNetworkModel): Model
@Binds
@IntoMap
@ClassKey(WcSendTransactionModel::class)

View file

@ -0,0 +1,21 @@
package com.tangem.features.walletconnect.transaction.components.chain
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.walletconnect.transaction.components.common.WcTransactionModelParams
import com.tangem.features.walletconnect.transaction.model.WcSwitchNetworkModel
internal class WcSwitchNetworkComponent(
appComponentContext: AppComponentContext,
params: WcTransactionModelParams,
) : AppComponentContext by appComponentContext, ComposableContentComponent {
private val model: WcSwitchNetworkModel = getOrCreateModel(params = params)
@Composable
override fun Content(modifier: Modifier) {
}
}

View file

@ -12,5 +12,9 @@ internal object WcHandleMethodErrorConverter :
is HandleMethodError.UnknownError,
-> 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)
}
}

View file

@ -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()
}

View file

@ -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<WcTransactionModelParams>()
init {
modelScope.launch {
val useCase = useCaseFactory.createUseCase<WcSwitchNetworkUseCase>(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))
}
}