Skip to content

Commit

Permalink
fix: session management when switching chains (#2640)
Browse files Browse the repository at this point in the history
* fix: session management when switching chains

* fix: move function to private method

* fix: enable header, grey out sessions + don't add chains
  • Loading branch information
iamacook authored Oct 17, 2023
1 parent d5edcc9 commit c199533
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 38 deletions.
4 changes: 2 additions & 2 deletions src/components/walletconnect/HeaderWidget/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type IconProps = {
}
}

const Icon = ({ sessionCount, sessionInfo, ...props }: IconProps): React.ReactElement => (
<ButtonBase disableRipple onClick={props.onClick}>
const Icon = ({ sessionCount, sessionInfo, onClick }: IconProps): React.ReactElement => (
<ButtonBase disableRipple onClick={onClick}>
<Badge
badgeContent={
sessionCount > 1
Expand Down
8 changes: 7 additions & 1 deletion src/components/walletconnect/SessionList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard'
import useSafeInfo from '@/hooks/useSafeInfo'
import { Button, List, ListItem, ListItemAvatar, ListItemIcon, ListItemText, Typography } from '@mui/material'
import type { SessionTypes } from '@walletconnect/types'
import type { ReactElement } from 'react'
Expand All @@ -17,14 +18,19 @@ const SessionListItem = ({
session: SessionTypes.Struct
onDisconnect: () => void
}): ReactElement => {
const { safeLoaded } = useSafeInfo()

return (
<ListItem className={css.sessionListItem}>
{session.peer.metadata.icons[0] && (
<ListItemAvatar className={css.sessionListAvatar}>
<SafeAppIconCard src={session.peer.metadata.icons[0]} alt="icon" width={20} height={20} />
</ListItemAvatar>
)}
<ListItemText primary={session.peer.metadata.name} />
<ListItemText
primary={session.peer.metadata.name}
primaryTypographyProps={{ color: safeLoaded ? undefined : 'text.secondary' }}
/>
<ListItemIcon className={css.sessionListSecondaryAction}>
<Button variant="danger" onClick={onDisconnect} className={css.button}>
Disconnect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding-top: var(--space-2);
}
4 changes: 3 additions & 1 deletion src/components/walletconnect/WcInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import type { ReactElement } from 'react'
import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext'
import { asError } from '@/services/exceptions/utils'
import { getClipboard, isPastingSupported } from '@/utils/clipboard'
import useSafeInfo from '@/hooks/useSafeInfo'

import css from '../SessionList/styles.module.css'

const WcInput = ({ uri }: { uri: string }): ReactElement => {
const { safeLoaded } = useSafeInfo()
const { walletConnect } = useContext(WalletConnectContext)
const [value, setValue] = useState('')
const [error, setError] = useState<Error>()
Expand Down Expand Up @@ -57,7 +59,7 @@ const WcInput = ({ uri }: { uri: string }): ReactElement => {
onChange={(e) => onInput(e.target.value)}
fullWidth
autoComplete="off"
disabled={connecting}
disabled={connecting || !safeLoaded}
error={!!error}
label={error ? error.message : 'Pairing UI'}
placeholder="wc:"
Expand Down
77 changes: 43 additions & 34 deletions src/services/walletconnect/WalletConnectWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,46 +80,50 @@ class WalletConnectWallet {
})
}

private getNamespaces(proposal: Web3WalletTypes.SessionProposal, currentChainId: string, safeAddress: string) {
// Most dApps require mainnet, but we aren't always on mainnet
// As workaround, we pretend include all required and optional chains with the Safe chainId
const requiredChains = proposal.params.requiredNamespaces[EIP155]?.chains || []
const optionalChains = proposal.params.optionalNamespaces[EIP155]?.chains || []

const supportedChainIds = [currentChainId].concat(
requiredChains.map(stripEip155Prefix),
optionalChains.map(stripEip155Prefix),
)

const eip155ChainIds = supportedChainIds.map(getEip155ChainId)
const eip155Accounts = eip155ChainIds.map((eip155ChainId) => `${eip155ChainId}:${safeAddress}`)

// Don't include optionalNamespaces methods/events
const methods = proposal.params.requiredNamespaces[EIP155]?.methods ?? SAFE_COMPATIBLE_METHODS
const events = proposal.params.requiredNamespaces[EIP155]?.events || []

return buildApprovedNamespaces({
proposal: proposal.params,
supportedNamespaces: {
[EIP155]: {
chains: eip155ChainIds,
accounts: eip155Accounts,
methods,
events,
},
},
})
}

public async approveSession(proposal: Web3WalletTypes.SessionProposal, currentChainId: string, safeAddress: string) {
assertWeb3Wallet(this.web3Wallet)

// Actual safe chainId
const safeChains = [currentChainId]

const getNamespaces = (chainIds: string[], methods: string[]) => {
const eip155ChainIds = chainIds.map(getEip155ChainId)

// Create a list of addresses for each chainId
const eip155Accounts = eip155ChainIds.map((eip155ChainId) => `${eip155ChainId}:${safeAddress}`)

return buildApprovedNamespaces({
proposal: proposal.params,
supportedNamespaces: {
[EIP155]: {
chains: eip155ChainIds,
methods,
accounts: eip155Accounts,
// Don't include optionalNamespaces events
events: proposal.params.requiredNamespaces[EIP155]?.events || [],
},
},
})
}
const namespaces = this.getNamespaces(proposal, currentChainId, safeAddress)

// Approve the session proposal
// 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),
namespaces,
})

await this.updateSession(session, currentChainId, safeAddress)
// Align the session with the current chainId
await this.chainChanged(session.topic, currentChainId)

// Workaround: WalletConnect doesn't have a session_add event
this.web3Wallet?.events.emit(SESSION_ADD_EVENT, session)
Expand All @@ -140,12 +144,17 @@ class WalletConnectWallet {
const hasNewChainId = !currentEip155ChainIds.includes(newEip155ChainId)
const hasNewAccount = !currentEip155Accounts.includes(newEip155Account)

// Add new chainId and/or account to the session namespace
if (hasNewChainId || hasNewAccount) {
// Switching to unsupported chain
if (hasNewChainId) {
return this.disconnectSession(session)
}

// Add new account to the session namespace
if (hasNewAccount) {
const namespaces: SessionTypes.Namespaces = {
[EIP155]: {
...session.namespaces[EIP155],
chains: [newEip155ChainId, ...currentEip155ChainIds],
chains: currentEip155ChainIds,
accounts: [newEip155Account, ...currentEip155Accounts],
},
}
Expand Down

0 comments on commit c199533

Please sign in to comment.