diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index aa615456cc..cc73660628 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -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] diff --git a/data/wallet-connect/build.gradle.kts b/data/wallet-connect/build.gradle.kts index 59061799bf..d1da51e0b0 100644 --- a/data/wallet-connect/build.gradle.kts +++ b/data/wallet-connect/build.gradle.kts @@ -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) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt index 8c735d8bad..7ae5173300 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/di/WalletConnectDataModule.kt @@ -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, ) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/DefaultWcPersonalEthSignUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/DefaultWcPersonalEthSignUseCase.kt new file mode 100644 index 0000000000..d6d7cd9c1f --- /dev/null +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/ethereum/DefaultWcPersonalEthSignUseCase.kt @@ -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(), + WcPersonalEthSignUseCase { + + override val onSign: OnSign = 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> = 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("") + } +} \ No newline at end of file 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 ffe057712c..a03f8d46ba 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 @@ -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(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) { wcRequest as WcRequest val useCase = when (wcRequest.method) { - is SignMessage -> TODO() + is WcEthMethod.PersonalEthSign -> TODO() } _useCases.trySend(useCase) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt index 423eeb9ea3..1c11137607 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/pair/WcPairSdkDelegate.kt @@ -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() private val onSessionSettleResponse = Channel() diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt index 6e31f2783d..341a80d075 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sessions/DefaultWcSessionsManager.kt @@ -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)) } diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt index dd432f1df6..c1d53f51b9 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/sign/BaseWcSignUseCase.kt @@ -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 : 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 { internal class WcMethodUseCaseContext( val session: WcSession, val rawSdkRequest: WcSdkSessionRequest, + val network: Network, ) internal typealias OnSign = diff --git a/domain/wallet-connect/build.gradle.kts b/domain/wallet-connect/build.gradle.kts index 9b0cbaa2e3..e6bd5dea5d 100644 --- a/domain/wallet-connect/build.gradle.kts +++ b/domain/wallet-connect/build.gradle.kts @@ -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) 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 new file mode 100644 index 0000000000..ee9e7bfc98 --- /dev/null +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcEthMethod.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.walletconnect.model + +sealed interface WcEthMethod : WcMethod { + + data class PersonalEthSign( + val message: String, + val account: String, + ) : WcEthMethod +} \ No newline at end of file diff --git a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethod.kt b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethod.kt index 05c3df4d98..fafbb31a81 100644 --- a/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethod.kt +++ b/domain/wallet-connect/models/src/main/java/com/tangem/domain/walletconnect/model/WcMethod.kt @@ -1,5 +1,5 @@ package com.tangem.domain.walletconnect.model -interface WcMethod { +sealed interface WcMethod { data object Unsupported : WcMethod } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt index 6ff2c38c73..1aff7cea04 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/WcUseCase.kt @@ -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 } \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcEthMethod.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcEthMethod.kt deleted file mode 100644 index cf40981044..0000000000 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcEthMethod.kt +++ /dev/null @@ -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, - ) : WcEthMethod -} \ No newline at end of file diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcPersonalEthSignUseCase.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcPersonalEthSignUseCase.kt new file mode 100644 index 0000000000..b853bbb096 --- /dev/null +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/usecase/ethereum/WcPersonalEthSignUseCase.kt @@ -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 { + + data class SignModel( + val humanMsg: String, + val account: String, + val rawMsg: String, + ) +} \ No newline at end of file