From a62861d7753592f458eb0ab76a39ca45fcf53566 Mon Sep 17 00:00:00 2001 From: Alexander Ivchenko <47615361+TravellerOnTheRun@users.noreply.github.com> Date: Fri, 29 Mar 2024 17:11:39 +0400 Subject: [PATCH] US-1964 WalletConnect sessions don't get erased when the wallet is reset (#900) * fix: wc sessions are being pulled from the same storage, even for different address * refactor: more effective solutions for removing WC sessions * fix: return the WC2 key to MMKV storage * fix: not being able to delete wc sessions * fix: renaming the mmkv back to mmkv.default * Added .toLowerCase() in the sign typed data resolver logic. * Update WalletConnect2Context.tsx * Update WalletConnect2Context.tsx --------- Co-authored-by: Francis Rodriguez Co-authored-by: Jesse Clark --- src/redux/slices/settingsSlice/index.ts | 2 + .../walletConnect/WalletConnect2Context.tsx | 56 +++++++++++-------- .../walletConnect/walletConnect2.utils.ts | 9 ++- src/storage/ChainStorage.ts | 8 +-- src/storage/MMKVStorage.ts | 31 +++------- 5 files changed, 54 insertions(+), 52 deletions(-) diff --git a/src/redux/slices/settingsSlice/index.ts b/src/redux/slices/settingsSlice/index.ts index 186f4456c..0c3695205 100644 --- a/src/redux/slices/settingsSlice/index.ts +++ b/src/redux/slices/settingsSlice/index.ts @@ -39,6 +39,7 @@ import { import { addressToUse, Wallet } from 'shared/wallet' import { createAppWallet, loadAppWallet } from 'shared/utils' import { MMKVStorage } from 'storage/MMKVStorage' +import { deleteWCSessions } from 'screens/walletConnect/walletConnect2.utils' import { Bitcoin, @@ -346,6 +347,7 @@ export const resetApp = createAsyncThunk( thunkAPI.dispatch(setKeysExist(false)) resetMainStorage() resetReduxStorage() + deleteWCSessions() return 'deleted' } catch (err) { return thunkAPI.rejectWithValue(err) diff --git a/src/screens/walletConnect/WalletConnect2Context.tsx b/src/screens/walletConnect/WalletConnect2Context.tsx index 389dc82a7..b88410996 100644 --- a/src/screens/walletConnect/WalletConnect2Context.tsx +++ b/src/screens/walletConnect/WalletConnect2Context.tsx @@ -11,6 +11,7 @@ import { IWeb3Wallet } from '@walletconnect/web3wallet' import { WalletConnectAdapter } from '@rsksmart/rif-wallet-adapters' import { ChainID } from 'lib/eoaWallet' +import { createPendingTxFromTxResponse } from 'lib/utils' import { buildRskAllowedNamespaces, @@ -21,7 +22,7 @@ import { import { useAppDispatch, useAppSelector } from 'store/storeUtils' import { selectChainId } from 'store/slices/settingsSlice' import { addPendingTransaction } from 'store/slices/transactionsSlice' -import { createPendingTxFromTxResponse } from 'src/lib/utils' +import { Wallet, addressToUse } from 'shared/wallet' const onSessionApprove = async ( web3wallet: Web3Wallet, @@ -112,6 +113,7 @@ export const WalletConnect2Provider = ({ children, wallet, }: WalletConnect2ProviderProps) => { + const address = wallet ? addressToUse(wallet) : null const dispatch = useAppDispatch() const chainId = useAppSelector(selectChainId) const [sessions, setSessions] = useState([]) @@ -125,6 +127,8 @@ export const WalletConnect2Provider = ({ proposal: Web3WalletTypes.SessionProposal, usersWallet: Web3Wallet, ) => { + console.log('onSessionProposal', proposal) + const hasErrors = getProposalErrorComparedWithRskNamespace(proposal) if (hasErrors) { await onSessionReject(usersWallet, proposal, hasErrors) @@ -144,15 +148,15 @@ export const WalletConnect2Provider = ({ } const subscribeToEvents = useCallback( - (usersWallet: Web3Wallet) => { + (usersWallet: Web3Wallet, _wallet: Wallet) => { usersWallet.on('session_proposal', async proposal => onSessionProposal(proposal, usersWallet), ) usersWallet.on('session_request', async event => { - if (!wallet) { + if (!_wallet) { return } - const adapter = new WalletConnectAdapter(wallet) + const adapter = new WalletConnectAdapter(_wallet) const eth_signTypedDataResolver = adapter .getResolvers() .find( @@ -164,10 +168,7 @@ export const WalletConnect2Provider = ({ // if address = relay address - throw error const { verifyingContract } = domain if ( - [ - wallet.smartWalletAddress.toLowerCase(), - wallet.rifRelaySdk.smartWalletFactory.address.toLowerCase(), - ].includes(verifyingContract.toLowerCase()) + [address?.toLowerCase()].includes(verifyingContract.toLowerCase()) ) { throw new Error( 'Error: Unauthorized Contract Address - Signing not permitted. This address is exclusive to the relay contract.', @@ -194,12 +195,12 @@ export const WalletConnect2Provider = ({ adapter .handleCall(method, params) .then(async signedMessage => { - if (method === 'eth_sendTransaction') { + if (method === 'eth_sendTransaction' && address) { const pendingTx = await createPendingTxFromTxResponse( signedMessage, { chainId, - from: wallet.smartWalletAddress, + from: address, to: params[0].to, }, ) @@ -231,14 +232,14 @@ export const WalletConnect2Provider = ({ ) }) }, - [chainId, dispatch, wallet], + [chainId, dispatch, address], ) const onCreateNewSession = useCallback( async (uri: string) => { - if (web3wallet) { + if (web3wallet && wallet) { try { - subscribeToEvents(web3wallet) + subscribeToEvents(web3wallet, wallet) // Refer to https://docs.walletconnect.com/2.0/reactnative/web3wallet/wallet-usage#session-requests if (!isWcUriValid(uri)) { @@ -279,15 +280,15 @@ export const WalletConnect2Provider = ({ } } }, - [subscribeToEvents, web3wallet], + [subscribeToEvents, web3wallet, wallet], ) const onUserApprovedSession = async () => { - if (pendingSession && wallet) { + if (pendingSession && address) { const newSession = await onSessionApprove( pendingSession.web3wallet, pendingSession.proposal, - wallet.smartWalletAddress, + address, chainId, ) if (typeof newSession === 'string') { @@ -338,20 +339,27 @@ export const WalletConnect2Provider = ({ [web3wallet], ) - const onContextFirstLoad = useCallback(async () => { - console.log('onContextFirstLoad') - const newWeb3Wallet = await createWeb3Wallet() - setWeb3Wallet(newWeb3Wallet) - subscribeToEvents(newWeb3Wallet) - setSessions(Object.values(newWeb3Wallet.getActiveSessions())) - }, [subscribeToEvents]) + const onContextFirstLoad = useCallback( + async (_wallet: Wallet) => { + try { + console.log('onContextFirstLoad') + const newWeb3Wallet = await createWeb3Wallet() + setWeb3Wallet(newWeb3Wallet) + subscribeToEvents(newWeb3Wallet, _wallet) + setSessions(Object.values(newWeb3Wallet.getActiveSessions())) + } catch (err) { + throw new Error(err) + } + }, + [subscribeToEvents], + ) /** * useEffect On first load, fetch previous saved sessions */ useEffect(() => { if (wallet) { - onContextFirstLoad().catch(console.log) + onContextFirstLoad(wallet).catch(console.log) } }, [wallet, onContextFirstLoad]) diff --git a/src/screens/walletConnect/walletConnect2.utils.ts b/src/screens/walletConnect/walletConnect2.utils.ts index 07011506e..c518b1ef4 100644 --- a/src/screens/walletConnect/walletConnect2.utils.ts +++ b/src/screens/walletConnect/walletConnect2.utils.ts @@ -8,6 +8,8 @@ import { getEnvSetting } from 'core/config' import { SETTINGS } from 'core/types' import { AcceptedValue, MMKVStorage } from 'storage/MMKVStorage' +const WC2 = 'WC2' + export type WalletConnect2SdkErrorString = Parameters[0] const WalletConnect2SdkErrorEnum: { [P in WalletConnect2SdkErrorString]: P } = { @@ -36,8 +38,13 @@ const WalletConnect2SdkErrorEnum: { [P in WalletConnect2SdkErrorString]: P } = { type StorageTypeFromCore = InstanceType['storage'] +export const deleteWCSessions = () => { + const storage = new MMKVStorage('WC2') + storage.deleteAll() +} + class MMKVCoreStorage implements StorageTypeFromCore { - storage = new MMKVStorage('WC2') + storage = new MMKVStorage(WC2) getEntries(): Promise<[string, T][]> { const keys = this.storage.getAllKeys() diff --git a/src/storage/ChainStorage.ts b/src/storage/ChainStorage.ts index c36204f05..44cb3b9c1 100644 --- a/src/storage/ChainStorage.ts +++ b/src/storage/ChainStorage.ts @@ -1,11 +1,9 @@ import { ChainID } from 'lib/eoaWallet' -import { MMKVStorage } from 'storage/MMKVStorage' - -const ChainStorage = new MMKVStorage('chainStorage') +import { MainStorage } from './MainStorage' export const getCurrentChainId: () => ChainID = () => - ChainStorage.get('chainId') || 31 + MainStorage.get('chainId') || 31 export const setCurrentChainId = (chainId: ChainID) => - ChainStorage.set('chainId', chainId) + MainStorage.set('chainId', chainId) diff --git a/src/storage/MMKVStorage.ts b/src/storage/MMKVStorage.ts index 3abc004a2..6358942a6 100644 --- a/src/storage/MMKVStorage.ts +++ b/src/storage/MMKVStorage.ts @@ -4,8 +4,8 @@ import { initializeMMKVFlipper } from 'react-native-mmkv-flipper-plugin' export type AcceptedValue = boolean | string | number | object export class MMKVStorage { - private id = 'mmkv.default' - private storage: MMKV | null = null + private id: string + private storage: MMKV constructor(id = 'mmkv.default', encryptionKey?: string) { this.id = id @@ -20,42 +20,29 @@ export class MMKVStorage { } public set(key: string, value: AcceptedValue) { - if (this.storage && typeof value !== 'undefined') { + if (typeof value !== 'undefined') { this.storage.set(key, JSON.stringify(value)) } } public get(key = 'default') { - if (this.storage) { - const value = this.storage.getString(key) - return value && JSON.parse(value) - } + const value = this.storage.getString(key) + return value && JSON.parse(value) } public has(key = 'default') { - if (this.storage) { - return this.storage.contains(key) - } else { - return false - } + return this.storage.contains(key) } public delete(key: string) { - if (this.storage) { - this.storage.delete(key) - } + this.storage.delete(key) } public deleteAll() { - if (this.storage) { - this.storage.clearAll() - } + this.storage.clearAll() } public getAllKeys() { - if (this.storage) { - return this.storage.getAllKeys() - } - return [] + return this.storage.getAllKeys() || [] } }