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

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