Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: wc2 dependencies update and handle switchNetwork #6782

Merged
merged 11 commits into from
Jul 13, 2023
4 changes: 4 additions & 0 deletions app/core/AppConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ export default {
description: 'MetaMask Wallet Integration',
url: 'https://metamask.io/',
icons: [],
redirect: {
native: 'metamask://',
universal: 'https://metamask.app.link/',
},
},
},
SWAPS: {
Expand Down
68 changes: 48 additions & 20 deletions app/core/WalletConnect/WalletConnectV2.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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, {
Expand All @@ -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 =
Expand All @@ -50,13 +49,19 @@ const ERROR_MESSAGES = {
const ERROR_CODES = {
USER_REJECT_CODE: 5000,
};

const RPC_WALLET_SWITCHETHEREUMCHAIN = 'wallet_switchEthereumChain';

class WalletConnect2Session {
private backgroundBridge: BackgroundBridge;
private web3Wallet: Client;
private deeplink: boolean;
private session: SessionTypes.Struct;
private requestsToRedirect: { [request: string]: boolean } = {};
private topicByRequestId: { [requestId: string]: string } = {};
private requestByRequestId: {
[requestId: string]: SingleEthereumTypes.SessionRequest;
} = {};

constructor({
web3Wallet,
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -223,12 +248,13 @@ class WalletConnect2Session {

handleRequest = async (requestEvent: SingleEthereumTypes.SessionRequest) => {
this.topicByRequestId[requestEvent.id] = requestEvent.topic;
this.requestByRequestId[requestEvent.id] = requestEvent;

const verified = requestEvent.verifyContext.verified;
const verified = requestEvent.verifyContext?.verified;
const hostname = verified?.origin;

let method = requestEvent.params.request.method;
const chainId = requestEvent.params.chainId;
const chainId = parseInt(requestEvent.params.chainId);
const methodParams = requestEvent.params.request.params as any;
Logger.log(
`WalletConnect2Session::handleRequest chainId=${chainId} method=${method}`,
Expand All @@ -238,11 +264,11 @@ class WalletConnect2Session {
const networkController = (
Engine.context as { NetworkController: NetworkController }
).NetworkController;
const selectedChainId = networkController.state.network;
const selectedChainId = parseInt(networkController.state.network);

if (selectedChainId !== chainId) {
await this.web3Wallet.rejectRequest({
id: parseInt(chainId),
id: chainId,
topic: this.session.topic,
error: { code: 1, message: ERROR_MESSAGES.INVALID_CHAIN },
});
Expand Down Expand Up @@ -373,27 +399,26 @@ export class WC2Manager {
if (typeof PROJECT_ID === 'string') {
core = new Core({
projectId: PROJECT_ID,
// logger: 'debug',
logger: 'fatal',
});
} else {
throw new Error('WC2::init Init Missing projectId');
}
} 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 = {};
Expand Down Expand Up @@ -609,7 +634,10 @@ export class WC2Manager {

await session.handleRequest(requestEvent);
} catch (err) {
console.error(`Error while handling request`, err);
console.error(
`WC2::onSessionRequest() Error while handling request`,
err,
);
}
}

Expand Down
30 changes: 30 additions & 0 deletions app/core/WalletConnect/wc-utils.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<void> => {
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;
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@
"@tradle/react-native-http": "2.0.1",
"@types/lodash": "^4.14.184",
"@walletconnect/client": "^1.7.1",
"@walletconnect/core": "2.7.6",
"@walletconnect/core": "^2.9.0",
"@walletconnect/jsonrpc-types": "^1.0.2",
"@walletconnect/react-native-compat": "2.7.6",
"@walletconnect/se-sdk": "1.4.0",
"@walletconnect/utils": "2.7.6",
"@walletconnect/react-native-compat": "^2.9.0",
"@walletconnect/se-sdk": "1.5.0",
"@walletconnect/utils": "^2.9.0",
"appium-adb": "^9.11.4",
"asyncstorage-down": "4.2.0",
"axios": "^0.26.1",
Expand Down Expand Up @@ -374,7 +374,7 @@
"@types/url-parse": "^1.4.8",
"@typescript-eslint/eslint-plugin": "^4.20.0",
"@typescript-eslint/parser": "^4.20.0",
"@walletconnect/types": "^2.7.1",
"@walletconnect/types": "^2.9.0",
"@wdio/appium-service": "^7.19.1",
"@wdio/browserstack-service": "^7.26.0",
"@wdio/cli": "^7.19.1",
Expand Down
Loading
Loading