From bc28067ca0d65fe126a00bd39db95c2e679537f0 Mon Sep 17 00:00:00 2001 From: iamacook Date: Wed, 27 Sep 2023 13:09:26 +0200 Subject: [PATCH] fix: keep session aligned with peer --- .../walletconnect/ProposalForm/index.tsx | 5 +- .../walletconnect/WalletConnectContext.tsx | 11 +- .../walletconnect/WalletConnectWallet.ts | 129 +++++++++--------- src/services/walletconnect/utils.ts | 4 +- 4 files changed, 72 insertions(+), 77 deletions(-) diff --git a/src/components/walletconnect/ProposalForm/index.tsx b/src/components/walletconnect/ProposalForm/index.tsx index 440222064f..a6c58390e9 100644 --- a/src/components/walletconnect/ProposalForm/index.tsx +++ b/src/components/walletconnect/ProposalForm/index.tsx @@ -5,7 +5,6 @@ import { EIP155 } from '@/services/walletconnect/constants' import useChains from '@/hooks/useChains' import ChainIndicator from '@/components/common/ChainIndicator' import { getEip155ChainId } from '@/services/walletconnect/utils' -import type { Eip155ChainId } from '@/services/walletconnect/utils' import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard' import css from './styles.module.css' import ProposalVerification from './ProposalVerification' @@ -20,8 +19,8 @@ const ProposalForm = ({ proposal, onApprove, onReject }: ProposalFormProps) => { const { configs } = useChains() const { requiredNamespaces, optionalNamespaces, proposer } = proposal.params const { isScam } = proposal.verifyContext.verified - const requiredChains = (requiredNamespaces[EIP155]?.chains as Array | undefined) ?? [] - const optionalChains = (optionalNamespaces[EIP155]?.chains as Array | undefined) ?? [] + const requiredChains = requiredNamespaces[EIP155]?.chains ?? [] + const optionalChains = optionalNamespaces[EIP155]?.chains ?? [] const chainIds = configs .filter((chain) => { diff --git a/src/services/walletconnect/WalletConnectContext.tsx b/src/services/walletconnect/WalletConnectContext.tsx index 481bcb8361..40ea570d07 100644 --- a/src/services/walletconnect/WalletConnectContext.tsx +++ b/src/services/walletconnect/WalletConnectContext.tsx @@ -33,18 +33,11 @@ export const WalletConnectProvider = ({ children }: { children: ReactNode }) => .catch(setError) }, []) - // Update chainId - useEffect(() => { - if (!walletConnect || !chainId) return - - walletConnect.chainChanged(chainId).catch(setError) - }, [walletConnect, chainId]) - - // Update accounts + // Update chainId/safeAddress useEffect(() => { if (!walletConnect || !chainId || !safeAddress) return - walletConnect.accountsChanged(chainId, safeAddress).catch(setError) + walletConnect.updateSessions(chainId, safeAddress).catch(setError) }, [walletConnect, chainId, safeAddress]) // Subscribe to requests diff --git a/src/services/walletconnect/WalletConnectWallet.ts b/src/services/walletconnect/WalletConnectWallet.ts index fbea43a972..49f4e31a11 100644 --- a/src/services/walletconnect/WalletConnectWallet.ts +++ b/src/services/walletconnect/WalletConnectWallet.ts @@ -10,9 +10,8 @@ import { IS_PRODUCTION, WC_PROJECT_ID } from '@/config/constants' import { EIP155, SAFE_COMPATIBLE_METHODS, SAFE_WALLET_METADATA } from './constants' import { invariant } from '@/utils/helpers' import { getEip155ChainId, stripEip155Prefix } from './utils' -import type { Eip155ChainId } from './utils' -const SESSION_ADD_EVENT = 'session_add' as 'session_delete' // Workaround: WalletConnect doesn't emit session_add event +const SESSION_ADD_EVENT = 'session_add' as Web3WalletTypes.Event // Workaround: WalletConnect doesn't emit session_add event function assertWeb3Wallet(web3Wallet: T): asserts web3Wallet { return invariant(web3Wallet, 'WalletConnect not initialized') @@ -54,40 +53,30 @@ class WalletConnectWallet { await this.web3Wallet.core.pairing.pair({ uri }) } - public async chainChanged(chainId: string) { - const sessions = this.getActiveSessions() + public async chainChanged(topic: string, chainId: string) { const eipChainId = getEip155ChainId(chainId) - await Promise.all( - sessions.map(({ topic }) => { - return this.web3Wallet?.emitSessionEvent({ - topic, - event: { - name: 'chainChanged', - data: Number(chainId), - }, - chainId: eipChainId, - }) - }), - ) + return this.web3Wallet?.emitSessionEvent({ + topic, + event: { + name: 'chainChanged', + data: Number(chainId), + }, + chainId: eipChainId, + }) } - public async accountsChanged(chainId: string, address: string) { - const sessions = this.getActiveSessions() + public async accountsChanged(topic: string, chainId: string, address: string) { const eipChainId = getEip155ChainId(chainId) - await Promise.all( - sessions.map(({ topic }) => { - return this.web3Wallet?.emitSessionEvent({ - topic, - event: { - name: 'accountsChanged', - data: [address], - }, - chainId: eipChainId, - }) - }), - ) + return this.web3Wallet?.emitSessionEvent({ + topic, + event: { + name: 'accountsChanged', + data: [address], + }, + chainId: eipChainId, + }) } public async approveSession(proposal: Web3WalletTypes.SessionProposal, currentChainId: string, safeAddress: string) { @@ -116,39 +105,19 @@ class WalletConnectWallet { } // Approve the session proposal - let session - try { - session = await this.web3Wallet.approveSession({ - id: proposal.id, - namespaces: getNamespaces(safeChains, SAFE_COMPATIBLE_METHODS), - }) - } catch (e) { - // Most dapps require mainnet, but we aren't always on mainnet - // A workaround, pretend to support all required chains - const requiredChains = - (proposal.params.requiredNamespaces[EIP155]?.chains as Array | undefined) || [] - const chains = safeChains.concat(requiredChains.map(stripEip155Prefix)) - - session = await this.web3Wallet.approveSession({ - id: proposal.id, - namespaces: getNamespaces( - chains, - proposal.params.requiredNamespaces[EIP155]?.methods ?? SAFE_COMPATIBLE_METHODS, - ), - }) + // Most dapps require mainnet, but we aren't always on mainnet + // A workaround, pretend to support all required chains + const requiredChains = proposal.params.requiredNamespaces[EIP155]?.chains || [] + // TODO: Filter against those which we support + const optionalChains = proposal.params.optionalNamespaces[EIP155]?.chains || [] + const chains = safeChains.concat(requiredChains.map(stripEip155Prefix), optionalChains.map(stripEip155Prefix)) + + const session = await this.web3Wallet.approveSession({ + id: proposal.id, + namespaces: getNamespaces(chains, proposal.params.requiredNamespaces[EIP155]?.methods ?? SAFE_COMPATIBLE_METHODS), + }) - // Immediately switch to the correct chain and set the actual namespace - try { - await this.chainChanged(currentChainId) - - await this.web3Wallet.updateSession({ - topic: session.topic, - namespaces: getNamespaces(safeChains, SAFE_COMPATIBLE_METHODS), - }) - } catch (e) { - // Ignore - } - } + // TODO: Align session via update then filter "fake" addresses? If we filter in updateSession this would be covered // Workaround: WalletConnect doesn't have a session_add event this.web3Wallet?.events.emit(SESSION_ADD_EVENT) @@ -156,6 +125,42 @@ class WalletConnectWallet { return session } + private async updateSession(session: SessionTypes.Struct, chainId: string, safeAddress: string) { + const currentChains = session.namespaces[EIP155]?.chains || [] + const currentAccounts = session.namespaces[EIP155]?.accounts || [] + + const eip155ChainIds = [...new Set([...currentChains, getEip155ChainId(chainId)])] + const eip155Accounts = [...new Set([...currentAccounts, `${getEip155ChainId(chainId)}:${safeAddress}`])] + + const namespaces: SessionTypes.Namespaces = { + [EIP155]: { + ...session.namespaces[EIP155], + chains: eip155ChainIds, + accounts: eip155Accounts, + }, + } + + const { topic } = session + + await this.web3Wallet?.updateSession({ + topic, + namespaces, + }) + + // TODO: Only emit if new address doesn't match that of current one? + await this.accountsChanged(topic, chainId, safeAddress) + + await this.chainChanged(topic, chainId) + + // TODO: Filter "fake" addresses? + } + + public async updateSessions(chainId: string, safeAddress: string) { + assertWeb3Wallet(this.web3Wallet) + + await Promise.all(this.getActiveSessions().map((session) => this.updateSession(session, chainId, safeAddress))) + } + public async rejectSession(proposal: Web3WalletTypes.SessionProposal) { assertWeb3Wallet(this.web3Wallet) diff --git a/src/services/walletconnect/utils.ts b/src/services/walletconnect/utils.ts index 013d3d41fa..225cdc0316 100644 --- a/src/services/walletconnect/utils.ts +++ b/src/services/walletconnect/utils.ts @@ -1,8 +1,6 @@ import { EIP155 } from './constants' -export type Eip155ChainId = `${typeof EIP155}:${string}` - -export const getEip155ChainId = (chainId: string): Eip155ChainId => { +export const getEip155ChainId = (chainId: string): string => { return `${EIP155}:${chainId}` }