From c1ad1e43ce8472909ff4e3b6bc965b41e1acb0f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 18 Jul 2025 22:03:10 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../network/ethereum/WcEthNetwork.kt | 62 +++++++++++++------ .../network/solana/WcSolanaNetwork.kt | 54 ++++++++++------ .../request/DefaultWcRequestUseCaseFactory.kt | 17 ++++- .../request/WcRequestToUseCaseConverter.kt | 15 ++++- .../respond/DefaultWcRespondService.kt | 17 +++-- .../domain/walletconnect/model/WcEthMethod.kt | 2 +- .../model/WcEthSignTypedDataParams.kt | 8 +-- .../walletconnect/model/WcRequestError.kt | 9 ++- 8 files changed, 127 insertions(+), 57 deletions(-) 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 66cbe92413..df5c570220 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 @@ -1,5 +1,9 @@ package com.tangem.data.walletconnect.network.ethereum +import arrow.core.Either +import arrow.core.getOrElse +import arrow.core.left +import arrow.core.right import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.blockchainsdk.utils.ExcludedBlockchains @@ -35,12 +39,18 @@ internal class WcEthNetwork( } @Suppress("CyclomaticComplexMethod") - override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? { - val name = toWcMethodName(request) ?: return null - val session = sessionsManager.findSessionByTopic(request.topic) ?: return null + override suspend fun toUseCase( + request: WcSdkSessionRequest, + ): Either { + fun error(message: String) = WcRequestError.HandleMethodError(message).left() + val name = toWcMethodName(request) ?: return error("Unknown method name") + val session = sessionsManager.findSessionByTopic(request.topic) + ?: return error("Failed to find session for topic ${request.topic}") val wallet = session.wallet val chainId = request.chainId.orEmpty() - val method: WcEthMethod = name.toMethod(request, wallet) ?: return null + val method: WcEthMethod = name.toMethod(request, wallet) + .getOrElse { return error(it.message.orEmpty()) } + ?: return error("Failed to parse $name") suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) val accountAddress = when (method) { @@ -60,7 +70,7 @@ internal class WcEthNetwork( is WcEthMethod.SignTransaction, -> networksConverter.findWalletNetworkForRequest(request, session, accountAddress) is WcEthMethod.AddEthereumChain -> anyExistNetwork() - } ?: return null + } ?: return error("Failed to find walletNetwork for accountAddress $accountAddress") val context = WcMethodUseCaseContext( session = session, @@ -74,10 +84,13 @@ 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) - } + }.right() } - private suspend fun WcEthMethodName.toMethod(request: WcSdkSessionRequest, wallet: UserWallet): WcEthMethod? { + private suspend fun WcEthMethodName.toMethod( + request: WcSdkSessionRequest, + wallet: UserWallet, + ): Either { val rawParams = request.request.params return when (this) { WcEthMethodName.EthSign, @@ -89,39 +102,48 @@ internal class WcEthNetwork( WcEthMethodName.SignTransaction, WcEthMethodName.SendTransaction, -> moshi.fromJson>(rawParams) + .getOrElse { return it.left() } ?.firstOrNull() ?.let { if (this == WcEthMethodName.SignTransaction) { - WcEthMethod.SignTransaction(transaction = it) + WcEthMethod.SignTransaction(transaction = it).right() } else { - WcEthMethod.SendTransaction(transaction = it) + WcEthMethod.SendTransaction(transaction = it).right() } } + ?: return null.right() WcEthMethodName.AddEthereumChain -> moshi.fromJson>(rawParams) + .getOrElse { return it.left() } ?.firstOrNull() ?.let { - val newNetwork = networksConverter.mainOrAnyWalletNetworkForRequest(it.chainId, wallet) - ?: return null - WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork) + val newNetwork = networksConverter + .mainOrAnyWalletNetworkForRequest(it.chainId, wallet) + ?: return null.right() + WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork).right() } + ?: null.right() } } - private fun WcEthMethodName.parseMessageSign(rawParams: String): WcEthMethod.MessageSign? { - val list = moshi.fromJson>(rawParams) ?: return null + private fun WcEthMethodName.parseMessageSign(rawParams: String): Either { + val list = moshi.fromJson>(rawParams) + .getOrElse { return it.left() } + ?: return null.right() val accountIndex = if (this == WcEthMethodName.EthSign) 0 else 1 val messageIndex = if (this == WcEthMethodName.EthSign) 1 else 0 - val account = list.getOrNull(accountIndex) ?: return null - val message = list.getOrNull(messageIndex) ?: return null + val account = list.getOrNull(accountIndex) ?: return null.right() + val message = list.getOrNull(messageIndex) ?: return null.right() val humanMsg = LegacySdkHelper.hexToAscii(message).orEmpty() - return WcEthMethod.MessageSign(account = account, rawMessage = message, humanMsg = humanMsg) + return WcEthMethod.MessageSign(account = account, rawMessage = message, humanMsg = humanMsg).right() } - private fun parseTypeData(params: String): WcEthMethod.SignTypedData? { + private fun parseTypeData(params: String): Either { val account = params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2)) val data = params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1) - val parsedParams = moshi.fromJson(data) ?: return null - return WcEthMethod.SignTypedData(params = parsedParams, account = account, dataForSign = data) + val parsedParams = moshi.fromJson(data) + .getOrElse { return it.left() } + ?: return null.right() + return WcEthMethod.SignTypedData(params = parsedParams, account = account, dataForSign = data).right() } internal class NamespaceConverter( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt index f994aba0f3..e640f5cb36 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaNetwork.kt @@ -1,11 +1,16 @@ package com.tangem.data.walletconnect.network.solana +import arrow.core.Either +import arrow.core.getOrElse +import arrow.core.left +import arrow.core.right import com.squareup.moshi.Moshi import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.extensions.decodeBase58 import com.tangem.blockchainsdk.utils.ExcludedBlockchains import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.common.extensions.toHexString +import com.tangem.data.walletconnect.model.CAIP2 import com.tangem.data.walletconnect.model.NamespaceKey import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson @@ -13,7 +18,7 @@ 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.network.Network -import com.tangem.data.walletconnect.model.CAIP2 +import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.model.WcSolanaMethodName import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest @@ -37,10 +42,16 @@ internal class WcSolanaNetwork( } @Suppress("CyclomaticComplexMethod") - override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? { - val name = toWcMethodName(request) ?: return null - val method: WcSolanaMethod = name.toMethod(request) ?: return null - val session = sessionsManager.findSessionByTopic(request.topic) ?: return null + override suspend fun toUseCase( + request: WcSdkSessionRequest, + ): Either { + fun error(message: String) = WcRequestError.HandleMethodError(message).left() + val name = toWcMethodName(request) ?: return error("Unknown method name") + val method: WcSolanaMethod = name.toMethod(request) + .getOrElse { return error(it.message.orEmpty()) } + ?: return error("Failed to parse $name") + val session = sessionsManager.findSessionByTopic(request.topic) + ?: return error("Failed to find session for topic ${request.topic}") val wallet = session.wallet val chainId = request.chainId.orEmpty() suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet) @@ -56,7 +67,7 @@ internal class WcSolanaNetwork( val walletNetwork = networksConverter .findWalletNetworkForRequest(request, session, accountAddress) ?: anyExistNetwork() - ?: return null + ?: return error("Failed to find walletNetwork for accountAddress $accountAddress") val context = WcMethodUseCaseContext( session = session, @@ -68,7 +79,7 @@ internal class WcSolanaNetwork( is WcSolanaMethod.SignMessage -> factories.messageSign.create(context, method) is WcSolanaMethod.SignTransaction -> factories.signTransaction.create(context, method) is WcSolanaMethod.SignAllTransaction -> factories.signAllTransaction.create(context, method) - } + }.right() } internal class NamespaceConverter @Inject constructor( @@ -101,23 +112,26 @@ internal class WcSolanaNetwork( } } - private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? { + private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): Either { val rawParams = request.request.params return when (this) { - WcSolanaMethodName.SignMessage -> moshi.fromJson(rawParams)?.let { request -> - val humanMsg = request.message.decodeBase58()?.toHexString().orEmpty() - WcSolanaMethod.SignMessage( - pubKey = request.publicKey, - rawMessage = request.message, - humanMsg = humanMsg, - ) - } + WcSolanaMethodName.SignMessage -> moshi.fromJson(rawParams) + .getOrElse { return it.left() } + ?.let { request -> + val humanMsg = request.message.decodeBase58()?.toHexString().orEmpty() + WcSolanaMethod.SignMessage( + pubKey = request.publicKey, + rawMessage = request.message, + humanMsg = humanMsg, + ) + } WcSolanaMethodName.SignTransaction -> moshi.fromJson(rawParams) + .getOrElse { return it.left() } ?.let { request -> WcSolanaMethod.SignTransaction(request.transaction, request.feePayer) } - WcSolanaMethodName.SendAllTransaction -> moshi.fromJson>(rawParams)?.let { list -> - WcSolanaMethod.SignAllTransaction(list) - } - } + WcSolanaMethodName.SendAllTransaction -> moshi.fromJson>(rawParams) + .getOrElse { return it.left() } + ?.let { list -> WcSolanaMethod.SignAllTransaction(list) } + }.right() } internal class Factories @Inject constructor( diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt index 3cc56ffebd..dfcecb1a92 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/DefaultWcRequestUseCaseFactory.kt @@ -3,10 +3,13 @@ package com.tangem.data.walletconnect.request import arrow.core.Either import arrow.core.left import arrow.core.right +import com.tangem.data.walletconnect.utils.WC_TAG import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.model.WcMethod +import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase +import timber.log.Timber import javax.inject.Inject internal class DefaultWcRequestUseCaseFactory @Inject constructor( @@ -17,7 +20,17 @@ internal class DefaultWcRequestUseCaseFactory @Inject constructor( override suspend fun createUseCase( request: WcSdkSessionRequest, ): Either { - val useCase = requestConverters.firstNotNullOfOrNull { converter -> converter.toUseCase(request) } - return (useCase as? T)?.let { useCase.right() } ?: WcMethod.Unsupported(request).left() + val useCase = requestConverters + .find { it.toWcMethodName(request) != null } + ?.toUseCase(request) + ?: WcRequestError.HandleMethodError("Failed to create WcUseCase").left() + + return useCase.fold( + ifLeft = { + Timber.tag(WC_TAG).e("$it") + WcMethod.Unsupported(request).left() + }, + ifRight = { (it as? T)?.right() ?: WcMethod.Unsupported(request).left() }, + ) } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcRequestToUseCaseConverter.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcRequestToUseCaseConverter.kt index e0a679f05e..4f7f8e7ad3 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcRequestToUseCaseConverter.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/request/WcRequestToUseCaseConverter.kt @@ -1,18 +1,27 @@ package com.tangem.data.walletconnect.request +import arrow.core.Either +import arrow.core.left +import arrow.core.right import com.squareup.moshi.Moshi import com.squareup.moshi.adapter import com.tangem.domain.walletconnect.model.WcMethodName +import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase interface WcRequestToUseCaseConverter { fun toWcMethodName(request: WcSdkSessionRequest): WcMethodName? - suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? + suspend fun toUseCase(request: WcSdkSessionRequest): Either companion object { @OptIn(ExperimentalStdlibApi::class) - inline fun Moshi.fromJson(params: String): T? = - runCatching { this.adapter().fromJson(params) }.getOrNull() + inline fun Moshi.fromJson(params: String): Either { + return runCatching { this.adapter().fromJson(params) } + .fold( + onSuccess = { it.right() }, + onFailure = { it.left() }, + ) + } } } \ No newline at end of file diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt index 8393816677..3b8ea4f337 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/respond/DefaultWcRespondService.kt @@ -26,13 +26,18 @@ internal class DefaultWcRespondService : WcRespondService { ), onSuccess = { if (continuation.isCompleted) return@respondSessionRequest - Timber.tag(WC_TAG).i("Successful respond for request $request") val result = when (val response = it.jsonRpcResponse) { - is Wallet.Model.JsonRpcResponse.JsonRpcError -> WcRequestError.WcRespondError( - code = response.code, - message = response.message, - ).left() - is Wallet.Model.JsonRpcResponse.JsonRpcResult -> response.result.right() + is Wallet.Model.JsonRpcResponse.JsonRpcError -> { + Timber.tag(WC_TAG).e("Failed respond $response for request $request") + WcRequestError.WcRespondError( + code = response.code, + message = response.message, + ).left() + } + is Wallet.Model.JsonRpcResponse.JsonRpcResult -> { + Timber.tag(WC_TAG).i("Successful respond $response for request $request") + response.result.right() + } } continuation.resume(result) }, 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 9382574cf3..24a4878ec4 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 @@ -15,7 +15,7 @@ sealed interface WcEthMethod : WcMethod { val account: String, val dataForSign: String, ) : WcEthMethod { - val humanMsg: String = params.message.contents + val humanMsg: String = params.message.contents.orEmpty() } data class SendTransaction( diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt index 0800719a63..0054a9c68c 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt @@ -12,7 +12,7 @@ data class WcEthSignTypedDataParams( @Json(name = "primaryType") val primaryType: String, @Json(name = "types") - val types: Types, + val types: Map>, ) { @JsonClass(generateAdapter = true) data class Domain( @@ -29,11 +29,11 @@ data class WcEthSignTypedDataParams( @JsonClass(generateAdapter = true) data class Message( @Json(name = "contents") - val contents: String, + val contents: String?, @Json(name = "from") - val from: Address, + val from: Address?, @Json(name = "to") - val to: Address, + val to: Address?, ) { @JsonClass(generateAdapter = true) data class Address( 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 55a61db209..58cc9cd17a 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 @@ -13,6 +13,10 @@ sealed class WcRequestError { val message: String, ) : WcRequestError() + data class HandleMethodError( + val message: String, + ) : WcRequestError() + data class UnknownError(val ex: Throwable? = null) : WcRequestError() companion object { @@ -21,10 +25,13 @@ sealed class WcRequestError { is UnknownError -> ex?.message is WcRespondError -> message is WrappedSendError -> sendTransactionError.message() + is HandleMethodError -> message } fun WcRequestError.code(): String? = when (this) { - is UnknownError -> null + is HandleMethodError, + is UnknownError, + -> null is WcRespondError -> this.code.toString() is WrappedSendError -> sendTransactionError.code() }