Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-11 01:42:48 +07:00
parent 5762ba0d16
commit 17c7ae519c
14 changed files with 154 additions and 54 deletions

View file

@ -16,6 +16,7 @@ dependencies {
implementation(projects.domain.walletConnect.models)
implementation(projects.domain.wallets)
implementation(projects.domain.wallets.models)
implementation(projects.domain.card)
implementation(projects.domain.tokens)
implementation(projects.domain.tokens.models)
implementation(projects.domain.models)

View file

@ -30,7 +30,6 @@ import com.tangem.domain.walletconnect.request.WcRequestService
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
@ -96,7 +95,6 @@ internal object WalletConnectDataModule {
store: WalletConnectStore,
dispatchers: CoroutineDispatcherProvider,
legacyStore: WalletConnectSessionsRepository,
getUserWallet: GetUserWalletUseCase,
getWallets: GetWalletsUseCase,
): DefaultWcSessionsManager {
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
@ -105,7 +103,6 @@ internal object WalletConnectDataModule {
dispatchers = dispatchers,
legacyStore = legacyStore,
getWallets = getWallets,
getUserWallet = getUserWallet,
scope = scope,
)
}

View file

@ -0,0 +1,110 @@
package com.tangem.data.walletconnect.network.ethereum
import arrow.core.left
import arrow.core.right
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
import com.tangem.blockchain.common.HEX_PREFIX
import com.tangem.blockchain.extensions.isAscii
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.data.walletconnect.sign.BaseWcSignUseCase
import com.tangem.data.walletconnect.sign.OnSign
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.walletconnect.model.WcEthMethod
import com.tangem.domain.walletconnect.usecase.ethereum.WcPersonalEthSignUseCase
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
import com.tangem.domain.walletmanager.WalletManagersFacade
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
internal class DefaultWcPersonalEthSignUseCase(
override val respondService: WcRespondService,
override val context: WcMethodUseCaseContext,
private val method: WcEthMethod.PersonalEthSign,
private val walletManagersFacade: WalletManagersFacade,
private val cardRepository: CardSdkConfigRepository,
) : BaseWcSignUseCase<Nothing, WcPersonalEthSignUseCase.SignModel>(),
WcPersonalEthSignUseCase {
override val onSign: OnSign<WcPersonalEthSignUseCase.SignModel> = collector@{ state ->
val hashToSign = LegacySdkHelper.createMessageData(state.signModel.rawMsg)
val userWallet = session.wallet
val card = userWallet.scanResponse.card
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
val walletManager = walletManagersFacade.getOrCreateWalletManager(userWallet.walletId, network)
?: return@collector
val signer = cardRepository.getCommonSigner(
cardId = card.cardId.takeIf { isCardNotBackedUp },
twinKey = TwinKey.getOrNull(scanResponse = userWallet.scanResponse),
)
val signResult = when (val signResult = signer.sign(hashToSign, walletManager.wallet.publicKey)) {
is CompletionResult.Failure -> signResult.error.left()
is CompletionResult.Success -> signResult.data.right()
}
val signedHash = signResult
.onLeft { emit(state.toResult(it.left())) }
.getOrNull() ?: return@collector
val respond = EthereumUtils.prepareSignedMessageData(
signedHash = signedHash,
hashToSign = hashToSign,
publicKey = walletManager.wallet.publicKey.blockchainKey.toDecompressedPublicKey(),
)
val wcRespondResult = respondService.respond(rawSdkRequest, respond)
emit(state.toResult(wcRespondResult))
}
override fun invoke(): Flow<WcSignState<WcPersonalEthSignUseCase.SignModel>> = flow {
val model = WcPersonalEthSignUseCase.SignModel(
rawMsg = method.message,
account = method.account,
humanMsg = LegacySdkHelper.hexToAscii(method.message).orEmpty(),
)
emitAll(delegate(model))
}
}
object LegacySdkHelper {
private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n"
fun createMessageData(message: String): ByteArray {
val messageData = try {
message.removePrefix(HEX_PREFIX).hexToBytes()
} catch (exception: Exception) {
message.asciiToHex()?.hexToBytes() ?: byteArrayOf()
}
val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray()
return (prefixData + messageData).toKeccak()
}
fun hexToAscii(hex: String): String? {
return try {
hex.removePrefix(HEX_PREFIX).hexToBytes().map {
val char = it.toInt().toChar()
if (char.isAscii()) char else return null
}.joinToString("")
} catch (exception: Exception) {
return null
}
}
private fun String.asciiToHex(): String? {
return map {
if (!it.isAscii()) return null
Integer.toHexString(it.code)
}.joinToString("")
}
}

View file

@ -10,8 +10,7 @@ import com.tangem.domain.walletconnect.model.WcMethod
import com.tangem.domain.walletconnect.model.WcRequest
import com.tangem.domain.walletconnect.usecase.WcUseCase
import com.tangem.domain.walletconnect.usecase.WcUseCasesFlowProvider
import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod
import com.tangem.domain.walletconnect.usecase.ethereum.WcEthMethod.SignMessage
import com.tangem.domain.walletconnect.model.WcEthMethod
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
@ -32,7 +31,7 @@ internal class WcEthNetwork(
val name = Name.entries.find { it.raw == methodName } ?: return null
return when (name) {
Name.Sign -> TODO()
Name.PersonalSign -> WcMethodHandler.fromJson<SignMessage>(params, moshi)
Name.PersonalSign -> TODO()
Name.SignTypeData -> TODO()
Name.SignTypeDataV4 -> TODO()
Name.SignTransaction -> TODO()
@ -43,7 +42,7 @@ internal class WcEthNetwork(
override fun handle(wcRequest: WcRequest<WcMethod>) {
wcRequest as WcRequest<WcEthMethod>
val useCase = when (wcRequest.method) {
is SignMessage -> TODO()
is WcEthMethod.PersonalEthSign -> TODO()
}
_useCases.trySend(useCase)
}

View file

@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
class WcPairSdkDelegate : WcSdkObserver {
internal class WcPairSdkDelegate : WcSdkObserver {
private val onSessionProposal = Channel<Wallet.Model.SessionProposal>()
private val onSessionSettleResponse = Channel<Wallet.Model.SettledSessionResponse>()

View file

@ -13,7 +13,6 @@ import com.tangem.domain.walletconnect.model.WcSessionDTO
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
import com.tangem.domain.walletconnect.repository.WcSessionsManager
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
@ -30,7 +29,6 @@ internal class DefaultWcSessionsManager constructor(
private val store: WalletConnectStore,
private val legacyStore: WalletConnectSessionsRepository,
private val getWallets: GetWalletsUseCase,
private val getUserWallet: GetUserWalletUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val scope: CoroutineScope,
) : WcSessionsManager, WcSdkObserver {
@ -84,9 +82,12 @@ internal class DefaultWcSessionsManager constructor(
}
override suspend fun findSessionByTopic(topic: String): WcSession? = withContext(dispatchers.io) {
val storedSessions = store.findSessionByTopic(topic) ?: return@withContext null
val storedSessions = sessions.firstOrNull()
?.values?.flatten()
?.firstOrNull { it.sdkModel.topic == topic }
?: return@withContext null
val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null
val wallet = getUserWallet.invoke(storedSessions.walletId).getOrNull() ?: return@withContext null
val wallet = storedSessions.wallet
WcSession(wallet = wallet, sdkModel = WcSdkSessionConverter.convert(sdkSession))
}

View file

@ -3,6 +3,7 @@ package com.tangem.data.walletconnect.sign
import com.tangem.domain.walletconnect.model.WcSession
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
import com.tangem.data.walletconnect.respond.WcRespondService
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.walletconnect.usecase.WcMethodUseCase
import com.tangem.domain.walletconnect.usecase.sign.WcSignState
import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase
@ -17,6 +18,7 @@ internal abstract class BaseWcSignUseCase<MiddleAction, SignModel> :
abstract val respondService: WcRespondService
abstract val context: WcMethodUseCaseContext
override val network: Network get() = context.network
override val session: WcSession get() = context.session
override val rawSdkRequest: WcSdkSessionRequest get() = context.rawSdkRequest
@ -55,6 +57,7 @@ internal interface FinalActionCollector<SignModel> {
internal class WcMethodUseCaseContext(
val session: WcSession,
val rawSdkRequest: WcSdkSessionRequest,
val network: Network,
)
internal typealias OnSign<SignModel> =