Updated on 2026-08-14
This commit is contained in:
parent
5762ba0d16
commit
17c7ae519c
14 changed files with 154 additions and 54 deletions
|
|
@ -4,7 +4,6 @@ import com.tangem.Message
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.*
|
||||
|
|
@ -16,6 +15,7 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
|
||||
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
@ -357,36 +357,9 @@ class WalletConnectSdkHelper {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray {
|
||||
val messageData = try {
|
||||
message.data.removePrefix(HEX_PREFIX).hexToBytes()
|
||||
} catch (exception: Exception) {
|
||||
message.data.asciiToHex()?.hexToBytes() ?: byteArrayOf()
|
||||
}
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray = LegacySdkHelper.createMessageData(message.data)
|
||||
|
||||
val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray()
|
||||
return (prefixData + messageData).toKeccak()
|
||||
}
|
||||
|
||||
private fun String.hexToAscii(): String? {
|
||||
return try {
|
||||
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("")
|
||||
}
|
||||
private fun String.hexToAscii(): String? = LegacySdkHelper.hexToAscii(hex = this)
|
||||
|
||||
suspend fun signPersonalMessage(
|
||||
hashToSign: ByteArray,
|
||||
|
|
@ -535,7 +508,6 @@ class WalletConnectSdkHelper {
|
|||
"{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}"
|
||||
|
||||
private companion object {
|
||||
const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n"
|
||||
const val HEX_PREFIX = "0x"
|
||||
const val DEFAULT_MAX_GASLIMIT = 350000
|
||||
// TODO remove after [REDACTED_JIRA]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> =
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ plugins {
|
|||
dependencies {
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.walletConnect.models)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
sealed interface WcEthMethod : WcMethod {
|
||||
|
||||
data class PersonalEthSign(
|
||||
val message: String,
|
||||
val account: String,
|
||||
) : WcEthMethod
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
package com.tangem.domain.walletconnect.model
|
||||
|
||||
interface WcMethod {
|
||||
sealed interface WcMethod {
|
||||
data object Unsupported : WcMethod
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.walletconnect.usecase
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
||||
|
|
@ -8,4 +9,5 @@ interface WcUseCase
|
|||
interface WcMethodUseCase : WcUseCase {
|
||||
val session: WcSession
|
||||
val rawSdkRequest: WcSdkSessionRequest
|
||||
val network: Network
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.domain.walletconnect.usecase.ethereum
|
||||
|
||||
import com.tangem.domain.walletconnect.model.WcMethod
|
||||
|
||||
sealed interface WcEthMethod : WcMethod {
|
||||
|
||||
data class SignMessage(
|
||||
val raw: List<String>,
|
||||
) : WcEthMethod
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.domain.walletconnect.usecase.ethereum
|
||||
|
||||
import com.tangem.domain.walletconnect.usecase.sign.WcSignUseCase
|
||||
|
||||
interface WcPersonalEthSignUseCase :
|
||||
WcSignUseCase,
|
||||
WcSignUseCase.FinalAction,
|
||||
WcSignUseCase.SimpleRun<WcPersonalEthSignUseCase.SignModel> {
|
||||
|
||||
data class SignModel(
|
||||
val humanMsg: String,
|
||||
val account: String,
|
||||
val rawMsg: String,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue