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 70f4ae2d61..a5682e3b5c 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -105,6 +105,12 @@ class DialogManager : StoreSubscriber { dAppName = state.dialog.dAppName, context = context ) + is WalletConnectDialog.UnsupportedNetwork -> + SimpleAlertDialog.create( + titleRes = R.string.wallet_connect, + messageRes = R.string.wallet_connect_scanner_error_unsupported_network, + 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/EthSignHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt index fe8a593787..b366229775 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt @@ -1,11 +1,13 @@ package com.tangem.tap.domain.walletconnect import com.github.salomonbrys.kotson.fromJson +import com.github.salomonbrys.kotson.toMap import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonParser import com.google.gson.annotations.SerializedName +import com.tangem.blockchain.common.Blockchain import com.trustwallet.walletconnect.JSONRPC_VERSION import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage @@ -18,9 +20,11 @@ class EthSignHelper { .create() } - fun tryToParseEthTypedMessage(data: String): WCEthereumSignMessage? { - val request = - gson.fromJson(data) + fun parseCustomRequest(data: String): CustomJsonRpcRequest { + return gson.fromJson(data) + } + + fun tryToParseEthTypedMessage(request: CustomJsonRpcRequest): WCEthereumSignMessage? { return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) { WCEthereumSignMessage( listOf( @@ -53,12 +57,26 @@ class EthSignHelper { } } -data class CustomJsonRpcRequests( +data class CustomJsonRpcRequest( val id: Long, val jsonrpc: String = JSONRPC_VERSION, val method: WCMethodExtended?, val params: JsonArray -) +) { + + fun blockchainFromChainId(): Blockchain? { + return try { + val hex = params[0].asJsonObject.toMap()[CHAIN_ID_KEY]?.asString ?: "" + Blockchain.fromChainId(Integer.decode(hex)) + } catch (exception: Exception) { + null + } + } + + companion object { + const val CHAIN_ID_KEY = "chainId" + } +} enum class WCMethodExtended { @SerializedName("wc_sessionRequest") @@ -95,5 +113,10 @@ enum class WCMethodExtended { GET_ACCOUNTS, @SerializedName("trust_signTransaction") - SIGN_TRANSACTION; + SIGN_TRANSACTION, + + @SerializedName("wallet_switchEthereumChain") + SWITCH_CHAIN, + + ; } \ No newline at end of file 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 dd94266b06..7d36064f1e 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,14 +11,20 @@ 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.* +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 import com.trustwallet.walletconnect.models.session.WCSessionUpdate import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import okhttp3.* +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import okhttp3.logging.HttpLoggingInterceptor import timber.log.Timber import java.util.* @@ -71,6 +77,22 @@ class WalletConnectManager { } } + fun updateBlockchain(session: WalletConnectSession) { + sessions[session.session]?.client?.updateSession( + accounts = listOfNotNull(session.getAddress()), + chainId = session.wallet.blockchain?.getChainId(), + approved = true + ) + + + 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) @@ -409,23 +431,43 @@ class WalletConnectManager { Timber.d("Custom Request") Timber.d(data) - val message = EthSignHelper.tryToParseEthTypedMessage(data) - if (message != null) { - Timber.d("onEthSign_v4: $message") - store.state.globalState.analyticsHandlers?.logWcEvent( - Analytics.WcAnalyticsEvent.Action( - Analytics.WcAction.PersonalSign + val request = EthSignHelper.parseCustomRequest(data) + + when (request.method) { + WCMethodExtended.ETH_SIGN_TYPE_DATA_V4 -> handleTypedDataV4(request, client, id) + WCMethodExtended.SWITCH_CHAIN -> { + val blockchain = request.blockchainFromChainId() + Timber.d("WC switch chainID\nNew Blockchain: $blockchain") + val session = sessions[client.session]?.toWalletConnectSession() + if (session != null) { + store.dispatchOnMain(WalletConnectAction.SwitchBlockchain(blockchain, session)) + } + } + else -> { + Timber.d("WC: unrecognized custom request") + } + } + } + + } + + private fun handleTypedDataV4(request: CustomJsonRpcRequest, client: WCClient, id: Long) { + val message = EthSignHelper.tryToParseEthTypedMessage(request) + if (message != null) { + Timber.d("onEthSign_v4: $message") + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Action( + Analytics.WcAction.PersonalSign + ) + ) + sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + store.dispatchOnMain( + WalletConnectAction.HandlePersonalSignRequest( + message, + sessionData, + id ) ) - sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain( - WalletConnectAction.HandlePersonalSignRequest( - message, - sessionData, - id - ) - ) - } } } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt new file mode 100644 index 0000000000..7c978b7b6f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WcWalletManagerFactory.kt @@ -0,0 +1,95 @@ +package com.tangem.tap.domain.walletconnect + +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.common.card.EllipticCurve +import com.tangem.domain.common.ScanResponse +import com.tangem.domain.common.TapWorkarounds.isTestCard +import com.tangem.tap.domain.extensions.getPrimaryCurve +import com.tangem.tap.domain.extensions.makeWalletManagerForApp +import com.tangem.tap.domain.tokens.CurrenciesRepository +import com.tangem.tap.domain.tokens.models.BlockchainNetwork +import com.tangem.tap.features.details.redux.walletconnect.WalletForSession +import com.tangem.tap.features.wallet.redux.WalletState + +class WcWalletManagerFactory( + private val factory: WalletManagerFactory, + private val currenciesRepository: CurrenciesRepository, + ) { + + suspend fun getWalletManager( + wallet: WalletForSession, blockchain: Blockchain, walletState: WalletState + ): WalletManager? { + val blockchainToMake = if (blockchain == Blockchain.Ethereum && wallet.isTestNet) { + Blockchain.EthereumTestnet + } else { + blockchain + } + + val blockchainNetwork = BlockchainNetwork( + blockchain = blockchainToMake, + derivationPath = wallet.derivationPath?.rawPath, + tokens = emptyList() + ) + + return if (walletState.cardId == wallet.cardId) { + walletState.getWalletManager(blockchainNetwork) + } else { + val blockchainNetworkWithTokens = currenciesRepository + .loadSavedCurrencies( + cardId = wallet.cardId, + isHdWalletSupported = wallet.derivationPath != null + ).firstOrNull { it == blockchainNetwork } + + if (blockchainNetworkWithTokens != null) { + factory.makeWalletManager( + cardId = wallet.cardId, + blockchain = blockchainToMake, + publicKey = Wallet.PublicKey( + wallet.walletPublicKey!!, + wallet.derivedPublicKey, + wallet.derivationPath + ), + tokens = blockchainNetworkWithTokens.tokens, + curve = blockchainToMake.getPrimaryCurve() ?: EllipticCurve.Secp256k1 + ) + } else { + null + } + } + } + + suspend fun getWalletManager( + scanResponse: ScanResponse, blockchain: Blockchain, walletState: WalletState + ): WalletManager? { + val card = scanResponse.card + val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) { + Blockchain.EthereumTestnet + } else { + blockchain + } + + val blockchainNetwork = BlockchainNetwork( + blockchain = blockchainToMake, + card = card + ) + + return if (walletState.cardId == card.cardId) { + walletState.getWalletManager(blockchainNetwork) + } else { + if (currenciesRepository + .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed) + .contains(blockchainNetwork) + ) { + factory.makeWalletManagerForApp( + scanResponse = scanResponse, + blockchainNetwork = blockchainNetwork + ) + } else { + null + } + } + } +} \ 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 a180d20d4a..471ccaa44c 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.details.redux.walletconnect import android.app.Activity +import com.tangem.blockchain.common.Blockchain import com.tangem.domain.common.ScanResponse import com.tangem.tap.common.redux.NotificationAction import com.tangem.wallet.R @@ -47,6 +48,16 @@ sealed class WalletConnectAction : Action { data class Success(val session: WalletConnectSession) : WalletConnectAction() } + data class SwitchBlockchain( + val blockchain: Blockchain?, + val session: WalletConnectSession + ) : WalletConnectAction() + + data class UpdateBlockchain( + val updatedSession: WalletConnectSession + ) : WalletConnectAction() + + data class FailureEstablishingSession(val session: WCSession?) : WalletConnectAction() data class SetSessionsRestored(val sessions: List) : 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 25c5d373b4..7f7a88748a 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,10 +1,8 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.WalletManager import com.tangem.common.extensions.guard import com.tangem.domain.common.ScanResponse -import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.common.extensions.withMainContext import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard @@ -14,11 +12,10 @@ import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.extensions.isMultiwalletAllowed -import com.tangem.tap.domain.extensions.makeWalletManagerForApp -import com.tangem.tap.domain.tokens.models.BlockchainNetwork 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.domain.walletconnect.WcWalletManagerFactory import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.scope import com.tangem.tap.store @@ -159,6 +156,45 @@ class WalletConnectMiddleware { action.id, action.data, action.sessionData ) } + is WalletConnectAction.SwitchBlockchain -> { + val blockchain = action.blockchain.guard { + store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork)) + return + } + + val factory = WcWalletManagerFactory( + factory = store.state.globalState.tapWalletManager.walletManagerFactory, + currenciesRepository = currenciesRepository + ) + val walletState = store.state.walletState + scope.launch { + val walletManager = factory.getWalletManager( + wallet = action.session.wallet, + blockchain = blockchain, + walletState = walletState + ).guard { + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.AddNetwork(blockchain.fullName) + ) + ) + return@launch + } + + val updatedWallet = action.session.wallet.copy( + walletPublicKey = walletManager.wallet.publicKey.seedKey, + derivedPublicKey = walletManager.wallet.publicKey.derivedKey, + derivationPath = walletManager.wallet.publicKey.derivationPath, + blockchain = action.blockchain + + ) + val updatedSession = action.session.copy(wallet = updatedWallet) + store.dispatchOnMain(WalletConnectAction.UpdateBlockchain(updatedSession)) + } + } + is WalletConnectAction.UpdateBlockchain -> { + walletConnectManager.updateBlockchain(action.updatedSession) + } } } @@ -189,8 +225,13 @@ class WalletConnectMiddleware { return } + val factory = WcWalletManagerFactory( + factory = store.state.globalState.tapWalletManager.walletManagerFactory, + currenciesRepository = currenciesRepository + ) + val walletState = store.state.walletState scope.launch { - val walletManager = getWalletManager(scanResponse, blockchain).guard { + val walletManager = factory.getWalletManager(scanResponse, blockchain, walletState).guard { store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(session.session)) store.dispatchOnMain( GlobalAction.ShowDialog( @@ -232,37 +273,4 @@ class WalletConnectMiddleware { } } - - private suspend fun getWalletManager( - scanResponse: ScanResponse, blockchain: Blockchain - ): WalletManager? { - val card = scanResponse.card - val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val blockchainToMake = if (blockchain == Blockchain.Ethereum && card.isTestCard) { - Blockchain.EthereumTestnet - } else { - blockchain - } - - val blockchainNetwork = BlockchainNetwork( - blockchain = blockchainToMake, - card = card - ) - - return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) { - store.state.walletState.getWalletManager(blockchainNetwork) - } else { - if (currenciesRepository - .loadSavedCurrencies(card.cardId, card.settings.isHDWalletAllowed) - .contains(blockchainNetwork) - ) { - factory.makeWalletManagerForApp( - scanResponse = scanResponse, - blockchainNetwork = blockchainNetwork - ) - } else { - null - } - } - } } \ 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 216367e9ee..191fa7b170 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 @@ -34,6 +34,11 @@ class WalletConnectReducer { is WalletConnectAction.RefuseOpeningSession -> state.copy(loading = false) is WalletConnectAction.OpeningSessionTimeout -> state.copy(loading = false) is WalletConnectAction.FailureEstablishingSession -> state.copy(loading = false) + is WalletConnectAction.UpdateBlockchain -> state.copy( + sessions = state.sessions + .filterNot { it.peerId == action.updatedSession.peerId } + + action.updatedSession + ) else -> state } 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 4f5861c67b..fbd95c6393 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 @@ -82,6 +82,7 @@ data class WalletForSession( sealed class WalletConnectDialog : StateDialog { data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog() object UnsupportedCard : WalletConnectDialog() + object UnsupportedNetwork : WalletConnectDialog() data class AddNetwork(val network: String) : WalletConnectDialog() object OpeningSessionRejected : WalletConnectDialog() object SessionTimeout : WalletConnectDialog() diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index ba70c84e05..21489b82f3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -73,6 +73,9 @@ data class WalletState( val walletManagers: List get() = wallets.mapNotNull { it.walletManager } + val cardId: String? + get() = wallets.firstOrNull()?.walletManager?.wallet?.cardId + fun getWalletManager(currency: Currency?): WalletManager? { if (currency?.blockchain == null) return null return getWalletStore(currency)?.walletManager diff --git a/app/src/main/res/values-de/strings_final.xml b/app/src/main/res/values-de/strings_final.xml index 4eae54e057..e859d46e47 100644 --- a/app/src/main/res/values-de/strings_final.xml +++ b/app/src/main/res/values-de/strings_final.xml @@ -59,5 +59,6 @@ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. %s network not found. Please, add it first and try again. + This network is not supported. Please select another network. %s network \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings_final.xml b/app/src/main/res/values-fr/strings_final.xml index 158ab46c65..c4e1f946db 100644 --- a/app/src/main/res/values-fr/strings_final.xml +++ b/app/src/main/res/values-fr/strings_final.xml @@ -59,5 +59,6 @@ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. %s network not found. Please, add it first and try again. + This network is not supported. Please select another network. %s network \ No newline at end of file diff --git a/app/src/main/res/values-it/strings_final.xml b/app/src/main/res/values-it/strings_final.xml index 158ab46c65..bb1cf7be30 100644 --- a/app/src/main/res/values-it/strings_final.xml +++ b/app/src/main/res/values-it/strings_final.xml @@ -59,5 +59,7 @@ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. %s network not found. Please, add it first and try again. + This network is not supported. Please select another network. + %s network \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 588b043f15..d4ddb9ad8c 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -65,6 +65,8 @@ Токен %s является основной валютой в сети %s и не может быть скрыт до тех пор, пока у вас в списке есть другие токены этой сети. Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. + Сеть не поддерживается. Пожалуйста, выберите другую сеть. + Сеть %s Бэкап кошелька не был произведен diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index adbf1e7604..4c7ec8e0bd 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -64,6 +64,8 @@ The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. %s network not found. Please, add it first and try again. + This network is not supported. Please select another network. + %s network Your wallet has not been backed up