Skip to content

Commit

Permalink
feat: improve mm impersonator checks (#5896)
Browse files Browse the repository at this point in the history
* feat: mm impersonator checks

* feat: add comment

* feat: she worky

* chore: bump hdwallet to 1.53.3

* doodle checksums

---------

Co-authored-by: Apotheosis <97164662+0xApotheosis@users.noreply.github.com>
Co-authored-by: woodenfurniture <125113430+woodenfurniture@users.noreply.github.com>
  • Loading branch information
3 people committed Jan 30, 2024
1 parent 0fb6d66 commit 97e2c34
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 103 deletions.
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,19 @@
"@shapeshiftoss/caip": "workspace:^",
"@shapeshiftoss/chain-adapters": "workspace:^",
"@shapeshiftoss/errors": "workspace:^",
"@shapeshiftoss/hdwallet-coinbase": "1.53.1",
"@shapeshiftoss/hdwallet-core": "1.53.1",
"@shapeshiftoss/hdwallet-keepkey": "1.53.1",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.53.1",
"@shapeshiftoss/hdwallet-keplr": "1.53.1",
"@shapeshiftoss/hdwallet-ledger": "1.53.1",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.53.1",
"@shapeshiftoss/hdwallet-metamask": "1.53.1",
"@shapeshiftoss/hdwallet-native": "1.53.1",
"@shapeshiftoss/hdwallet-native-vault": "1.53.1",
"@shapeshiftoss/hdwallet-shapeshift-multichain": "1.53.1",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.53.1",
"@shapeshiftoss/hdwallet-xdefi": "1.53.1",
"@shapeshiftoss/hdwallet-coinbase": "1.53.3",
"@shapeshiftoss/hdwallet-core": "1.53.3",
"@shapeshiftoss/hdwallet-keepkey": "1.53.3",
"@shapeshiftoss/hdwallet-keepkey-webusb": "1.53.3",
"@shapeshiftoss/hdwallet-keplr": "1.53.3",
"@shapeshiftoss/hdwallet-ledger": "1.53.3",
"@shapeshiftoss/hdwallet-ledger-webusb": "1.53.3",
"@shapeshiftoss/hdwallet-metamask": "1.53.3",
"@shapeshiftoss/hdwallet-native": "1.53.3",
"@shapeshiftoss/hdwallet-native-vault": "1.53.3",
"@shapeshiftoss/hdwallet-shapeshift-multichain": "1.53.3",
"@shapeshiftoss/hdwallet-walletconnectv2": "1.53.3",
"@shapeshiftoss/hdwallet-xdefi": "1.53.3",
"@shapeshiftoss/swapper": "workspace:^",
"@shapeshiftoss/types": "workspace:^",
"@shapeshiftoss/unchained-client": "workspace:^",
Expand Down
10 changes: 8 additions & 2 deletions src/components/Modals/Send/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { utxoChainIds } from '@shapeshiftoss/chain-adapters'
import type { HDWallet } from '@shapeshiftoss/hdwallet-core'
import { supportsETH } from '@shapeshiftoss/hdwallet-core'
import type { KnownChainIds } from '@shapeshiftoss/types'
import { checkIsMetaMask, checkIsSnapInstalled } from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import {
checkIsMetaMask,
checkIsMetaMaskImpersonator,
checkIsSnapInstalled,
} from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import { bn, bnOrZero } from 'lib/bignumber/bignumber'
import { assertGetChainAdapter, tokenOrUndefined } from 'lib/utils'
import { assertGetCosmosSdkChainAdapter } from 'lib/utils/cosmosSdk'
Expand Down Expand Up @@ -104,10 +108,12 @@ export const handleSend = async ({
const acccountMetadataFilter = { accountId: sendInput.accountId }
const accountMetadata = selectPortfolioAccountMetadataByAccountId(state, acccountMetadataFilter)
const isMetaMask = await checkIsMetaMask(wallet)
const isMetaMaskImpersonator = await checkIsMetaMaskImpersonator(wallet)
if (
fromChainId(asset.chainId).chainNamespace === CHAIN_NAMESPACE.CosmosSdk &&
!wallet.supportsOfflineSigning() &&
(!isMetaMask || (isMetaMask && !(await checkIsSnapInstalled())))
// MM impersonators don't support Cosmos SDK chains
(!isMetaMask || isMetaMaskImpersonator || (isMetaMask && !(await checkIsSnapInstalled())))
) {
throw new Error(`unsupported wallet: ${await wallet.getModel()}`)
}
Expand Down
9 changes: 6 additions & 3 deletions src/context/WalletProvider/MetaMask/components/Connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useLocalWallet } from 'context/WalletProvider/local-wallet'
import { useFeatureFlag } from 'hooks/useFeatureFlag/useFeatureFlag'
import {
checkIsMetaMask,
checkIsMetaMaskImpersonator,
checkisMetaMaskMobileWebView,
checkIsSnapInstalled,
} from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
Expand Down Expand Up @@ -85,11 +86,13 @@ export const MetaMaskConnect = ({ history }: MetaMaskSetupProps) => {

await (async () => {
const isMetaMask = await checkIsMetaMask(wallet)
if (!isMetaMask) return dispatch({ type: WalletActions.SET_WALLET_MODAL, payload: false })
const isMetaMaskImpersonator = await checkIsMetaMaskImpersonator(wallet)
// Wallets other than MM desktop - including MM impersonators - don't support MM snaps
if (!isMetaMask || isMetaMaskImpersonator || isMetaMaskMobileWebView)
return dispatch({ type: WalletActions.SET_WALLET_MODAL, payload: false })
const isSnapInstalled = await checkIsSnapInstalled()

// We don't want to show the snaps modal on MM mobile browser, as snaps aren't supported on mobile
if (isSnapsEnabled && !isMetaMaskMobileWebView && !isSnapInstalled && showSnapModal) {
if (isSnapsEnabled && !isSnapInstalled && showSnapModal) {
return history.push('/metamask/snap/install')
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { MenuDivider, MenuItem, Skeleton, Tag } from '@chakra-ui/react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslate } from 'react-polyglot'
import { checkIsMetaMask, useIsSnapInstalled } from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import {
checkIsMetaMask,
checkIsMetaMaskImpersonator,
useIsSnapInstalled,
} from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import { useModal } from 'hooks/useModal/useModal'
import { useWallet } from 'hooks/useWallet/useWallet'

Expand All @@ -19,7 +23,8 @@ export const MetaMaskMenu = () => {
if (!wallet) return
;(async () => {
const _isMetaMask = await checkIsMetaMask(wallet)
setIsMetaMask(_isMetaMask)
const isMetaMaskImpersonator = await checkIsMetaMaskImpersonator(wallet)
setIsMetaMask(_isMetaMask && !isMetaMaskImpersonator)
})()
}, [wallet])

Expand Down
86 changes: 84 additions & 2 deletions src/hooks/useIsSnapInstalled/useIsSnapInstalled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,71 @@ import { useWallet } from 'hooks/useWallet/useWallet'
const POLL_INTERVAL = 3000 // tune me to make this "feel" right
const snapId = getConfig().REACT_APP_SNAP_ID

// https://github.com/wevm/wagmi/blob/21245be51d7c6dff1c7b285226d0c89c4a9d8cac/packages/connectors/src/utils/getInjectedName.ts#L6-L56
// This will need to be kept up-to-date with the latest list of impersonators
const METAMASK_IMPERSONATORS = [
'isBraveWallet',
'isTokenary',
'isFrame',
'isLiquality',
'isOpera',
'isTally',
'isStatus',
'isXDEFI',
'isNifty',
'isRonin',
'isBinance',
'isCoinbase',
'isExodus',
'isPhantom',
'isGlow',
'isOneInch',
'isRabby',
'isTrezor',
'isLedger',
'isKeystone',
'isBitBox',
'isGridPlus',
'isJade',
'isPortis',
'isFortmatic',
'isTorus',
'isAuthereum',
'isWalletLink',
'isWalletConnect',
'isDapper',
'isBitski',
'isVenly',
'isSequence',
'isGamestop',
'isZerion',
'isDeBank',
'isKukai',
'isTemple',
'isSpire',
'isWallet',
'isCore',
'isAnchor',
'isWombat',
'isMathWallet',
'isMeetone',
'isHyperPay',
'isTokenPocket',
'isBitpie',
'isAToken',
'isOwnbit',
'isHbWallet',
'isMYKEY',
'isHuobiWallet',
'isEidoo',
'isTrust',
'isImToken',
'isONTO',
'isSafePal',
'isCoin98',
'isVision',
]

export const checkIsSnapInstalled = pDebounce.promise(
(): Promise<boolean | null> => shapeShiftSnapInstalled(snapId),
)
Expand All @@ -25,8 +90,6 @@ export const checkIsMetaMask = pMemoize(
const provider = (await detectEthereumProvider()) as providers.ExternalProvider
// MetaMask impersonators don't support the methods we need to check for snap installation, and will throw
if (!provider.isMetaMask) return false
// Some impersonators really like to make it difficult for us to detect *actual* MetaMask
if ((provider as any).isBraveWallet) return false

return true
},
Expand All @@ -35,6 +98,23 @@ export const checkIsMetaMask = pMemoize(
},
)

export const checkIsMetaMaskImpersonator = pMemoize(
async (wallet: HDWallet | null): Promise<boolean> => {
const isMetaMaskMultichainWallet = wallet instanceof MetaMaskShapeShiftMultiChainHDWallet
// We don't want to run this hook altogether if using any wallet other than MM
if (!isMetaMaskMultichainWallet) return false

const provider = (await detectEthereumProvider()) as providers.ExternalProvider
// Some impersonators really like to make it difficult for us to detect *actual* MetaMask
// Note, checking for the truthiness of the value isn't enough - some impersonators have the key present but undefined
// This is weird, but welcome to the world of web3
return METAMASK_IMPERSONATORS.some(impersonator => impersonator in provider)
},
{
cacheKey: ([_wallet]) => (_wallet as MetaMaskShapeShiftMultiChainHDWallet | null)?._isMetaMask,
},
)

// https://github.com/MetaMask/metamask-sdk/blob/6230d8394157f53f1b020ae44601a0a69edc6155/packages/sdk/src/Platform/PlatfformManager.ts#L102C30-L111
export const checkisMetaMaskMobileWebView = () => {
if (typeof window === 'undefined') {
Expand All @@ -56,6 +136,8 @@ export const useIsSnapInstalled = (): null | boolean => {
const checkSnapInstallation = useCallback(async () => {
if (!isConnected || isDemoWallet) return
const isMetaMask = await checkIsMetaMask(wallet)
const isMetaMaskImpersonator = await checkIsMetaMaskImpersonator(wallet)
if (isMetaMaskImpersonator) return
if (!isMetaMask) return

const _isSnapInstalled = await checkIsSnapInstalled()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
StakingAction,
} from 'plugins/cosmos/components/modals/Staking/StakingCommon'
import { getChainAdapterManager } from 'context/PluginProvider/chainAdapterSingleton'
import { checkIsMetaMask, checkIsSnapInstalled } from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import {
checkIsMetaMask,
checkIsMetaMaskImpersonator,
checkIsSnapInstalled,
} from 'hooks/useIsSnapInstalled/useIsSnapInstalled'
import { useWallet } from 'hooks/useWallet/useWallet'
import { SHAPESHIFT_COSMOS_VALIDATOR_ADDRESS } from 'state/slices/opportunitiesSlice/resolvers/cosmosSdk/constants'

Expand Down Expand Up @@ -43,9 +47,10 @@ export const useStakingAction = () => {
// Native and KeepKey hdwallets only support offline signing, not broadcasting signed TXs like e.g Metamask

const isMetaMask = await checkIsMetaMask(wallet)
const isMetaMaskImpersonator = await checkIsMetaMaskImpersonator(wallet)
if (
!wallet.supportsOfflineSigning() &&
(!isMetaMask || (isMetaMask && !(await checkIsSnapInstalled())))
(!isMetaMask || isMetaMaskImpersonator || (isMetaMask && !(await checkIsSnapInstalled())))
) {
throw new Error(`unsupported wallet: ${await wallet.getModel()}`)
}
Expand Down
Loading

0 comments on commit 97e2c34

Please sign in to comment.