From 77c0b3615cd1675d7f145aea24ea3d664e107f58 Mon Sep 17 00:00:00 2001 From: abretonc7s Date: Tue, 11 Jul 2023 20:17:40 +0800 Subject: [PATCH] feat: handle wallet_switchEthereumChain modal and autoswitch between network --- app/core/WalletConnect/WalletConnectV2.ts | 53 +++++++++++++++++------ app/core/WalletConnect/wc-utils.ts | 30 +++++++++++++ 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/app/core/WalletConnect/WalletConnectV2.ts b/app/core/WalletConnect/WalletConnectV2.ts index 10fb9a76f34..7d22bc3992e 100644 --- a/app/core/WalletConnect/WalletConnectV2.ts +++ b/app/core/WalletConnect/WalletConnectV2.ts @@ -1,6 +1,6 @@ -import { Minimizer } from '../NativeModules'; import AppConstants from '../AppConstants'; import BackgroundBridge from '../BackgroundBridge/BackgroundBridge'; +import { Minimizer } from '../NativeModules'; import getRpcMethodMiddleware, { ApprovalTypes, } from '../RPCMethods/RPCMethodMiddleware'; @@ -16,9 +16,7 @@ import { WalletDevice, } from '@metamask/transaction-controller'; -// disable linting as core is included from se-sdk, -// including it in package.json overwrites sdk deps and create error -// eslint-disable-next-line import/no-extraneous-dependencies +import AsyncStorage from '@react-native-async-storage/async-storage'; import { Core } from '@walletconnect/core'; import { ErrorResponse } from '@walletconnect/jsonrpc-types'; import Client, { @@ -31,9 +29,10 @@ import Engine from '../Engine'; import getAllUrlParams from '../SDKConnect/utils/getAllUrlParams.util'; import { waitForKeychainUnlocked } from '../SDKConnect/utils/wait.util'; import WalletConnect from './WalletConnect'; -import parseWalletConnectUri from './wc-utils'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import METHODS_TO_REDIRECT from './wc-config'; +import parseWalletConnectUri, { + waitForNetworkModalOnboarding, +} from './wc-utils'; const { PROJECT_ID } = AppConstants.WALLET_CONNECT; export const isWC2Enabled = @@ -50,6 +49,9 @@ const ERROR_MESSAGES = { const ERROR_CODES = { USER_REJECT_CODE: 5000, }; + +const RPC_WALLET_SWITCHETHEREUMCHAIN = 'wallet_switchEthereumChain'; + class WalletConnect2Session { private backgroundBridge: BackgroundBridge; private web3Wallet: Client; @@ -57,6 +59,9 @@ class WalletConnect2Session { private session: SessionTypes.Struct; private requestsToRedirect: { [request: string]: boolean } = {}; private topicByRequestId: { [requestId: string]: string } = {}; + private requestByRequestId: { + [requestId: string]: SingleEthereumTypes.SessionRequest; + } = {}; constructor({ web3Wallet, @@ -149,6 +154,26 @@ class WalletConnect2Session { approveRequest = async ({ id, result }: { id: string; result: unknown }) => { const topic = this.topicByRequestId[id]; + const initialRequest = this.requestByRequestId[id]; + + // Special case for eth_switchNetwork to wait for the modal to be closed + if ( + initialRequest?.params.request.method === RPC_WALLET_SWITCHETHEREUMCHAIN + ) { + try { + const params = initialRequest.params.request.params as unknown[]; + const { chainId } = params[0] as { chainId: string }; + + if (chainId) { + await waitForNetworkModalOnboarding({ + chainId: parseInt(chainId) + '', + }); + } + } catch (err) { + // Ignore error as it is not critical when timeout for modal is reached + // It allows to safely continue and prevent pilling up the requests. + } + } try { await this.web3Wallet.approveRequest({ @@ -223,6 +248,7 @@ class WalletConnect2Session { handleRequest = async (requestEvent: SingleEthereumTypes.SessionRequest) => { this.topicByRequestId[requestEvent.id] = requestEvent.topic; + this.requestByRequestId[requestEvent.id] = requestEvent; const verified = requestEvent.verifyContext?.verified; const hostname = verified?.origin; @@ -385,20 +411,19 @@ export class WC2Manager { } } catch (err) { console.warn(`WC2::init Init failed due to ${err}`); + throw err; } let web3Wallet; + const options: SingleEthereumTypes.Options = { + core: core as any, + metadata: AppConstants.WALLET_CONNECT.METADATA, + }; try { - web3Wallet = await SingleEthereum.init({ - core: core as any, - metadata: AppConstants.WALLET_CONNECT.METADATA, - }); + web3Wallet = await SingleEthereum.init(options); } catch (err) { // TODO Sometime needs to init twice --- not sure why... - web3Wallet = await SingleEthereum.init({ - core: core as any, - metadata: AppConstants.WALLET_CONNECT.METADATA, - }); + web3Wallet = await SingleEthereum.init(options); } let deeplinkSessions = {}; diff --git a/app/core/WalletConnect/wc-utils.ts b/app/core/WalletConnect/wc-utils.ts index 5025812011f..47f42f2cb50 100644 --- a/app/core/WalletConnect/wc-utils.ts +++ b/app/core/WalletConnect/wc-utils.ts @@ -1,6 +1,8 @@ import { RelayerTypes } from '@walletconnect/types'; import { parseRelayParams } from '@walletconnect/utils'; import qs from 'qs'; +import { store } from '../../../app/store'; +import { wait } from '../SDKConnect/utils/wait.util'; export interface WCMultiVersionParams { protocol: string; @@ -52,4 +54,32 @@ export const isValidWCURI = (uri: string): boolean => { return false; }; +const MAX_LOOP_COUNTER = 60; +export const waitForNetworkModalOnboarding = async ({ + chainId, +}: { + chainId: string; +}): Promise => { + let waitForNetworkModalOnboarded = true; + + // throw timeout error after 30sec + let loopCounter = 0; + + while (waitForNetworkModalOnboarded) { + loopCounter += 1; + const { networkOnboarded } = store.getState(); + const { networkOnboardedState } = networkOnboarded; + + if (networkOnboardedState[chainId]) { + waitForNetworkModalOnboarded = false; + // exit the looop + } else { + await wait(1000); + } + + if (loopCounter >= MAX_LOOP_COUNTER) { + throw new Error('Timeout error'); + } + } +}; export default parseWalletConnectUri;