diff --git a/app/build.gradle b/app/build.gradle index ff53e7b789..b35d59c395 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,7 +77,7 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-52' + implementation 'com.tangem:blockchain:develop-53' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-115' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-115' 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 8f1491368c..7f3bf094a7 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -79,6 +79,15 @@ class DialogManager : StoreSubscriber { TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) + is WalletConnectDialog.BnbTransactionDialog -> + BnbTransactionDialog.create( + data = state.dialog.data, + session = state.dialog.session, + sessionId = state.dialog.sessionId, + cardId = state.dialog.cardId, + dAppName = state.dialog.dAppName, + context = context + ) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) 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 new file mode 100644 index 0000000000..a8f3eb83c1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -0,0 +1,79 @@ +package com.tangem.tap.domain.walletconnect + +import com.github.salomonbrys.kotson.registerTypeAdapter +import com.google.gson.GsonBuilder +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.calculateSha256 +import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData +import com.tangem.tap.features.details.redux.walletconnect.TradeData +import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder +import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer +import timber.log.Timber + +class BnbHelper { + companion object { + + fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { + val input = order.msgs.first().inputs.first() + val output = order.msgs.first().inputs.first() + + val currency = + input.coins.firstOrNull()?.denom ?: Blockchain.Binance + val amount = input.coins + .mapNotNull { if (it.denom == currency) it.amount else null } + .sum() + .toBigDecimal() + .movePointRight(Blockchain.Binance.decimals()) + .toString() + + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + + return BinanceMessageData.Transfer( + outputAddress = output.address, + amount = amount, + address = input.address, + data = gson.toJson(order).toByteArray().calculateSha256() + ) + } + + fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { + val address = order.msgs.first().sender + + val tradeData = order.msgs.map { + val price = it.price.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + val quantity = it.quantity.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + + val amount = price * quantity + val symbol = it.symbol.substringBefore("-") + + TradeData( + price = "$price ${Blockchain.Binance.currency}", + quantity = "$quantity $symbol", + amount = "$amount ${Blockchain.Binance.currency}", + symbol = symbol + ) + } + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + val serialized = gson.toJson(order) + Timber.d(serialized) + + return BinanceMessageData.Trade( + tradeData = tradeData, + address = address, + data = serialized.toByteArray().calculateSha256() + ) + } + + } +} 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 7c74264429..c5e727e23b 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 @@ -11,6 +11,10 @@ import com.tangem.tap.store import com.tangem.tap.walletConnectRepository import com.trustwallet.walletconnect.WCClient import com.trustwallet.walletconnect.models.WCPeerMeta +import com.trustwallet.walletconnect.models.binance.WCBinanceCancelOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTxConfirmParam import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction import com.trustwallet.walletconnect.models.session.WCSession @@ -60,6 +64,15 @@ class WalletConnectManager { setupConnectionTimeoutCheck(session) } + fun updateSession(session: WalletConnectSession) { + val updatedSession = sessions[session.session]?.copy( + wallet = session.wallet + ) + if (updatedSession != null) { + sessions[session.session] = updatedSession + } + } + private fun setupConnectionTimeoutCheck(session: WCSession) { scope.launch { delay(20_000) @@ -81,7 +94,7 @@ class WalletConnectManager { client = WCClient(httpClient = okHttpClient), session = session.session, peerMeta = session.peerMeta, - wallet = session.wallet + wallet = session.wallet, ) .also { setListeners(it.client) @@ -96,11 +109,12 @@ class WalletConnectManager { val activeData = sessions[session] ?: return removeSimilarSessions(activeData) - val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey - val accounts = listOf(Blockchain.Ethereum.makeAddresses(key).first().value) + val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return + val blockchain = activeData.wallet.getBlockchainForSession() + val accounts = listOf(blockchain.makeAddresses(key).first().value) val approved = activeData.client.approveSession( accounts = accounts, - chainId = activeData.wallet.chainId + chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!! ) if (approved) { val walletConnectSession = WalletConnectSession( @@ -111,15 +125,18 @@ class WalletConnectManager { peerMeta = activeData.peerMeta!! ) walletConnectRepository.saveSession(walletConnectSession) - store.dispatchOnMain(WalletConnectAction.ApproveSession.Success( - walletConnectSession)) + store.dispatchOnMain( + WalletConnectAction.ApproveSession.Success( + walletConnectSession + ) + ) } } fun removeSimilarSessions(activeData: WalletConnectActiveData) { val sessionsToRemove = sessions.filter { - it.value.wallet.walletPublicKey == activeData.wallet.walletPublicKey + it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true && it.value.peerMeta?.url == activeData.peerMeta?.url && it.value.session != activeData.session } @@ -148,7 +165,8 @@ class WalletConnectManager { private fun onSessionClosed(session: WCSession) { FirebaseAnalyticsHandler.logWcEvent( FirebaseAnalyticsHandler.WcAnalyticsEvent.Session( - FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url) + FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url + ) ) sessions.remove(session) walletConnectRepository.removeSession(session) @@ -170,17 +188,23 @@ class WalletConnectManager { type = type ).guard { sessions[session.session] = activeData.copy(transactionData = null) - store.dispatchOnMain(WalletConnectAction.RejectRequest( - session.session, - id - )) + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + session.session, + id + ) + ) return@launch } sessions[session.session] = activeData.copy(transactionData = data) - store.dispatchOnMain(WalletConnectAction.SetDataToSend(data)) - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.RequestTransaction( - data.dialogData))) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.RequestTransaction( + data.dialogData + ) + ) + ) } } @@ -190,13 +214,34 @@ class WalletConnectManager { scope.launch { val hash = WalletConnectSdkHelper().completeTransaction(data).guard { sessions[data.session.session] = activeData.copy(transactionData = null) - store.dispatchOnMain(WalletConnectAction.RejectRequest(data.session.session, - data.id - )) + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + data.session.session, + data.id + ) + ) return@launch } - acceptRequest(data.session.session, data.id, hash) - sessions[data.session.session] = activeData.copy(transactionData = null) + acceptRequest(session, data.id, hash) + sessions[session] = activeData.copy(transactionData = null) + } + } + + fun signBnb( + id: Long, data: ByteArray, sessionData: WCSession, + ) { + val activeData = sessions[sessionData] ?: return + scope.launch { + val hash = WalletConnectSdkHelper().signBnbTransaction(data, activeData).guard { + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + sessionData, + id + ) + ) + return@launch + } + acceptRequest(sessionData, id, hash) } } @@ -211,9 +256,13 @@ class WalletConnectManager { message = message, session = session, id = id ) sessions[session.session] = activeData.copy(personalSignData = data) - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.PersonalSign( - data.dialogData - ))) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.PersonalSign( + data.dialogData + ) + ) + ) } } @@ -224,9 +273,12 @@ class WalletConnectManager { val hash = WalletConnectSdkHelper().signPersonalMessage(data.hash, activeData.wallet) .guard { sessions[data.session.session] = activeData.copy(transactionData = null) - store.dispatchOnMain(WalletConnectAction.RejectRequest(data.session.session, - data.id - )) + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + data.session.session, + data.id + ) + ) return@launch } sessions[session] = activeData.copy(personalSignData = data) @@ -245,12 +297,17 @@ class WalletConnectManager { sessions[session] = data val sessionData = data.toWalletConnectSession() sessionData?.let { - store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession( - sessionData)) + store.dispatchOnMain( + WalletConnectAction.AcceptOpeningSession( + session = sessionData, + chainId = client.chainId?.toIntOrNull() + ) + ) } FirebaseAnalyticsHandler.logWcEvent( FirebaseAnalyticsHandler.WcAnalyticsEvent.Session( - FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url) + FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url + ) ) } } @@ -267,12 +324,14 @@ class WalletConnectManager { ) ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest( - transaction = transaction, - session = sessionData, - id = id, - type = WcTransactionType.EthSendTransaction - )) + store.dispatchOnMain( + WalletConnectAction.HandleTransactionRequest( + transaction = transaction, + session = sessionData, + id = id, + type = WcTransactionType.EthSendTransaction + ) + ) } } client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction -> @@ -283,12 +342,14 @@ class WalletConnectManager { ) ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest( - transaction = transaction, - session = sessionData, - id = id, - type = WcTransactionType.EthSignTransaction - )) + store.dispatchOnMain( + WalletConnectAction.HandleTransactionRequest( + transaction = transaction, + session = sessionData, + id = id, + type = WcTransactionType.EthSignTransaction + ) + ) } } client.onEthSign = { id: Long, message: WCEthereumSignMessage -> @@ -299,12 +360,42 @@ class WalletConnectManager { ) ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain(WalletConnectAction.HandlePersonalSignRequest( - message, - sessionData, - id)) + store.dispatchOnMain( + WalletConnectAction.HandlePersonalSignRequest( + message, + sessionData, + id + ) + ) } } + + + client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder -> + + } + client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder -> + sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + store.dispatchOnMain( + WalletConnectAction.BinanceTransaction.Trade( + id = id, order = order, sessionData = sessionData + ) + ) + } + } + client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder -> + sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + store.dispatchOnMain( + WalletConnectAction.BinanceTransaction.Transfer( + id = id, order = order, sessionData = sessionData + ) + ) + } + } + client.onBnbTxConfirm = { id: Long, order: WCBinanceTxConfirmParam -> + // send empty approve request if status is OK + if (order.ok) client.approveRequest(id, "") + } client.onDisconnect = { code: Int, reason: String -> val session = client.session if (session != null) { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt new file mode 100644 index 0000000000..4907e8d40c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.domain.walletconnect + +import com.tangem.blockchain.common.Blockchain +import com.trustwallet.walletconnect.models.WCPeerMeta + +class WalletConnectNetworkUtils { + + companion object { + + fun parseBlockchain( + chainId: Int?, + peer: WCPeerMeta, + isTestNet: Boolean? = null + ): Blockchain? { + return when { + chainId != null -> { + Blockchain.fromChainId(chainId) + } + peer.url.contains("matic.network") || peer.name == "Polygon" -> { + Blockchain.Polygon + } + peer.url.contains("binance.org") || peer.name.contains("Binance") -> { + if (peer.icons.firstOrNull()?.contains("testnet") == true) { + Blockchain.BinanceTestnet + } else { + Blockchain.Binance + } + } + peer.name.contains("BSC") -> { + Blockchain.BSC + } + else -> { + if (isTestNet == true) Blockchain.EthereumTestnet else Blockchain.Ethereum + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt index a4654d6429..67dab3645d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt @@ -88,7 +88,6 @@ data class SessionDao( session = session.session, peerMeta = session.peerMeta ) - } } } \ No newline at end of file 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 85ef0eaac6..74f1abc2b0 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 @@ -10,6 +10,7 @@ import com.tangem.blockchain.extensions.* import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler @@ -33,20 +34,8 @@ class WalletConnectSdkHelper { id: Long, type: WcTransactionType, ): WcTransactionData? { - val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val publicKey = Wallet.PublicKey( - session.wallet.walletPublicKey, - session.wallet.derivedPublicKey, - session.wallet.derivationPath, - ) - val walletManager = factory.makeEthereumWalletManager( - session.wallet.cardId, - publicKey, - emptyList(), - isTestNet = session.wallet.isTestNet - ) ?: return null - + val walletManager = getWalletManager(session) ?: return null try { walletManager.update() } catch (exception: Exception) { @@ -54,15 +43,14 @@ class WalletConnectSdkHelper { return null } - val blockchain = walletManager.wallet.blockchain - + val wallet = walletManager.wallet val balance = - walletManager.wallet.amounts[AmountType.Coin]?.value ?: return null + wallet.amounts[AmountType.Coin]?.value ?: return null val gas = transaction.gas?.hexToBigDecimal() ?: transaction.gasLimit?.hexToBigDecimal() ?: return null - val decimals = blockchain.decimals() + val decimals = wallet.blockchain.decimals() val value = (transaction.value ?: "0").hexToBigDecimal() ?.movePointLeft(decimals) ?: return null @@ -82,8 +70,8 @@ class WalletConnectSdkHelper { val total = value + fee val transactionData = TransactionData( - amount = Amount(value, blockchain), - fee = Amount(fee, blockchain), + amount = Amount(value, wallet.blockchain), + fee = Amount(fee, wallet.blockchain), sourceAddress = transaction.from, destinationAddress = transaction.to!!, extras = EthereumTransactionExtras( @@ -115,6 +103,21 @@ class WalletConnectSdkHelper { ) } + private fun getWalletManager(session: WalletConnectSession): WalletManager? { + val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val publicKey = Wallet.PublicKey( + session.wallet.walletPublicKey ?: return null, + session.wallet.derivedPublicKey, + session.wallet.derivationPath, + ) + val blockchain = session.wallet.getBlockchainForSession() + return factory.makeWalletManager( + session.wallet.cardId, + blockchain, + publicKey + ) + } + suspend fun completeTransaction(data: WcTransactionData): String? { return when (data.type) { WcTransactionType.EthSendTransaction -> sendTransaction(data) @@ -175,6 +178,35 @@ class WalletConnectSdkHelper { } } + suspend fun signBnbTransaction(data: ByteArray, session: WalletConnectActiveData): String? { + val command = SignHashCommand( + hash = data, + walletPublicKey = session.wallet.walletPublicKey ?: return null, + derivationPath = session.wallet.derivationPath + ) + val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message()) + return when (result) { + is CompletionResult.Success -> { + val key = session.wallet.derivedPublicKey + ?: session.wallet.walletPublicKey + getBnbResultString( + key.toHexString(), + result.data.signature.toHexString() + ) + } + is CompletionResult.Failure -> { + (result.error as? TangemSdkError)?.let { error -> + FirebaseAnalyticsHandler.logCardSdkError( + error, + FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction, + ) + } + Timber.e(result.error.customMessage) + null + } + } + } + fun prepareDataForPersonalSign( message: WCEthereumSignMessage, session: WalletConnectSession, @@ -213,12 +245,12 @@ class WalletConnectSdkHelper { suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? { val key = wallet.derivedPublicKey ?: wallet.walletPublicKey - val command = SignHashCommand(hashToSign, wallet.walletPublicKey, wallet.derivationPath) + val command = SignHashCommand(hashToSign, wallet.walletPublicKey!!, wallet.derivationPath) return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) { is CompletionResult.Success -> { val hash = result.data.signature return EthereumUtils.prepareSignedMessageData( - hash, hashToSign, CryptoUtils.decompressPublicKey(key) + hash, hashToSign, CryptoUtils.decompressPublicKey(key!!) ) } is CompletionResult.Failure -> { @@ -237,5 +269,8 @@ class WalletConnectSdkHelper { companion object { private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n" private const val HEX_PREFIX = "0x" + fun getBnbResultString(publicKey: String, signature: String): String { + return "{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 16a3f638a4..f5f509cc74 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -2,7 +2,10 @@ package com.tangem.tap.features.details.redux.walletconnect import android.app.Activity import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.wallet.R +import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction import com.trustwallet.walletconnect.models.session.WCSession @@ -27,13 +30,16 @@ sealed class WalletConnectAction : Action { data class OpenSession( val wcUri: String, val wallet: WalletForSession, + val scanResponse: ScanResponse ) : WalletConnectAction() object RefuseOpeningSession : WalletConnectAction() data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction() - data class AcceptOpeningSession(val session: WalletConnectSession) : WalletConnectAction() + data class AcceptOpeningSession( + val session: WalletConnectSession, val chainId: Int?, + ) : WalletConnectAction() data class ApproveSession( val session: WCSession, @@ -79,4 +85,20 @@ sealed class WalletConnectAction : Action { object NotifyCameraPermissionIsRequired : WalletConnectAction(), NotificationAction { override val messageResource = R.string.common_camera_denied_alert_message } + + object BinanceTransaction : WalletConnectAction() { + data class Trade( + val id: Long, val order: WCBinanceTradeOrder, + val sessionData: WalletConnectSession, + ) : WalletConnectAction() + + data class Transfer( + val id: Long, val order: WCBinanceTransferOrder, + val sessionData: WalletConnectSession, + ) : WalletConnectAction() + + data class Sign( + val id: Long, val data: ByteArray, val sessionData: WCSession, + ) : WalletConnectAction() + } } \ 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 2c1aab1596..92c6d69072 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 @@ -1,7 +1,6 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.* -import com.tangem.common.extensions.guard import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard @@ -13,10 +12,14 @@ import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.isMultiwalletAllowed import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.domain.walletconnect.BnbHelper import com.tangem.tap.domain.walletconnect.WalletConnectManager +import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.wallet.R +import com.trustwallet.walletconnect.models.WCPeerMeta import org.rekotlin.Middleware +import timber.log.Timber class WalletConnectMiddleware { private val walletConnectManager = WalletConnectManager() @@ -48,8 +51,13 @@ class WalletConnectMiddleware { } is WalletConnectAction.ShowClipboardOrScanQrDialog -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ClipboardOrScanQr( - action.wcUri))) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.ClipboardOrScanQr( + action.wcUri + ) + ) + ) } is WalletConnectAction.ScanCard -> { @@ -78,14 +86,23 @@ class WalletConnectMiddleware { } is WalletConnectAction.RefuseOpeningSession -> { - store.dispatch(GlobalAction.ShowDialog( - WalletConnectDialog.OpeningSessionRejected - )) + store.dispatch( + GlobalAction.ShowDialog( + WalletConnectDialog.OpeningSessionRejected + ) + ) } is WalletConnectAction.AcceptOpeningSession -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ApproveWcSession( - action.session))) + generateWalletKeys(action.session, action.chainId) + + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.ApproveWcSession( + action.session + ) + ) + ) } is WalletConnectAction.ApproveSession -> { @@ -120,6 +137,39 @@ class WalletConnectMiddleware { is WalletConnectAction.SignMessage -> { walletConnectManager.sendSignedMessage(action.session) } + is WalletConnectAction.BinanceTransaction.Trade -> { + val messageData = BnbHelper.createMessageData(action.order) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.BnbTransactionDialog( + data = messageData, + session = action.sessionData.session, + sessionId = action.id, + cardId = action.sessionData.wallet.cardId, + dAppName = action.sessionData.peerMeta.name + ) + ) + ) + } + is WalletConnectAction.BinanceTransaction.Transfer -> { + val messageData = BnbHelper.createMessageData(action.order) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.BnbTransactionDialog( + data = messageData, + session = action.sessionData.session, + sessionId = action.id, + cardId = action.sessionData.wallet.cardId, + dAppName = action.sessionData.peerMeta.name + ) + ) + ) + } + is WalletConnectAction.BinanceTransaction.Sign -> { + walletConnectManager.signBnb( + action.id, action.data, action.sessionData + ) + } } next(action) } @@ -134,60 +184,79 @@ class WalletConnectMiddleware { return@ScanCard } - val walletManager = getWalletManager(scanResponse).guard { - store.dispatchOnMain(WalletConnectAction.UnsupportedCard) - return@ScanCard - } - - val wallet = walletManager.wallet - val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { - null - } else { - walletManager.wallet.publicKey.blockchainKey - } - store.dispatchOnMain(WalletConnectAction.OpenSession( - wcUri = wcUri, - wallet = WalletForSession( - card.cardId, - wallet.publicKey.seedKey, - derivedKey, - wallet.publicKey.derivationPath, - card.isTestCard - ), - )) + store.dispatchOnMain( + WalletConnectAction.OpenSession( + wcUri = wcUri, + wallet = WalletForSession( + scanResponse.card.cardId, + null, + null, + null, + scanResponse.card.isTestCard + ), + scanResponse = scanResponse + ) + ) }, { store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null)) }, R.string.wallet_connect_scan_card_message)) } - private fun getWalletManager(scanResponse: ScanResponse): WalletManager? { + private fun generateWalletKeys(session: WalletConnectSession, chainId: Int?) { + val scanResponse = store.state.walletConnectState.scanResponse ?: return + val walletManager = getWalletManager(scanResponse, session.peerMeta, chainId) ?: return + + val wallet = walletManager.wallet + val derivedKey = + if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { + null + } else { + walletManager.wallet.publicKey.blockchainKey + } + val updatedWallet = session.wallet.copy( + walletPublicKey = wallet.publicKey.seedKey, + derivedPublicKey = derivedKey, + derivationPath = wallet.publicKey.derivationPath, + blockchain = wallet.blockchain + ) + val updatedSession = session.copy(wallet = updatedWallet) + walletConnectManager.updateSession(updatedSession) + } + + private fun getWalletManager( + scanResponse: ScanResponse, peer: WCPeerMeta, chainId: Int? + ): WalletManager? { val card = scanResponse.card val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val wcBlockchain = if (scanResponse.card.isTestCard) { - Blockchain.EthereumTestnet - } else { - Blockchain.Ethereum - } + val blockchain = WalletConnectNetworkUtils.parseBlockchain( + chainId = chainId, peer = peer, isTestNet = scanResponse.card.isTestCard + ) ?: if (scanResponse.card.isTestCard) Blockchain.EthereumTestnet else Blockchain.Ethereum + + Timber.d(blockchain.fullName) + return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) { - store.state.walletState.getWalletManager(wcBlockchain) - ?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain) + store.state.walletState.getWalletManager(blockchain) + ?: factory.makeWalletManagerForApp(scanResponse, blockchain) ?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } } else { + val walletManager = factory.makeWalletManagerForApp(scanResponse, blockchain) if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains( - wcBlockchain) == true + blockchain + ) != true ) { - factory.makeWalletManagerForApp(scanResponse, wcBlockchain) - } else { - factory.makeWalletManagerForApp(scanResponse, wcBlockchain) - ?.also { - currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain) - } + walletManager?.let { + currenciesRepository.saveAddedBlockchain( + card.cardId, + blockchain + ) + } } + return walletManager } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index c8b6f3b1b7..c96f72fc9e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -16,6 +16,9 @@ class WalletConnectReducer { sessions = state.sessions + action.session ) } + is WalletConnectAction.OpenSession -> { + state.copy(scanResponse = action.scanResponse) + } is WalletConnectAction.SetSessionsRestored -> WalletConnectState(sessions = action.sessions) 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 2757d8cea9..b00b7d9ff9 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 @@ -1,10 +1,12 @@ package com.tangem.tap.features.details.redux.walletconnect import com.squareup.moshi.JsonClass +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.WalletManager import com.tangem.common.hdWallet.DerivationPath import com.tangem.tap.common.redux.StateDialog +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.trustwallet.walletconnect.models.WCPeerMeta @@ -13,6 +15,7 @@ import com.trustwallet.walletconnect.models.session.WCSession data class WalletConnectState( val loading: Boolean = false, val sessions: List = listOf(), + val scanResponse: ScanResponse? = null ) data class WalletConnectSession( @@ -21,18 +24,58 @@ data class WalletConnectSession( val wallet: WalletForSession, val session: WCSession, val peerMeta: WCPeerMeta, -) +) { + fun getAddress(): String? { + val key = wallet.derivedPublicKey ?: wallet.walletPublicKey ?: return null + return wallet.blockchain?.makeAddresses(key)?.first()?.value + } +} @JsonClass(generateAdapter = true) data class WalletForSession( val cardId: String, - val walletPublicKey: ByteArray, + val walletPublicKey: ByteArray?, val derivedPublicKey: ByteArray?, val derivationPath: DerivationPath?, val isTestNet: Boolean = false, + val blockchain: Blockchain? = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum ) { - val chainId - get() = if (isTestNet) 4 else 1 + + fun getBlockchainForSession(): Blockchain { + return blockchain ?: if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as WalletForSession + + if (cardId != other.cardId) return false + if (walletPublicKey != null) { + if (other.walletPublicKey == null) return false + if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false + } else if (other.walletPublicKey != null) return false + if (derivedPublicKey != null) { + if (other.derivedPublicKey == null) return false + if (!derivedPublicKey.contentEquals(other.derivedPublicKey)) return false + } else if (other.derivedPublicKey != null) return false + if (derivationPath != other.derivationPath) return false + if (isTestNet != other.isTestNet) return false + if (blockchain != other.blockchain) return false + + return true + } + + override fun hashCode(): Int { + var result = cardId.hashCode() + result = 31 * result + (walletPublicKey?.contentHashCode() ?: 0) + result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0) + result = 31 * result + (derivationPath?.hashCode() ?: 0) + result = 31 * result + isTestNet.hashCode() + result = 31 * result + (blockchain?.hashCode() ?: 0) + return result + } } @@ -46,6 +89,12 @@ sealed class WalletConnectDialog : StateDialog { WalletConnectDialog() data class PersonalSign(val data: PersonalSignDialogData) : WalletConnectDialog() + data class BnbTransactionDialog(val data: BinanceMessageData, + val session: WCSession, + val sessionId: Long, + val cardId: String, + val dAppName: String + ) : WalletConnectDialog() } data class WcTransactionData( @@ -59,7 +108,7 @@ data class WcTransactionData( enum class WcTransactionType { EthSignTransaction, - EthSendTransaction, + EthSendTransaction } data class WcPersonalSignData( @@ -68,4 +117,29 @@ data class WcPersonalSignData( val id: Long, val dialogData: PersonalSignDialogData, - ) \ No newline at end of file + ) + +sealed class BinanceMessageData( + val address: String, + val data: ByteArray, +) { + class Trade( + val tradeData: List, + address: String, + data: ByteArray, + ) : BinanceMessageData(address, data) + + class Transfer( + val outputAddress: String, + val amount: String, + address: String, + data: ByteArray, + ) : BinanceMessageData(address, data) +} + +data class TradeData( + val price: String, + val quantity: String, + val amount: String, + val symbol: String +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt new file mode 100644 index 0000000000..2ec39ca844 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/dialogs/BnbTransactionDialog.kt @@ -0,0 +1,66 @@ +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.features.details.redux.walletconnect.BinanceMessageData +import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction +import com.tangem.tap.store +import com.tangem.wallet.R +import com.trustwallet.walletconnect.models.session.WCSession + +class BnbTransactionDialog { + companion object { + fun create( + data: BinanceMessageData, + session: WCSession, + sessionId: Long, + cardId: String, + dAppName: String, + context: Context, + ): AlertDialog { + + val message = when (data) { + is BinanceMessageData.Trade -> data.tradeData.map { + context.getString( + R.string.wallet_connect_bnb_trade_order_message, + it.symbol, + it.price, + it.quantity, + it.amount + ) + }.joinToString ( "\n\n" ) + is BinanceMessageData.Transfer -> context.getString( + R.string.wallet_connect_bnb_transaction_message, + data.address, + data.outputAddress, + data.amount + ) + } + + val fullMessage = context.getString( + R.string.wallet_connect_bnb_sign_message, + dAppName, cardId, message + ) + val positiveButtonTitle = context.getText(R.string.common_sign) + + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect)) + setMessage(fullMessage) + setPositiveButton(positiveButtonTitle) { _, _ -> + store.dispatch(WalletConnectAction.BinanceTransaction.Sign( + id = sessionId, + data = data.data, + sessionData = session + )) + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index ace2084261..b7444dcb16 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -132,7 +132,7 @@ class MultiWalletMiddleware { val walletManager = walletState?.getWalletManager(Blockchain.Ethereum) ?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) - val tokenFinder = walletManager as TokenFinder + val tokenFinder = walletManager as? TokenFinder ?: return scope.launch { val result = tokenFinder.findTokens() withContext(Dispatchers.Main) { diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index faad406cb3..ce001133db 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -294,4 +294,21 @@ Access code is available only for cards with a backup. + + + The BNB transaction has been successfully signed and sent to the Dapp + Dapp %s, requesting to\n +sign BNB transaction with\n +Card: %s\n +\n +%s + Transaction details:\n +From: %s\n +To: %s\n +Amount: %s + Trade order for %s\n +Price: %s\n +Amount to receive: %s\n +Amount to pay: %s +