diff --git a/.prettierrc b/.prettierrc index b924090dba..e11ba81fdd 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,6 +3,5 @@ "printWidth": 120, "trailingComma": "all", "singleQuote": true, - "semi": false, - "endOfLine": "auto" + "semi": false } diff --git a/package.json b/package.json index 344328557e..bdb3fcade2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "generate-types:erc721": "cross-env typechain --target=web3-v1 --out-dir './src/types/contracts' './node_modules/@openzeppelin/contracts/build/contracts/ERC721.json'", "generate-types:spendingLimit": "cross-env typechain --target=web3-v1 --out-dir './src/types/contracts' ./node_modules/@gnosis.pm/safe-modules-deployments/dist/assets/**/*.json", "generate-types:safeDeployments": "cross-env typechain --target=web3-v1 --out-dir './src/types/contracts' ./node_modules/@gnosis.pm/safe-deployments/dist/assets/**/*.json", - "lint:check": "cross-env eslint './src/**/*.{js,jsx,ts,tsx}'", + "lint:check": "eslint './src/**/*.{js,jsx,ts,tsx}'", "lint:fix": "yarn lint:check --fix", "postinstall": "patch-package && yarn generate-types", "prettier:check": "yarn prettier --check", @@ -94,7 +94,7 @@ "abi-decoder": "^2.4.0", "axios": "0.21.4", "bignumber.js": "9.0.1", - "bnc-onboard": "^1.38.2", + "bnc-onboard": "^1.37.3", "classnames": "^2.2.6", "currency-flags": "3.2.1", "date-fns": "^2.20.2", diff --git a/src/components/AppLayout/Header/components/ProviderDetails/PairingDetails.tsx b/src/components/AppLayout/Header/components/ProviderDetails/PairingDetails.tsx index 68bc651569..3b3142693f 100644 --- a/src/components/AppLayout/Header/components/ProviderDetails/PairingDetails.tsx +++ b/src/components/AppLayout/Header/components/ProviderDetails/PairingDetails.tsx @@ -1,4 +1,7 @@ -import { ReactElement } from 'react' +import { CSSProperties, ReactElement } from 'react' +import Skeleton from '@material-ui/lab/Skeleton' +import RefreshIcon from '@material-ui/icons/Refresh' +import IconButton from '@material-ui/core/IconButton' import { Divider, Link } from '@gnosis.pm/safe-react-components' import styled from 'styled-components' import QRCode from 'qrcode.react' @@ -6,6 +9,8 @@ import QRCode from 'qrcode.react' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import usePairing from 'src/logic/wallets/pairing/hooks/usePairing' +import { initPairing, isPairingModule } from 'src/logic/wallets/pairing/utils' +import { useGetPairingUri } from 'src/logic/wallets/pairing/hooks/useGetPairingUri' import { OVERVIEW_EVENTS } from 'src/utils/events/overview' import Track from 'src/components/Track' import AppstoreButton from 'src/components/AppstoreButton' @@ -17,8 +22,15 @@ const StyledDivider = styled(Divider)` const QR_DIMENSION = 120 +const qrRefresh: CSSProperties = { + width: QR_DIMENSION, + height: QR_DIMENSION, +} + const PairingDetails = ({ classes }: { classes: Record }): ReactElement => { - const { uri } = usePairing() + const uri = useGetPairingUri() + const isPairingLoaded = isPairingModule() + usePairing() return ( <> @@ -31,7 +43,15 @@ const PairingDetails = ({ classes }: { classes: Record }): React - + {uri ? ( + + ) : isPairingLoaded ? ( + + ) : ( + + + + )} diff --git a/src/logic/wallets/pairing/hooks/useGetPairingUri.ts b/src/logic/wallets/pairing/hooks/useGetPairingUri.ts new file mode 100644 index 0000000000..a515089841 --- /dev/null +++ b/src/logic/wallets/pairing/hooks/useGetPairingUri.ts @@ -0,0 +1,15 @@ +import { getPairingUri } from 'src/logic/wallets/pairing/utils' +import { useEffect, useState } from 'react' + +export const useGetPairingUri = (): string | undefined => { + const onboardUri = getPairingUri() + const [uri, setUri] = useState() + + useEffect(() => { + setTimeout(() => { + setUri(getPairingUri()) + }, 100) + }, [onboardUri]) + + return uri +} diff --git a/src/logic/wallets/pairing/hooks/usePairing.ts b/src/logic/wallets/pairing/hooks/usePairing.ts index 20a6a6cb19..d18b95a2da 100644 --- a/src/logic/wallets/pairing/hooks/usePairing.ts +++ b/src/logic/wallets/pairing/hooks/usePairing.ts @@ -1,68 +1,13 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import { IEventEmitter } from '@walletconnect/types' +import { useEffect } from 'react' -import { getPairingUri } from 'src/logic/wallets/pairing/utils' -import onboard from 'src/logic/wallets/onboard' -import { getPairingProvider, PAIRING_MODULE_NAME } from 'src/logic/wallets/pairing/module' - -let defaultEvents: IEventEmitter[] - -// These handful of events relate to the session lifecycle -enum WC_EVENT { - CONNECT = 'connect', - DISCONNECT = 'disconnect', - DISPLAY_URI = 'display_uri', - SESSION_UPDATE = 'wc_sessionUpdate', -} - -const usePairing = (): { uri: string } => { - const provider = useMemo(getPairingProvider, []) - - const [uri, setUri] = useState(getPairingUri(provider.wc.uri)) - - // Pairing session lifecycle - const createPairingSession = useCallback(() => { - if (!provider.wc.connected) { - provider.enable() - provider.wc.createSession() - } - }, [provider]) +import { initPairing, isPairingConnected } from 'src/logic/wallets/pairing/utils' +const usePairing = (): void => { useEffect(() => { - if (!defaultEvents) { - defaultEvents = (provider.wc as any)._eventManager._eventEmitters as IEventEmitter[] + if (!isPairingConnected()) { + initPairing() } - - // Resest event listeners to default - ;(provider.wc as any)._eventManager._eventEmitters = defaultEvents - - // Watch for URI change - Object.values(WC_EVENT).forEach((event) => - provider.wc.on(event, () => { - const pairingUri = getPairingUri(provider.wc.uri) - setUri(pairingUri) - }), - ) - - // Watch for connection - provider.wc.on(WC_EVENT.CONNECT, () => { - onboard().walletSelect(PAIRING_MODULE_NAME) - }) - - // Handle disconnection (`walletReset()` occurs inside pairing module) - provider.wc.on(WC_EVENT.DISCONNECT, createPairingSession) - provider.wc.on(WC_EVENT.SESSION_UPDATE, (_, { params }) => { - const didRevokeSession = params[0]?.approved === false - if (didRevokeSession) { - createPairingSession() - } - }) - - // Create pairing session if one isn't already active - createPairingSession() - }, [createPairingSession, provider.wc]) - - return { uri } + }, []) } export default usePairing diff --git a/src/logic/wallets/pairing/module.ts b/src/logic/wallets/pairing/module.ts index a91c7d4759..16d1020145 100644 --- a/src/logic/wallets/pairing/module.ts +++ b/src/logic/wallets/pairing/module.ts @@ -1,11 +1,10 @@ -import WalletConnectProvider from '@walletconnect/web3-provider' import { IClientMeta } from '@walletconnect/types' import { WalletModule } from 'bnc-onboard/dist/src/interfaces' import UAParser from 'ua-parser-js' import { APP_VERSION, PUBLIC_URL } from 'src/utils/constants' +import { ChainId } from 'src/config/chain' import { getWCWalletInterface, getWalletConnectProvider } from 'src/logic/wallets/walletConnect/utils' -import { _getChainId } from 'src/config' // Modified version of the built in WC module in Onboard v1.35.5 // https://github.com/blocknative/onboard/blob/release/1.35.5/src/modules/select/wallets/wallet-connect.ts @@ -34,59 +33,40 @@ const getClientMeta = (): IClientMeta => { } } -const createPairingProvider = (): WalletConnectProvider => { +// Note: this shares a lot of similarities with the patchedWalletConnect module +const getPairingModule = (chainId: ChainId): WalletModule => { const STORAGE_ID = 'SAFE__pairingProvider' const clientMeta = getClientMeta() - // Successful pairing does not use chainId of provider but that of the pairee - // so we can use any chainId here - const provider = getWalletConnectProvider(_getChainId(), { - storageId: STORAGE_ID, - qrcode: false, // Don't show QR modal - clientMeta, - }) - - // WalletConnect overrides the clientMeta, so we need to set it back - ;(provider.wc as any).clientMeta = clientMeta - ;(provider.wc as any)._clientMeta = clientMeta - - return provider -} - -let _pairingProvider: WalletConnectProvider | undefined - -export const getPairingProvider = (): WalletConnectProvider => { - // We cannot initialize provider immediately as we need to wait for chains to load RPCs - if (!_pairingProvider) { - _pairingProvider = createPairingProvider() - } - return _pairingProvider -} - -// Note: this shares a lot of similarities with the patchedWalletConnect module -const getPairingModule = (): WalletModule => { - const name = PAIRING_MODULE_NAME - const provider = getPairingProvider() - return { - name, + name: PAIRING_MODULE_NAME, wallet: async ({ resetWalletState }) => { - // Enable provider when a previously interrupted session exists - if (provider.wc.session.connected) { - provider.enable() + const provider = getWalletConnectProvider(chainId, { + storageId: STORAGE_ID, + qrcode: false, // Don't show QR modal + clientMeta, + }) + + // WalletConnect overrides the clientMeta, so we need to set it back + ;(provider.wc as any).clientMeta = clientMeta + ;(provider.wc as any)._clientMeta = clientMeta + + const onDisconnect = () => { + resetWalletState({ disconnected: true, walletName: PAIRING_MODULE_NAME }) } - const onDisconnect = () => resetWalletState({ walletName: name, disconnected: true }) provider.wc.on('disconnect', onDisconnect) - // Kill session if module unmounts (a non-pairing wallet connects) window.addEventListener('unload', onDisconnect, { once: true }) + // Establish WC connection + provider.enable() + return { provider, interface: { ...getWCWalletInterface(provider), - name, + name: PAIRING_MODULE_NAME, }, } }, diff --git a/src/logic/wallets/pairing/utils.ts b/src/logic/wallets/pairing/utils.ts index 2ce675067a..7b1d03a8b4 100644 --- a/src/logic/wallets/pairing/utils.ts +++ b/src/logic/wallets/pairing/utils.ts @@ -5,6 +5,15 @@ import { PAIRING_MODULE_NAME } from 'src/logic/wallets/pairing/module' import { WALLETS } from 'src/config/chain.d' import onboard from 'src/logic/wallets/onboard' +export const initPairing = async (): Promise => { + await onboard().walletSelect(PAIRING_MODULE_NAME) +} + +// Is WC connected (may work for other providers) +export const isPairingConnected = (): boolean => { + return onboard().getState().wallet.provider?.connected +} + export const isPairingSupported = (): boolean => { return !getDisabledWallets().includes(WALLETS.SAFE_MOBILE) } @@ -14,7 +23,8 @@ export const isPairingModule = (name: Wallet['name'] = onboard().getState().wall return name === PAIRING_MODULE_NAME } -export const getPairingUri = (wcUri: string): string => { +export const getPairingUri = (): string | undefined => { + const wcUri = onboard().getState().wallet.provider?.wc?.uri const PAIRING_MODULE_URI_PREFIX = 'safe-' - return wcUri ? `${PAIRING_MODULE_URI_PREFIX}${wcUri}` : '' + return wcUri ? `${PAIRING_MODULE_URI_PREFIX}${wcUri}` : undefined } diff --git a/src/logic/wallets/patchedWalletConnect.ts b/src/logic/wallets/patchedWalletConnect.ts index 146dc1dbbb..2982319334 100644 --- a/src/logic/wallets/patchedWalletConnect.ts +++ b/src/logic/wallets/patchedWalletConnect.ts @@ -4,9 +4,12 @@ import { WalletModule, Helpers } from 'bnc-onboard/dist/src/interfaces' import { getRpcServiceUrl } from 'src/config' import { getChains } from 'src/config/cache/chains' -import { INFURA_TOKEN, WC_BRIDGE } from 'src/utils/constants' +import { INFURA_TOKEN } from 'src/utils/constants' import { ChainId } from 'src/config/chain' +// TODO: When desktop pairing is merged, import these into there +export const WC_BRIDGE = 'https://safe-walletconnect.gnosis.io/' + // Modified version of the built in WC module in Onboard v1.35.5, including: // https://github.com/blocknative/onboard/blob/release/1.35.5/src/modules/select/wallets/wallet-connect.ts diff --git a/src/logic/wallets/utils/walletList.ts b/src/logic/wallets/utils/walletList.ts index e98577ddc4..76d8005e9b 100644 --- a/src/logic/wallets/utils/walletList.ts +++ b/src/logic/wallets/utils/walletList.ts @@ -93,5 +93,5 @@ export const getSupportedWallets = (chainId: ChainId): WalletSelectModuleOptions } // Pairing must be 1st in list (to hide via CSS) - return isPairingSupported() ? [getPairingModule(), ...supportedWallets] : supportedWallets + return isPairingSupported() ? [getPairingModule(chainId), ...supportedWallets] : supportedWallets } diff --git a/src/logic/wallets/walletConnect/utils.ts b/src/logic/wallets/walletConnect/utils.ts index 8924da7885..d2e889d3e4 100644 --- a/src/logic/wallets/walletConnect/utils.ts +++ b/src/logic/wallets/walletConnect/utils.ts @@ -5,11 +5,12 @@ import { WalletInterface } from 'bnc-onboard/dist/src/interfaces' import { getRpcServiceUrl } from 'src/config' import { getChains } from 'src/config/cache/chains' import { ChainId } from 'src/config/chain' -import { INFURA_TOKEN, WC_BRIDGE } from 'src/utils/constants' +import { INFURA_TOKEN } from 'src/utils/constants' type Options = Omit export const getWalletConnectProvider = (chainId: ChainId, options: Options = {}): WalletConnectProvider => { + const WC_BRIDGE = 'https://safe-walletconnect.gnosis.io/' // Prevent `eth_getBlockByNumber` polling every 4 seconds // https://github.com/WalletConnect/walletconnect-monorepo/issues/357#issuecomment-789663540 const POLLING_INTERVAL = 60_000 * 60 // 1 hour diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 14f20d7ea9..ff34868b44 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -23,8 +23,6 @@ export const ETHERSCAN_API_KEY = process.env.REACT_APP_ETHERSCAN_API_KEY || '' export const ETHGASSTATION_API_KEY = process.env.REACT_APP_ETHGASSTATION_API_KEY export const IPFS_GATEWAY = process.env.REACT_APP_IPFS_GATEWAY -export const WC_BRIDGE = 'https://safe-walletconnect.gnosis.io/' - // Google Tag Manager export const GOOGLE_TAG_MANAGER_ID = process.env.REACT_APP_GOOGLE_TAG_MANAGER_ID || '' export const GOOGLE_TAG_MANAGER_AUTH_LIVE = process.env.REACT_APP_GOOGLE_TAG_MANAGER_LIVE_AUTH || '' diff --git a/yarn.lock b/yarn.lock index d2fd49574e..9a5526da6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4976,10 +4976,10 @@ bnb-javascript-sdk-nobroadcast@^2.16.14: uuid "^3.3.2" websocket-stream "^5.5.0" -bnc-onboard@^1.38.2: - version "1.38.2" - resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.38.2.tgz#23aa7f10546e15d595b4420e5959732c9a485c71" - integrity sha512-ObRQaHV1HXXOVuUJMHVYhmsz8T804hl+cl47t12ZRVG3HtF8Pf/22jGjrG2mWkgdx4h3RVLvxOqW5V1nwHTdaw== +bnc-onboard@^1.37.3: + version "1.37.3" + resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.37.3.tgz#e4c3ae94ab44b3053a6dea07057447feb9b9da14" + integrity sha512-G5pHatWvJf/r7gB7A38p7BlQfbzNJhND2nr0tPKqGtpXucTZJ0cl0bPvrh2nRNMs22ajd2wG29N88bWrWU5Vcw== dependencies: "@cvbb/eth-keyring" "^1.1.0" "@ensdomains/ensjs" "^2.0.1" @@ -4999,7 +4999,7 @@ bnc-onboard@^1.38.2: "@walletconnect/web3-provider" "^1.7.1" authereum "^0.1.12" bignumber.js "^9.0.0" - bnc-sdk "^4.1.0" + bnc-sdk "^3.4.1" bowser "^2.10.0" eth-lattice-keyring "^0.2.7" eth-provider "^0.6.1" @@ -5010,16 +5010,15 @@ bnc-onboard@^1.38.2: hdkey "^2.0.1" regenerator-runtime "^0.13.7" trezor-connect "^8.1.9" - walletlink "^2.5.0" + walletlink "^2.4.6" web3-provider-engine "^15.0.4" -bnc-sdk@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/bnc-sdk/-/bnc-sdk-4.2.0.tgz#cabc70eaf4a49dd2f695c6fcee50c833aa2af6b2" - integrity sha512-uoe40jLL+eYxeCAOqLgWLOowU9kyUuXyLURVgSSfIBjUNuszlz+I2wZYtvdWF+YkYphSr7L3E6WFtmwFrne+Pw== +bnc-sdk@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/bnc-sdk/-/bnc-sdk-3.7.0.tgz#b6472067baad41e102c4fe748ba3cc32793fca9e" + integrity sha512-o8vd+LLL+sBU3aE+/tmhilbtkKQpGvA9rBhvT/bszvBpAgSNzVaP3vdmNPaglDmE2WymcrqvQGf57z5l9v+rrw== dependencies: crypto-es "^1.2.2" - nanoid "^3.3.1" rxjs "^6.6.3" sturdy-websocket "^0.1.12" @@ -12471,11 +12470,6 @@ nanoid@^3.1.30: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== -nanoid@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" - integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== - nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -17781,10 +17775,10 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.12" -walletlink@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/walletlink/-/walletlink-2.5.0.tgz#b8db10f4d9f124084feb16d1e2b2d08ba8c20d21" - integrity sha512-PBJmK5tZmonwKPABBI2/optaZ11O4kKmkmnU5eLKhk4XRlal5qJ1igZ4U5j3w6w8wxxdhCWpLMHzGWt3n/p7mw== +walletlink@^2.4.6: + version "2.4.7" + resolved "https://registry.yarnpkg.com/walletlink/-/walletlink-2.4.7.tgz#3dd034f7cd6e9d9f4cc1d677bb951869dc743e20" + integrity sha512-jhLVOMly9oWiSE8mZ4/+uMyVsAKHw71kGbgC1xYp50SQpuLT2pfa6Hiw2VQ0omP/WHsDAPFuBo8hJGxggr768w== dependencies: "@metamask/safe-event-emitter" "2.0.0" bind-decorator "^1.0.11"