Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Commit

Permalink
Revert "fix: decouple pairing provider (#3753)"
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh committed Apr 27, 2022
1 parent f9ad409 commit 781c87d
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 135 deletions.
3 changes: 1 addition & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true,
"semi": false,
"endOfLine": "auto"
"semi": false
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
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'

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'
Expand All @@ -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<string, string> }): ReactElement => {
const { uri } = usePairing()
const uri = useGetPairingUri()
const isPairingLoaded = isPairingModule()
usePairing()

return (
<>
Expand All @@ -31,7 +43,15 @@ const PairingDetails = ({ classes }: { classes: Record<string, string> }): React
</Row>

<Row className={classes.justifyCenter}>
<QRCode value={uri} size={QR_DIMENSION} />
{uri ? (
<QRCode value={uri} size={QR_DIMENSION} />
) : isPairingLoaded ? (
<Skeleton variant="rect" width={QR_DIMENSION} height={QR_DIMENSION} />
) : (
<IconButton disableRipple style={qrRefresh} onClick={initPairing}>
<RefreshIcon fontSize="large" />
</IconButton>
)}
</Row>

<Row>
Expand Down
15 changes: 15 additions & 0 deletions src/logic/wallets/pairing/hooks/useGetPairingUri.ts
Original file line number Diff line number Diff line change
@@ -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<string>()

useEffect(() => {
setTimeout(() => {
setUri(getPairingUri())
}, 100)
}, [onboardUri])

return uri
}
67 changes: 6 additions & 61 deletions src/logic/wallets/pairing/hooks/usePairing.ts
Original file line number Diff line number Diff line change
@@ -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<string>(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
60 changes: 20 additions & 40 deletions src/logic/wallets/pairing/module.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
},
}
},
Expand Down
14 changes: 12 additions & 2 deletions src/logic/wallets/pairing/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> => {
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)
}
Expand All @@ -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
}
5 changes: 4 additions & 1 deletion src/logic/wallets/patchedWalletConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/logic/wallets/utils/walletList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion src/logic/wallets/walletConnect/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IWalletConnectProviderOptions, 'bridge' | 'infuraId' | 'rpc' | 'chainId' | 'pollingInterval'>

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
Expand Down
2 changes: 0 additions & 2 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 || ''
Expand Down
Loading

0 comments on commit 781c87d

Please sign in to comment.