diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 4f4494f2bc..68fc7580bc 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -121,6 +121,10 @@ class DialogManager : StoreSubscriber { onReject = state.dialog.onReject, ) } + is WalletConnectDialog.SignTransactionDialog -> SignTransactionDialog.create( + preparedData = state.dialog.data, + context = context, + ) is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index ed47afa57c..6ea8b5b88f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -5,7 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] */ -sealed class WalletConnect( +internal sealed class WalletConnect( event: String, params: Map = mapOf(), error: Throwable? = null, @@ -65,7 +65,7 @@ sealed class WalletConnect( FAIL("Fail"), } - companion object { - private const val SUCCESS_CODE = "0" + private companion object { + const val SUCCESS_CODE = "0" } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt index 9e08d07d76..16a35bf539 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -14,7 +14,8 @@ import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import timber.log.Timber -object BnbHelper { +internal object BnbHelper { + fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { val input = order.msgs.first().inputs.first() val output = order.msgs.first().inputs.first() diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index b99b599dfd..9078975c2c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit import kotlin.collections.set @Suppress("LargeClass") -class WalletConnectManager { +internal class WalletConnectManager { private var cardId: String? = null @@ -348,6 +348,7 @@ class WalletConnectManager { val hash = WalletConnectSdkHelper().signPersonalMessage( hashToSign = data.hash, networkId = activeData.wallet.blockchain?.toNetworkId() ?: "", + type = data.type, derivationPath = activeData.wallet.derivationPath?.rawPath, cardId = cardId, ) @@ -522,9 +523,9 @@ class WalletConnectManager { } } -typealias Topic = String +internal typealias Topic = String -data class WalletConnectActiveData( +internal data class WalletConnectActiveData( val peerId: String, val remotePeerId: String?, val client: WCClient, @@ -547,7 +548,7 @@ data class WalletConnectActiveData( } @Suppress("MagicNumber") -class RetryInterceptor : Interceptor { +internal class RetryInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request: Request = chain.request() val response = chain.proceed(request) 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 a1c5cb5c0b..12dc242d80 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 @@ -7,10 +7,7 @@ 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.Result -import com.tangem.blockchain.extensions.SimpleResult -import com.tangem.blockchain.extensions.hexToBigDecimal -import com.tangem.blockchain.extensions.isAscii +import com.tangem.blockchain.extensions.* import com.tangem.common.CompletionResult import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toDecompressedPublicKey @@ -25,8 +22,9 @@ import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.extensions.toFormattedString import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTradeOrder import com.tangem.tap.domain.walletconnect.BnbHelper.toWCBinanceTransferOrder -import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage +import com.tangem.tap.domain.walletconnect2.domain.TransactionType import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction +import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder @@ -45,6 +43,7 @@ import timber.log.Timber import java.math.BigDecimal import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam +@Suppress("LargeClass") class WalletConnectSdkHelper { @Suppress("MagicNumber") @@ -206,8 +205,7 @@ class WalletConnectSdkHelper { walletPublicKey = data.walletManager.wallet.publicKey.seedKey, derivationPath = data.walletManager.wallet.publicKey.derivationPath, ) - val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId) - return when (result) { + return when (val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId)) { is CompletionResult.Success -> { val hash = EthereumUtils.prepareTransactionToSend( signature = result.data.signature, @@ -250,8 +248,7 @@ class WalletConnectSdkHelper { walletPublicKey = wallet.publicKey.seedKey, derivationPath = wallet.publicKey.derivationPath, ) - val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId) - return when (result) { + return when (val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId)) { is CompletionResult.Success -> { val key = wallet.publicKey.blockchainKey.toDecompressedPublicKey() getBnbResultString( @@ -267,16 +264,17 @@ class WalletConnectSdkHelper { } fun prepareDataForPersonalSign( - message: WcEthereumSignMessage, + message: WcSignMessage, topic: String, metaName: String, id: Long, ): WcPersonalSignData { val messageData = when (message.type) { - WcEthereumSignMessage.WCSignType.MESSAGE, - WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE, + WcSignMessage.WCSignType.MESSAGE, + WcSignMessage.WCSignType.PERSONAL_MESSAGE, -> createMessageData(message) - WcEthereumSignMessage.WCSignType.TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data) + WcSignMessage.WCSignType.TYPED_MESSAGE -> EthereumUtils.makeTypedDataHash(message.data) + WcSignMessage.WCSignType.SOLANA_MESSAGE -> message.data.decodeBase58() } val messageString = message.data.hexToAscii() @@ -290,7 +288,7 @@ class WalletConnectSdkHelper { id = id, ) return WcPersonalSignData( - hash = messageData, + hash = requireNotNull(messageData) { "Message data must not be null" }, topic = topic, id = id, dialogData = dialogData, @@ -298,7 +296,7 @@ class WalletConnectSdkHelper { ) } - private fun createMessageData(message: WcEthereumSignMessage): ByteArray { + private fun createMessageData(message: WcSignMessage): ByteArray { val messageData = try { message.data.removePrefix(HEX_PREFIX).hexToBytes() } catch (exception: Exception) { @@ -332,6 +330,7 @@ class WalletConnectSdkHelper { suspend fun signPersonalMessage( hashToSign: ByteArray, networkId: String, + type: WcSignMessage.WCSignType, derivationPath: String?, cardId: String?, ): String? { @@ -345,12 +344,16 @@ class WalletConnectSdkHelper { ) return when (val result = tangemSdkManager.runTaskAsync(command, cardId)) { is CompletionResult.Success -> { - val hash = result.data.signature - return EthereumUtils.prepareSignedMessageData( - signedHash = hash, - hashToSign = hashToSign, - publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(), - ) + val signedHash = result.data.signature + + return when (type) { + WcSignMessage.WCSignType.SOLANA_MESSAGE -> getSolanaResultString(signedHash) + else -> EthereumUtils.prepareSignedMessageData( + signedHash = signedHash, + hashToSign = hashToSign, + publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(), + ) + } } is CompletionResult.Failure -> { Timber.e(result.error.customMessage) @@ -359,12 +362,46 @@ class WalletConnectSdkHelper { } } - companion object { - private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n" - private const val HEX_PREFIX = "0x" - private const val DEFAULT_MAX_GASLIMIT = 350000 - fun getBnbResultString(publicKey: String, signature: String): String { - return "{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}" + suspend fun signTransaction( + hashToSign: ByteArray, + networkId: String, + type: TransactionType, + derivationPath: String?, + cardId: String?, + ): String? { + val blockchain = Blockchain.fromNetworkId(networkId) ?: return null + val wallet = getWalletManager(blockchain, derivationPath)?.wallet ?: return null + + val command = SignHashCommand( + hash = hashToSign, + walletPublicKey = wallet.publicKey.seedKey, + derivationPath = wallet.publicKey.derivationPath, + ) + return when (val result = tangemSdkManager.runTaskAsync(command, cardId)) { + is CompletionResult.Success -> { + val signedHash = result.data.signature + + when (type) { + TransactionType.SOLANA_TX -> { + getSolanaResultString(signedHash) + } + } + } + is CompletionResult.Failure -> { + Timber.e(result.error.customMessage) + null + } } } + + private fun getSolanaResultString(signedHash: ByteArray) = "{ signature: \"${signedHash.encodeBase58()}\" }" + + private fun getBnbResultString(publicKey: String, signature: String) = + "{\"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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt index 3197f2729c..d227a42923 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/extensions/WcPeerMeta.kt @@ -1,18 +1,18 @@ package com.tangem.tap.domain.walletconnect.extensions -import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage import com.tangem.tap.domain.walletconnect2.domain.WcEthereumTransaction +import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage import com.trustwallet.walletconnect.models.WCPeerMeta import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction -fun WCPeerMeta.isDappSupported(): Boolean { +internal fun WCPeerMeta.isDappSupported(): Boolean { return !unsupportedDappsList.any { this.url.contains(it) } } private val unsupportedDappsList: List = listOf("dydx.exchange") -fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction { +internal fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction { return WcEthereumTransaction( from = from, to = to, @@ -27,13 +27,13 @@ fun WCEthereumTransaction.toWcEthTransaction(): WcEthereumTransaction { ) } -fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcEthereumSignMessage { - return WcEthereumSignMessage( +internal fun WCEthereumSignMessage.toWcEthereumSignMessage(): WcSignMessage { + return WcSignMessage( raw = raw, type = when (type) { - WCEthereumSignMessage.WCSignType.MESSAGE -> WcEthereumSignMessage.WCSignType.MESSAGE - WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE - WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcEthereumSignMessage.WCSignType.TYPED_MESSAGE + WCEthereumSignMessage.WCSignType.MESSAGE -> WcSignMessage.WCSignType.MESSAGE + WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE -> WcSignMessage.WCSignType.PERSONAL_MESSAGE + WCEthereumSignMessage.WCSignType.TYPED_MESSAGE -> WcSignMessage.WCSignType.TYPED_MESSAGE }, ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt index 3b69331ae2..63e68ba973 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/TangemWcBlockchainHelper.kt @@ -4,8 +4,18 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.extensions.toNetworkId import com.tangem.tap.domain.walletconnect2.domain.WcBlockchainHelper +import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles + +internal class TangemWcBlockchainHelper( + private val featureToggles: WalletConnectFeatureToggles, +) : WcBlockchainHelper { + + private val supportedNonEvmBlockchains = if (featureToggles.isSolanaTxSignEnabled) { + setOf(Blockchain.Solana, Blockchain.SolanaTestnet) + } else { + emptySet() + } -class TangemWcBlockchainHelper : WcBlockchainHelper { override fun chainIdToNetworkIdOrNull(chainId: String): String? { val parsedId = chainId.parseId() ?: return null val blockchain = parsedId.chainIdToBlockchain() @@ -41,48 +51,57 @@ class TangemWcBlockchainHelper : WcBlockchainHelper { val networkId = chainIdToNetworkIdOrNull(chainId) ?: return null return Blockchain.fromNetworkId(networkId)?.fullName } -} -private fun Blockchain.getCaip2ChainId(): String? { - if (this.isEvm()) return this.getChainId()?.toString() + private fun Blockchain.getCaip2ChainId(): String? { + if (this.isEvm()) return this.getChainId()?.toString() - return when (this) { - Blockchain.Solana -> "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ" - Blockchain.Polkadot -> "91b171bb158e2d3848fa23a9f1c25182" - Blockchain.Tron -> "0x2b6653dc" - else -> null - } -} - -private fun Blockchain.getCaip2Namespace(): String? { - return when { - this.isEvm() -> EVM_NAMESPACE - supportedNonEvmBlockchains.contains(this) -> this.toNetworkId() - else -> null - } -} - -private fun String.parseId(): Pair? { - val parsed = this.split(CHAIN_SEPARATOR) - if (parsed.size != 2) return null - - return parsed[0] to parsed[1] -} - -private fun Pair.chainIdToBlockchain(): Blockchain? { - return when (first) { - EVM_NAMESPACE -> { - second.toIntOrNull() - ?.let(Blockchain::fromChainId) - } - else -> { - Blockchain.fromNetworkId(networkId = first) - .takeIf(supportedNonEvmBlockchains::contains) + return when (this) { + /* + * The WC sample application and documentation use a commented out chain ID. However, in real dApps + * uncommented is used. + * Docs: https://docs.walletconnect.com/advanced/multichain/chain-list + * + * Blockchain.Solana -> "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" + * */ + Blockchain.Solana -> "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ" + Blockchain.SolanaTestnet -> "z4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z" + Blockchain.Polkadot -> "91b171bb158e2d3848fa23a9f1c25182" + Blockchain.Tron -> "0x2b6653dc" + else -> null } } -} -private val supportedNonEvmBlockchains = emptySet() // TODO: add supported networks + private fun Blockchain.getCaip2Namespace(): String? { + return when { + this.isEvm() -> EVM_NAMESPACE + supportedNonEvmBlockchains.contains(this) -> this.toNetworkId().substringBefore(TESTNET_SEPARATOR) + else -> null + } + } -private const val EVM_NAMESPACE = "eip155" -private const val CHAIN_SEPARATOR = ":" \ No newline at end of file + private fun String.parseId(): Pair? { + val parsed = this.split(CHAIN_SEPARATOR) + if (parsed.size != 2) return null + + return parsed[0] to parsed[1] + } + + private fun Pair.chainIdToBlockchain(): Blockchain? { + return when (first) { + EVM_NAMESPACE -> { + second.toIntOrNull() + ?.let(Blockchain::fromChainId) + } + else -> { + Blockchain.fromNetworkId(networkId = first) + .takeIf(supportedNonEvmBlockchains::contains) + } + } + } + + private companion object { + const val EVM_NAMESPACE = "eip155" + const val CHAIN_SEPARATOR = ":" + const val TESTNET_SEPARATOR = "/" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt index d6767ddc8e..909f4dafb7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/app/WalletConnectEventsHandlerImpl.kt @@ -12,7 +12,7 @@ import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import com.tangem.tap.store import timber.log.Timber -class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler { +internal class WalletConnectEventsHandlerImpl : WalletConnectEventsHandler { override fun onProposalReceived(proposal: WalletConnectEvents.SessionProposal, networksFormatted: String) { store.dispatchOnMain( GlobalAction.ShowDialog( diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt similarity index 96% rename from app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt rename to app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt index 71f910ce82..46577399e9 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt @@ -18,9 +18,8 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import timber.log.Timber -import javax.inject.Inject -class WalletConnectRepositoryImpl @Inject constructor( +internal class DefaultWalletConnectRepository( private val application: Application, private val wcRequestDeserializer: WcJrpcRequestsDeserializer, private val analyticsHandler: AnalyticsEventHandler, @@ -95,11 +94,11 @@ class WalletConnectRepositoryImpl @Inject constructor( ) { // Triggered when wallet receives the session proposal sent by a Dapp Timber.d("sessionProposal: $sessionProposal") - this@WalletConnectRepositoryImpl.sessionProposal = sessionProposal + this@DefaultWalletConnectRepository.sessionProposal = sessionProposal val missingNetworks = findMissingNetworks( namespaces = sessionProposal.requiredNamespaces, - userNamespaces = this@WalletConnectRepositoryImpl.userNamespaces ?: emptyMap(), + userNamespaces = this@DefaultWalletConnectRepository.userNamespaces ?: emptyMap(), ) if (missingNetworks.isNotEmpty()) { @@ -116,7 +115,7 @@ class WalletConnectRepositoryImpl @Inject constructor( val optionalWithoutMissingNetworks = removeMissingNetworks( namespaces = sessionProposal.optionalNamespaces, - userNamespaces = this@WalletConnectRepositoryImpl.userNamespaces ?: emptyMap(), + userNamespaces = this@DefaultWalletConnectRepository.userNamespaces ?: emptyMap(), ) scope.launch { @@ -334,6 +333,7 @@ class WalletConnectRepositoryImpl @Inject constructor( ), ) } + Web3Wallet.respondSessionRequest( params = Wallet.Params.SessionRequestResponse( sessionTopic = requestData.topic, @@ -342,15 +342,19 @@ class WalletConnectRepositoryImpl @Inject constructor( result = result, ), ), - onSuccess = {}, - onError = { + onSuccess = { response -> + Timber.d("Session request responded successfully: $response") + }, + onError = { error -> + Timber.d(error.throwable, "Error while responging session request") + WalletConnect.RequestHandledParams( dAppName = session?.name ?: "", dAppUrl = session?.url ?: "", methodName = requestData.method, blockchain = requestData.blockchain, errorCode = WalletConnectError.ValidationError.error, - errorDescription = it.throwable.message, + errorDescription = error.throwable.message, ) }, ) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectSessionsRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectSessionsRepository.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectSessionsRepositoryImpl.kt rename to app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectSessionsRepository.kt index 99f4351a93..77b531934a 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectSessionsRepositoryImpl.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectSessionsRepository.kt @@ -3,15 +3,13 @@ package com.tangem.tap.domain.walletconnect2.data import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.Types -import com.tangem.datasource.di.SdkMoshi import com.tangem.datasource.files.FileReader import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository import com.tangem.tap.domain.walletconnect2.domain.models.Session import timber.log.Timber -import javax.inject.Inject -class WalletConnectSessionsRepositoryImpl @Inject constructor( - @SdkMoshi private val moshi: Moshi, +internal class DefaultWalletConnectSessionsRepository( + private val moshi: Moshi, private val fileReader: FileReader, ) : WalletConnectSessionsRepository { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt index 8cf7905260..927eab61da 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -1,15 +1,22 @@ package com.tangem.tap.domain.walletconnect2.di +import android.app.Application +import com.squareup.moshi.Moshi +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager +import com.tangem.datasource.di.SdkMoshi +import com.tangem.datasource.files.FileReader import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl -import com.tangem.tap.domain.walletconnect2.data.WalletConnectRepositoryImpl -import com.tangem.tap.domain.walletconnect2.data.WalletConnectSessionsRepositoryImpl +import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectRepository +import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectSessionsRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository +import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer +import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -20,19 +27,21 @@ import javax.inject.Singleton @Module @InstallIn(ActivityComponent::class) -class WalletConnectInteractorModule { +internal object WalletConnectInteractorModule { + @Provides @ActivityScoped fun provideWalletConnectInteractor( wcRepository: WalletConnectRepository, wcSessionsRepository: WalletConnectSessionsRepository, + walletConnectFeatureToggles: WalletConnectFeatureToggles, ): WalletConnectInteractor { return WalletConnectInteractor( handler = WalletConnectEventsHandlerImpl(), walletConnectRepository = wcRepository, sessionsRepository = wcSessionsRepository, sdkHelper = WalletConnectSdkHelper(), - blockchainHelper = TangemWcBlockchainHelper(), + blockchainHelper = TangemWcBlockchainHelper(walletConnectFeatureToggles), dispatcher = AppCoroutineDispatcherProvider(), ) } @@ -40,15 +49,37 @@ class WalletConnectInteractorModule { @Module @InstallIn(SingletonComponent::class) -interface WalletConnectModule { +internal object WalletConnectModule { - @Binds + @Provides @Singleton - fun bindWalletConnectRepository(repository: WalletConnectRepositoryImpl): WalletConnectRepository + fun provideWalletConnectFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletConnectFeatureToggles { + return WalletConnectFeatureToggles(featureTogglesManager) + } - @Binds + @Provides @Singleton - fun bindWalletConnectSessionsRepository( - repository: WalletConnectSessionsRepositoryImpl, - ): WalletConnectSessionsRepository + fun provideWalletConnectRepository( + application: Application, + wcRequestDeserializer: WcJrpcRequestsDeserializer, + analyticsHandler: AnalyticsEventHandler, + ): WalletConnectRepository { + return DefaultWalletConnectRepository( + application = application, + wcRequestDeserializer = wcRequestDeserializer, + analyticsHandler = analyticsHandler, + ) + } + + @Provides + @Singleton + fun provideWalletConnectSessionsRepository( + @SdkMoshi moshi: Moshi, + fileReader: FileReader, + ): WalletConnectSessionsRepository { + return DefaultWalletConnectSessionsRepository( + moshi = moshi, + fileReader = fileReader, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index 884d3ac5d7..3bf99ffd83 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -209,7 +209,7 @@ class WalletConnectInteractor( val currentRequest = this.currentRequest if (currentRequest == null || request.topic != currentRequest.topic) return - val networkId = blockchainHelper.chainIdToNetworkIdOrNull(currentRequest.chainId ?: "") ?: return + val networkId = blockchainHelper.chainIdToNetworkIdOrNull(currentRequest.chainId.orEmpty()) ?: return val signedHash = when (request) { is WcPreparedRequest.BnbTransaction -> sdkHelper.signBnbTransaction( data = request.preparedRequestData.data.data, @@ -224,13 +224,19 @@ class WalletConnectInteractor( is WcPreparedRequest.EthSign -> sdkHelper.signPersonalMessage( hashToSign = request.preparedRequestData.hash, networkId = networkId, + type = request.preparedRequestData.type, + derivationPath = request.derivationPath, + cardId = cardId, + ) + is WcPreparedRequest.SignTransaction -> sdkHelper.signTransaction( + hashToSign = request.preparedRequestData.hashToSign, + networkId = networkId, + type = request.preparedRequestData.type, derivationPath = request.derivationPath, cardId = cardId, ) } - Timber.d("Signed hash: $signedHash") - val requestData = RequestData( topic = request.topic, requestId = request.requestId, diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt index 9a0da6c971..3cb1187509 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcJrpcMethods.kt @@ -2,15 +2,17 @@ package com.tangem.tap.domain.walletconnect2.domain import com.squareup.moshi.* import com.tangem.datasource.di.SdkMoshi -import com.tangem.tap.domain.walletconnect2.domain.models.binance.WCBinanceTxConfirmParam import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceCancelOrder import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder +import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTxConfirmParam +import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaSignMessage +import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaTransactionRequest import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -enum class WcJrpcMethods(val code: String) { +internal enum class WcJrpcMethods(val code: String) { ETH_SIGN("eth_sign"), ETH_PERSONAL_SIGN("personal_sign"), @@ -18,10 +20,14 @@ enum class WcJrpcMethods(val code: String) { ETH_SIGN_TYPE_DATA_V4("eth_signTypedData_v4"), ETH_SIGN_TRANSACTION("eth_signTransaction"), ETH_SEND_TRANSACTION("eth_sendTransaction"), + BNB_SIGN("bnb_sign"), BNB_TRANSACTION_CONFIRM("bnb_tx_confirmation"), SIGN_TRANSACTION("trust_signTransaction"), WALLET_ADD_ETHEREUM_CHAIN("wallet_addEthereumChain"), + + SOLANA_SIGN_TX("solana_signTransaction"), + SOLANA_SIGN_MESSAGE("solana_signMessage"), ; companion object { @@ -30,7 +36,7 @@ enum class WcJrpcMethods(val code: String) { } @JsonClass(generateAdapter = true) -data class WCSignTransaction( +data class WcSignTransaction( @Json(name = "network") val network: Int, @@ -39,13 +45,7 @@ data class WCSignTransaction( ) : WcRequestData @JsonClass(generateAdapter = true) -data class WcAddChain( - @Json(name = "chainId") - val chainId: String, -) : WcRequestData - -@JsonClass(generateAdapter = true) -data class WcEthereumSignMessage( +data class WcSignMessage( @Json(name = "raw") val raw: List, @@ -53,7 +53,7 @@ data class WcEthereumSignMessage( val type: WCSignType, ) : WcRequestData { enum class WCSignType { - MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE + MESSAGE, PERSONAL_MESSAGE, TYPED_MESSAGE, SOLANA_MESSAGE, } /** @@ -63,6 +63,7 @@ data class WcEthereumSignMessage( * - MESSAGE: `[address, data ]` * - TYPED_MESSAGE: `[address, data]` * - PERSONAL_MESSAGE: `[data, address]` + * - SOLANA_MESSAGE: `[publicKey (address), message]` * * reference: https://docs.walletconnect.org/json-rpc/ethereum#eth_signtypeddata */ @@ -79,6 +80,21 @@ data class WcEthereumSignMessage( } } +@JsonClass(generateAdapter = true) +data class WcSignMessageData( + @Json(name = "address") + val address: String, + + @Json(name = "message") + val message: String, +) : WcRequestData + +@JsonClass(generateAdapter = true) +data class WcAddChain( + @Json(name = "chainId") + val chainId: String, +) : WcRequestData + @JsonClass(generateAdapter = true) data class WcEthereumTransaction( @Json(name = "from") @@ -117,20 +133,21 @@ interface WcRequestData data class WcCustomRequestData(val data: String) : WcRequestData sealed class WcRequest(open val data: WcRequestData) { - data class EthSign(override val data: WcEthereumSignMessage) : WcRequest(data) + data class EthSign(override val data: WcSignMessage) : WcRequest(data) data class EthSignTransaction(override val data: WcEthereumTransaction) : WcRequest(data) data class EthSendTransaction(override val data: WcEthereumTransaction) : WcRequest(data) data class BnbTrade(override val data: WcBinanceTradeOrder) : WcRequest(data) data class BnbCancel(override val data: WcBinanceCancelOrder) : WcRequest(data) data class BnbTransfer(override val data: WcBinanceTransferOrder) : WcRequest(data) - data class BnbTxConfirm(override val data: WCBinanceTxConfirmParam) : WcRequest(data) - data class SignTransaction(override val data: WCSignTransaction) : WcRequest(data) + data class BnbTxConfirm(override val data: WcBinanceTxConfirmParam) : WcRequest(data) + data class SignTransaction(override val data: WcSignTransaction) : WcRequest(data) data class AddChain(override val data: WcAddChain) : WcRequest(data) data class CustomRequest(override val data: WcCustomRequestData) : WcRequest(data) + data class SolanaSignRequest(override val data: SolanaTransactionRequest) : WcRequest(data) } @Singleton -class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi: Moshi) { +internal class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi: Moshi) { @Suppress("ComplexMethod", "LongMethod") fun deserialize(method: String, params: String): WcRequest { @@ -154,9 +171,9 @@ class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi val deserializedParams = moshi.adapter>( Types.newParameterizedType(List::class.java, String::class.java), ).fromJsonOrNull(params) ?: return customRequest - val data = WcEthereumSignMessage( + val data = WcSignMessage( raw = deserializedParams, - type = WcEthereumSignMessage.WCSignType.MESSAGE, + type = WcSignMessage.WCSignType.MESSAGE, ) WcRequest.EthSign(data = data) } @@ -164,9 +181,9 @@ class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi val deserializedParams = moshi.adapter>( Types.newParameterizedType(List::class.java, String::class.java), ).fromJsonOrNull(params) ?: return customRequest - val data = WcEthereumSignMessage( + val data = WcSignMessage( raw = deserializedParams, - type = WcEthereumSignMessage.WCSignType.PERSONAL_MESSAGE, + type = WcSignMessage.WCSignType.PERSONAL_MESSAGE, ) WcRequest.EthSign(data = data) } @@ -175,9 +192,9 @@ class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2)), params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1), ) - val data = WcEthereumSignMessage( + val data = WcSignMessage( deserializedParams, - WcEthereumSignMessage.WCSignType.TYPED_MESSAGE, + WcSignMessage.WCSignType.TYPED_MESSAGE, ) Timber.d("TypedData params: $deserializedParams") WcRequest.EthSign(data) @@ -186,23 +203,43 @@ class WcJrpcRequestsDeserializer @Inject constructor(@SdkMoshi private val moshi return deserializeBnb(moshi, params) ?: customRequest } WcJrpcMethods.BNB_TRANSACTION_CONFIRM -> { - val deserializedParams = moshi.adapter>( - Types.newParameterizedType(List::class.java, WCBinanceTxConfirmParam::class.java), + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcBinanceTxConfirmParam::class.java), ).fromJsonFirstOrNull(params) ?: return customRequest WcRequest.BnbTxConfirm(data = deserializedParams) } WcJrpcMethods.SIGN_TRANSACTION -> { - val deserializedParams = moshi.adapter>( - Types.newParameterizedType(List::class.java, WCSignTransaction::class.java), + val deserializedParams = moshi.adapter>( + Types.newParameterizedType(List::class.java, WcSignTransaction::class.java), ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.SignTransaction(data = deserializedParams) } WcJrpcMethods.WALLET_ADD_ETHEREUM_CHAIN -> { val deserializedParams: WcAddChain = moshi.adapter>( Types.newParameterizedType(List::class.java, WcAddChain::class.java), ).fromJsonFirstOrNull(params) ?: return customRequest + WcRequest.AddChain(data = deserializedParams) } + WcJrpcMethods.SOLANA_SIGN_TX -> { + val tx = moshi.adapter(SolanaTransactionRequest::class.java) + .fromJsonOrNull(params) + ?: return customRequest + + WcRequest.SolanaSignRequest(data = tx) + } + WcJrpcMethods.SOLANA_SIGN_MESSAGE -> { + val signMessage = moshi.adapter(SolanaSignMessage::class.java) + .fromJsonOrNull(params) + ?: return customRequest + val data = WcSignMessage( + raw = listOf(signMessage.publicKey, signMessage.message), + type = WcSignMessage.WCSignType.SOLANA_MESSAGE, + ) + + WcRequest.EthSign(data = data) + } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt index be17cc867b..64798e2d9c 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcPreparedRequest.kt @@ -30,4 +30,21 @@ sealed class WcPreparedRequest( requestId: Long, derivationPath: String?, ) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath) + + class SignTransaction( + override val preparedRequestData: WcGenericTransactionData, + topic: String, + requestId: Long, + derivationPath: String?, + ) : WcPreparedRequest(preparedRequestData, topic, requestId, derivationPath) +} + +data class WcGenericTransactionData( + val hashToSign: ByteArray, + val dAppName: String, + val type: TransactionType, +) + +enum class TransactionType { + SOLANA_TX, } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt index fe67571134..12085cad12 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt @@ -1,12 +1,13 @@ package com.tangem.tap.domain.walletconnect2.domain import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper +import com.tangem.tap.domain.walletconnect2.domain.mapper.mapToTransaction import com.tangem.tap.domain.walletconnect2.domain.models.BnbData import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType -class WcSessionRequestConverter( +internal class WcSessionRequestConverter( private val blockchainHelper: WcBlockchainHelper, private val sessionsRepository: WalletConnectSessionsRepository, private val sdkHelper: WalletConnectSdkHelper, @@ -107,6 +108,20 @@ class WcSessionRequestConverter( derivationPath = derivationPath, ) } + is WcRequest.SolanaSignRequest -> { + val data = request.data.mapToTransaction() + + WcPreparedRequest.SignTransaction( + preparedRequestData = WcGenericTransactionData( + hashToSign = data.getSerializedMessage(), + dAppName = sessionRequest.metaName, + type = TransactionType.SOLANA_TX, + ), + topic = sessionRequest.topic, + requestId = sessionRequest.id, + derivationPath = derivationPath, + ) + } else -> null } } @@ -118,6 +133,7 @@ class WcSessionRequestConverter( is WcRequest.EthSendTransaction -> request.data.from is WcRequest.EthSignTransaction -> request.data.from is WcRequest.EthSign -> request.data.address + is WcRequest.SolanaSignRequest -> request.data.feePayer else -> null } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/mapper/SolanaTransactionMappings.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/mapper/SolanaTransactionMappings.kt new file mode 100644 index 0000000000..c97506bb7e --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/mapper/SolanaTransactionMappings.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.domain.walletconnect2.domain.mapper + +import com.tangem.blockchain.blockchains.solana.solanaj.core.SolanaTransaction +import com.tangem.tap.domain.walletconnect2.domain.models.solana.SolanaTransactionRequest +import org.p2p.solanaj.core.AccountMeta +import org.p2p.solanaj.core.PublicKey +import org.p2p.solanaj.core.TransactionInstruction + +internal fun SolanaTransactionRequest.mapToTransaction(): SolanaTransaction { + val from = PublicKey(feePayer) + val instructions = instructions.map(SolanaTransactionRequest.Instruction::mapToInstruction) + + return SolanaTransaction(from) + .apply { + instructions.forEach(::addInstruction) + setRecentBlockHash(recentBlockhash) + } +} + +private fun SolanaTransactionRequest.Instruction.mapToInstruction() = TransactionInstruction( + /* programId = */ PublicKey(programId), + /* keys = */ keys.map(SolanaTransactionRequest.Key::mapToAccountMeta), + /* data = */ data.toByteArray(), +) + +private fun SolanaTransactionRequest.Key.mapToAccountMeta() = AccountMeta( + /* publicKey = */ PublicKey(publicKey), + /* isSigner = */ isSigner, + /* isWritable = */ isWritable, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt index 8325aadd72..1e3f33db29 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/binance/WcBinanceOrder.kt @@ -13,7 +13,7 @@ open class WcBinanceOrder( val msgs: List, ) : WcRequestData -data class WCBinanceTxConfirmParam( +data class WcBinanceTxConfirmParam( val ok: Boolean, val errorMsg: String?, ) : WcRequestData \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaSignMessage.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaSignMessage.kt new file mode 100644 index 0000000000..a521aa55e9 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaSignMessage.kt @@ -0,0 +1,13 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.solana + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SolanaSignMessage( + @Json(name = "pubkey") + val publicKey: String, + + @Json(name = "message") + val message: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt new file mode 100644 index 0000000000..7ea66042f4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/solana/SolanaTransactionRequest.kt @@ -0,0 +1,42 @@ +package com.tangem.tap.domain.walletconnect2.domain.models.solana + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.tangem.tap.domain.walletconnect2.domain.WcRequestData + +@JsonClass(generateAdapter = true) +data class SolanaTransactionRequest( + @Json(name = "feePayer") + val feePayer: String, + + @Json(name = "recentBlockhash") + val recentBlockhash: String, + + @Json(name = "instructions") + val instructions: List, +) : WcRequestData { + + @JsonClass(generateAdapter = true) + data class Instruction( + @Json(name = "programId") + val programId: String, + + @Json(name = "data") + val data: List, + + @Json(name = "keys") + val keys: List, + ) + + @JsonClass(generateAdapter = true) + data class Key( + @Json(name = "isSigner") + val isSigner: Boolean, + + @Json(name = "isWritable") + val isWritable: Boolean, + + @Json(name = "pubkey") + val publicKey: String, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/WalletConnectFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/WalletConnectFeatureToggles.kt new file mode 100644 index 0000000000..26547d68e8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/WalletConnectFeatureToggles.kt @@ -0,0 +1,11 @@ +package com.tangem.tap.domain.walletconnect2.toggles + +import com.tangem.core.featuretoggle.manager.FeatureTogglesManager + +internal class WalletConnectFeatureToggles( + private val featureTogglesManager: FeatureTogglesManager, +) { + + val isSolanaTxSignEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled(name = "WC_SOLANA_TX_SIGN_ENABLED") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index c45ec9dae3..47c26d8452 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -375,6 +375,7 @@ class WalletConnectMiddleware { is WcPreparedRequest.BnbTransaction -> WalletConnectDialog.BnbTransactionDialog(request) is WcPreparedRequest.EthTransaction -> WalletConnectDialog.RequestTransaction(request) is WcPreparedRequest.EthSign -> WalletConnectDialog.PersonalSign(request) + is WcPreparedRequest.SignTransaction -> WalletConnectDialog.SignTransactionDialog(request) } store.dispatch(GlobalAction.ShowDialog(dialog)) } diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 34eed45fd3..c696a29485 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -8,8 +8,8 @@ import com.tangem.blockchain.common.derivation.DerivationStyle import com.tangem.core.navigation.StateDialog import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.domain.walletconnect2.domain.WcEthereumSignMessage import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData @@ -117,6 +117,10 @@ sealed class WalletConnectDialog : StateDialog { data class BnbTransactionDialog( val data: WcPreparedRequest.BnbTransaction, ) : WalletConnectDialog() + + data class SignTransactionDialog( + val data: WcPreparedRequest.SignTransaction, + ) : WalletConnectDialog() } data class WcTransactionData( @@ -138,7 +142,7 @@ data class WcPersonalSignData( val topic: String, val id: Long, val dialogData: PersonalSignDialogData, - val type: WcEthereumSignMessage.WCSignType, + val type: WcSignMessage.WCSignType, ) sealed class BinanceMessageData( diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SignTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SignTransactionDialog.kt new file mode 100644 index 0000000000..a21b7a4d75 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/SignTransactionDialog.kt @@ -0,0 +1,29 @@ +package com.tangem.tap.features.details.ui.walletconnect.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.store +import com.tangem.wallet.R + +internal object SignTransactionDialog { + + fun create(preparedData: WcPreparedRequest.SignTransaction, context: Context): AlertDialog { + val message = context.getString(R.string.wallet_connect_alert_sign_message, "") + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect_title)) + setMessage(message) + setPositiveButton(context.getText(R.string.common_sign)) { _, _ -> + store.dispatch(WalletConnectAction.PerformRequestedAction(preparedData)) + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(preparedData.topic, preparedData.requestId)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() + } +} \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 8f979c41f0..c3c879c727 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -22,5 +22,9 @@ { "name": "GENERATE_XPUB_ENABLED", "version": "undefined" + }, + { + "name": "WC_SOLANA_TX_SIGN_ENABLED", + "version": "5.9.0" } ]