Updated on 2026-08-14

This commit is contained in:
Tangem 2021-12-29 08:10:27 +03:00
parent 8ac903ba80
commit 198d625848
10 changed files with 432 additions and 61 deletions

View file

@ -79,6 +79,15 @@ class DialogManager : StoreSubscriber<GlobalState> {
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)

View file

@ -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()
)
}
}
}

View file

@ -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
@ -121,8 +125,11 @@ class WalletConnectManager {
peerMeta = activeData.peerMeta!!
)
walletConnectRepository.saveSession(walletConnectSession)
store.dispatchOnMain(WalletConnectAction.ApproveSession.Success(
walletConnectSession))
store.dispatchOnMain(
WalletConnectAction.ApproveSession.Success(
walletConnectSession
)
)
}
}
@ -158,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)
@ -180,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
)
)
)
}
}
@ -200,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)
}
}
@ -221,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
)
)
)
}
}
@ -234,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)
@ -255,14 +297,17 @@ class WalletConnectManager {
sessions[session] = data
val sessionData = data.toWalletConnectSession()
sessionData?.let {
store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession(
session = sessionData,
chainId = client.chainId?.toIntOrNull()
))
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
)
)
}
}
@ -279,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 ->
@ -295,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 ->
@ -311,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) {

View file

@ -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,19 +34,8 @@ class WalletConnectSdkHelper {
id: Long,
type: WcTransactionType,
): WcTransactionData? {
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()
val walletManager = factory.makeWalletManager(
session.wallet.cardId,
blockchain,
publicKey
) ?: return null
val walletManager = getWalletManager(session) ?: return null
try {
walletManager.update()
} catch (exception: Exception) {
@ -53,13 +43,14 @@ class WalletConnectSdkHelper {
return null
}
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
@ -79,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(
@ -112,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)
@ -172,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,
@ -234,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\"}"
}
}
}

View file

@ -4,6 +4,8 @@ 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
@ -83,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()
}
}

View file

@ -12,6 +12,7 @@ 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
@ -136,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)
}

View file

@ -89,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(
@ -102,7 +108,7 @@ data class WcTransactionData(
enum class WcTransactionType {
EthSignTransaction,
EthSendTransaction,
EthSendTransaction
}
data class WcPersonalSignData(
@ -111,4 +117,29 @@ data class WcPersonalSignData(
val id: Long,
val dialogData: PersonalSignDialogData,
)
)
sealed class BinanceMessageData(
val address: String,
val data: ByteArray,
) {
class Trade(
val tradeData: List<TradeData>,
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
)

View file

@ -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()
}
}
}

View file

@ -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) {

View file

@ -294,4 +294,21 @@
<string name="details_manage_security_access_code_disclaimer" translatable="false">Access code is available only for cards with a backup.</string>
<string name="wallet_connect_bnb_transaction_signed" translatable="false">The BNB transaction has been successfully signed and sent to the Dapp</string>
<string name="wallet_connect_bnb_sign_message" translatable="false">Dapp %s, requesting to\n
sign BNB transaction with\n
Card: %s\n
\n
%s</string>
<string name="wallet_connect_bnb_transaction_message" translatable="false">Transaction details:\n
From: %s\n
To: %s\n
Amount: %s</string>
<string name="wallet_connect_bnb_trade_order_message" translatable="false">Trade order for %s\n
Price: %s\n
Amount to receive: %s\n
Amount to pay: %s</string>
</resources>