From 8619b424f5b1b15cf3649e3309269a36da40b8a8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 29 Apr 2025 18:34:29 +0700 Subject: [PATCH] Updated on 2026-08-14 --- .../ethereum/WcEthMessageSignUseCase.kt | 20 ++++-- .../network/ethereum/WcEthNetwork.kt | 35 +++++++--- .../ethereum/WcEthSignTypedDataUseCase.kt | 68 +++++++++++++++++++ .../domain/walletconnect/model/WcEthMethod.kt | 8 +++ .../model/WcEthSignTypedDataParams.kt | 64 +++++++++++++++++ 5 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTypedDataUseCase.kt create mode 100644 domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt index d9a24d2d24..817e5a2ddc 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthMessageSignUseCase.kt @@ -4,16 +4,18 @@ import arrow.core.left import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak import com.tangem.blockchain.common.HEX_PREFIX import com.tangem.blockchain.common.UnmarshalHelper +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.extensions.isAscii import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey -import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate import com.tangem.common.extensions.toHexString +import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper.prepareToSendMessageData import com.tangem.data.walletconnect.respond.WcRespondService import com.tangem.data.walletconnect.sign.BaseWcSignUseCase import com.tangem.data.walletconnect.sign.SignCollector import com.tangem.data.walletconnect.sign.SignStateConverter.toResult import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext +import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate import com.tangem.domain.transaction.usecase.SignUseCase import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase @@ -56,11 +58,7 @@ internal class WcEthMessageSignUseCase @AssistedInject constructor( .onLeft { emit(state.toResult(it.left())) } .getOrNull() ?: return - val respond = UnmarshalHelper.unmarshalSignatureExtended( - signature = signedHash, - hash = hashToSign, - publicKey = walletManager.wallet.publicKey.blockchainKey.toDecompressedPublicKey(), - ).asRSVLegacyEVM().toHexString() + val respond = prepareToSendMessageData(signedHash, hashToSign, walletManager) val wcRespondResult = respondService.respond(rawSdkRequest, respond) emit(state.toResult(wcRespondResult)) @@ -80,6 +78,16 @@ internal class WcEthMessageSignUseCase @AssistedInject constructor( object LegacySdkHelper { private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n" + internal fun prepareToSendMessageData( + signedHash: ByteArray, + hashToSign: ByteArray, + walletManager: WalletManager, + ): String = UnmarshalHelper.unmarshalSignatureExtended( + signature = signedHash, + hash = hashToSign, + publicKey = walletManager.wallet.publicKey.blockchainKey.toDecompressedPublicKey(), + ).asRSVLegacyEVM().toHexString() + fun createMessageData(message: String): ByteArray { val messageData = try { message.removePrefix(HEX_PREFIX).hexToBytes() 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 6921504472..175f715409 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 @@ -12,6 +12,7 @@ import com.tangem.data.walletconnect.utils.WcNamespaceConverter import com.tangem.domain.tokens.model.Network import com.tangem.domain.walletconnect.model.WcEthMethod import com.tangem.domain.walletconnect.model.WcEthMethodName +import com.tangem.domain.walletconnect.model.WcEthSignTypedDataParams import com.tangem.domain.walletconnect.model.WcEthTransactionParams import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest import com.tangem.domain.walletconnect.repository.WcSessionsManager @@ -43,6 +44,7 @@ internal class WcEthNetwork( is WcEthMethod.MessageSign -> method.account is WcEthMethod.SendTransaction -> method.transaction.from is WcEthMethod.SignTransaction -> method.transaction.from + is WcEthMethod.SignTypedData -> method.account } val context = WcMethodUseCaseContext( session = session, @@ -54,6 +56,7 @@ internal class WcEthNetwork( is WcEthMethod.MessageSign -> factories.messageSign.create(context, method) is WcEthMethod.SendTransaction -> factories.sendTransaction.create(context, method) is WcEthMethod.SignTransaction -> factories.signTransaction.create(context, method) + is WcEthMethod.SignTypedData -> factories.signTypedData.create(context, method) } } @@ -62,16 +65,10 @@ internal class WcEthNetwork( return when (this) { WcEthMethodName.EthSign, WcEthMethodName.PersonalSign, - -> moshi.fromJson>(rawParams)?.let { list -> - val accountIndex = if (this == WcEthMethodName.EthSign) 0 else 1 - val messageIndex = if (this == WcEthMethodName.EthSign) 1 else 0 - val account = list.getOrNull(accountIndex) ?: return@let null - val message = list.getOrNull(messageIndex) ?: return@let null - val humanMsg = LegacySdkHelper.hexToAscii(message).orEmpty() - WcEthMethod.MessageSign(account = account, rawMessage = message, humanMsg = humanMsg) - } - WcEthMethodName.SignTypeData -> TODO() - WcEthMethodName.SignTypeDataV4 -> TODO() + -> parseMessageSign(rawParams) + WcEthMethodName.SignTypeData, + WcEthMethodName.SignTypeDataV4, + -> parseTypeData(rawParams) WcEthMethodName.SignTransaction, WcEthMethodName.SendTransaction, -> moshi.fromJson>(rawParams) @@ -86,6 +83,23 @@ internal class WcEthNetwork( } } + private fun WcEthMethodName.parseMessageSign(rawParams: String): WcEthMethod.MessageSign? { + val list = moshi.fromJson>(rawParams) ?: return null + 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 humanMsg = LegacySdkHelper.hexToAscii(message).orEmpty() + return WcEthMethod.MessageSign(account = account, rawMessage = message, humanMsg = humanMsg) + } + + private fun parseTypeData(params: String): WcEthMethod.SignTypedData? { + val account = params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2)) + val data = params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1) + val params = moshi.fromJson(data) ?: return null + return WcEthMethod.SignTypedData(params = params, account = account, dataForSign = data) + } + override fun toNetwork(chainId: String, wallet: UserWallet): Network? { return toNetwork(chainId, wallet, excludedBlockchains) } @@ -108,6 +122,7 @@ internal class WcEthNetwork( internal class Factories @Inject constructor( val messageSign: WcEthMessageSignUseCase.Factory, + val signTypedData: WcEthSignTypedDataUseCase.Factory, val sendTransaction: WcEthSendTransactionUseCase.Factory, val signTransaction: WcEthSignTransactionUseCase.Factory, ) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTypedDataUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTypedDataUseCase.kt new file mode 100644 index 0000000000..61ad1718c5 --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/WcEthSignTypedDataUseCase.kt @@ -0,0 +1,68 @@ +package com.tangem.data.walletconnect.network.ethereum + +import arrow.core.left +import com.tangem.blockchain.blockchains.ethereum.EthereumUtils +import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper.prepareToSendMessageData +import com.tangem.data.walletconnect.respond.WcRespondService +import com.tangem.data.walletconnect.sign.BaseWcSignUseCase +import com.tangem.data.walletconnect.sign.SignCollector +import com.tangem.data.walletconnect.sign.SignStateConverter.toResult +import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext +import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate +import com.tangem.domain.transaction.usecase.SignUseCase +import com.tangem.domain.walletconnect.model.WcEthMethod +import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase +import com.tangem.domain.walletconnect.usecase.method.WcSignState +import com.tangem.domain.walletmanager.WalletManagersFacade +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow + +internal class WcEthSignTypedDataUseCase @AssistedInject constructor( + override val respondService: WcRespondService, + @Assisted override val context: WcMethodUseCaseContext, + @Assisted override val method: WcEthMethod.SignTypedData, + private val walletManagersFacade: WalletManagersFacade, + private val signUseCase: SignUseCase, + blockAidDelegate: BlockAidVerificationDelegate, +) : BaseWcSignUseCase(), + WcMessageSignUseCase { + + override val securityStatus = blockAidDelegate.getSecurityStatus( + network = network, + method = method, + rawSdkRequest = rawSdkRequest, + session = session, + accountAddress = context.accountAddress, + ) + + override suspend fun SignCollector.onSign( + state: WcSignState, + ) { + val hashToSign = EthereumUtils.makeTypedDataHash(method.dataForSign) + val userWallet = session.wallet + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWallet.walletId, network) + ?: return + + val signedHash = signUseCase(hashToSign, userWallet, network) + .onLeft { emit(state.toResult(it.left())) } + .getOrNull() ?: return + + val respond = prepareToSendMessageData(signedHash, hashToSign, walletManager) + val wcRespondResult = respondService.respond(rawSdkRequest, respond) + emit(state.toResult(wcRespondResult)) + } + + override fun invoke(): Flow> = flow { + val model = WcMessageSignUseCase.SignModel(humanMsg = method.humanMsg) + emitAll(delegate(model)) + } + + @AssistedFactory + interface Factory { + fun create(context: WcMethodUseCaseContext, method: WcEthMethod.SignTypedData): WcEthSignTypedDataUseCase + } +} \ No newline at end of file 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 8c13ca04f2..8f85b4063f 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 @@ -8,6 +8,14 @@ sealed interface WcEthMethod : WcMethod { val humanMsg: String, ) : WcEthMethod + data class SignTypedData( + val params: WcEthSignTypedDataParams, + val account: String, + val dataForSign: String, + ) : WcEthMethod { + val humanMsg: String = params.message.contents + } + data class SendTransaction( val transaction: WcEthTransactionParams, ) : WcEthMethod 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 new file mode 100644 index 0000000000..0800719a63 --- /dev/null +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthSignTypedDataParams.kt @@ -0,0 +1,64 @@ +package com.tangem.domain.walletconnect.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class WcEthSignTypedDataParams( + @Json(name = "domain") + val domain: Domain, + @Json(name = "message") + val message: Message, + @Json(name = "primaryType") + val primaryType: String, + @Json(name = "types") + val types: Types, +) { + @JsonClass(generateAdapter = true) + data class Domain( + @Json(name = "chainId") + val chainId: Int, + @Json(name = "name") + val name: String, + @Json(name = "verifyingContract") + val verifyingContract: String, + @Json(name = "version") + val version: String, + ) + + @JsonClass(generateAdapter = true) + data class Message( + @Json(name = "contents") + val contents: String, + @Json(name = "from") + val from: Address, + @Json(name = "to") + val to: Address, + ) { + @JsonClass(generateAdapter = true) + data class Address( + @Json(name = "name") + val name: String, + @Json(name = "wallet") + val wallet: String, + ) + } + + @JsonClass(generateAdapter = true) + data class Types( + @Json(name = "EIP712Domain") + val eIP712Domain: List = listOf(), + @Json(name = "Mail") + val mail: List = listOf(), + @Json(name = "Person") + val person: List = listOf(), + ) { + @JsonClass(generateAdapter = true) + data class Type( + @Json(name = "name") + val name: String, + @Json(name = "type") + val type: String, + ) + } +} \ No newline at end of file