Updated on 2026-08-14

This commit is contained in:
Tangem 2022-01-11 09:29:18 +00:00
commit 39114200c5
14 changed files with 623 additions and 120 deletions

View file

@ -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'

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

View file

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

View file

@ -88,7 +88,6 @@ data class SessionDao(
session = session.session,
peerMeta = session.peerMeta
)
}
}
}

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,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\"}"
}
}
}

View file

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

View file

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

View file

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

View file

@ -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<WalletConnectSession> = 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,
)
)
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>