Updated on 2026-08-14

This commit is contained in:
Tangem 2021-12-20 23:16:27 +03:00
parent 74c9f93bd3
commit 8ac903ba80
8 changed files with 200 additions and 68 deletions

View file

@ -60,6 +60,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 +90,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 +105,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(
@ -119,7 +129,7 @@ class WalletConnectManager {
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
}
@ -246,7 +256,9 @@ class WalletConnectManager {
val sessionData = data.toWalletConnectSession()
sessionData?.let {
store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession(
sessionData))
session = sessionData,
chainId = client.chainId?.toIntOrNull()
))
}
FirebaseAnalyticsHandler.logWcEvent(
FirebaseAnalyticsHandler.WcAnalyticsEvent.Session(

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

@ -35,17 +35,16 @@ class WalletConnectSdkHelper {
): WcTransactionData? {
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
val publicKey = Wallet.PublicKey(
session.wallet.walletPublicKey,
session.wallet.walletPublicKey ?: return null,
session.wallet.derivedPublicKey,
session.wallet.derivationPath,
)
val walletManager = factory.makeEthereumWalletManager(
session.wallet.cardId,
publicKey,
emptyList(),
isTestNet = session.wallet.isTestNet
) ?: return null
val blockchain = session.wallet.getBlockchainForSession()
val walletManager = factory.makeWalletManager(
session.wallet.cardId,
blockchain,
publicKey
) ?: return null
try {
walletManager.update()
@ -54,8 +53,6 @@ class WalletConnectSdkHelper {
return null
}
val blockchain = walletManager.wallet.blockchain
val balance =
walletManager.wallet.amounts[AmountType.Coin]?.value ?: return null
@ -213,12 +210,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 -> {

View file

@ -2,6 +2,7 @@ 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.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
@ -27,13 +28,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,

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
@ -14,9 +13,12 @@ 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.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 +50,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 +85,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 -> {
@ -134,60 +150,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
}
}