Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-29 18:34:29 +07:00
parent 3f0e976558
commit 8619b424f5
5 changed files with 179 additions and 16 deletions

View file

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

View file

@ -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<List<String>>(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<List<WcEthTransactionParams>>(rawParams)
@ -86,6 +83,23 @@ internal class WcEthNetwork(
}
}
private fun WcEthMethodName.parseMessageSign(rawParams: String): WcEthMethod.MessageSign? {
val list = moshi.fromJson<List<String>>(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<WcEthSignTypedDataParams>(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,
)

View file

@ -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<Nothing, WcMessageSignUseCase.SignModel>(),
WcMessageSignUseCase {
override val securityStatus = blockAidDelegate.getSecurityStatus(
network = network,
method = method,
rawSdkRequest = rawSdkRequest,
session = session,
accountAddress = context.accountAddress,
)
override suspend fun SignCollector<WcMessageSignUseCase.SignModel>.onSign(
state: WcSignState<WcMessageSignUseCase.SignModel>,
) {
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<WcSignState<WcMessageSignUseCase.SignModel>> = flow {
val model = WcMessageSignUseCase.SignModel(humanMsg = method.humanMsg)
emitAll(delegate(model))
}
@AssistedFactory
interface Factory {
fun create(context: WcMethodUseCaseContext, method: WcEthMethod.SignTypedData): WcEthSignTypedDataUseCase
}
}

View file

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

View file

@ -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<Type> = listOf(),
@Json(name = "Mail")
val mail: List<Type> = listOf(),
@Json(name = "Person")
val person: List<Type> = listOf(),
) {
@JsonClass(generateAdapter = true)
data class Type(
@Json(name = "name")
val name: String,
@Json(name = "type")
val type: String,
)
}
}