fix: allow wallets w/o mainnet to connect to WCv2 (#6854)

* fix: re-initialize wc connector with active chain

* test(e2e): fix universal-search flake
This commit is contained in:
Zach Pomerantz 2023-06-30 13:28:48 -04:00 committed by GitHub
parent 1c4a383a49
commit e5591e8f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 63 additions and 49 deletions

@ -1,13 +1,7 @@
import { getTestSelector } from '../utils'
describe('Universal search bar', () => {
before(() => {
beforeEach(() => {
cy.visit('/')
cy.get('[data-cy="magnifying-icon"]')
.parent()
.then(($navIcon) => {
$navIcon.click()
})
cy.get('[data-cy="magnifying-icon"]').parent().eq(1).click()
})
it('should yield clickable result for regular token or nft collection search term', () => {
@ -19,20 +13,7 @@ describe('Universal search bar', () => {
.and('contain.text', '$')
.and('contain.text', '%')
cy.get('[data-cy="searchbar-token-row-UNI"]').first().click()
cy.get('div').contains('Uniswap').should('exist')
// Stats should have: TVL, 24H Volume, 52W low, 52W high.
cy.get(getTestSelector('token-details-stats')).should('exist')
cy.get(getTestSelector('token-details-stats')).within(() => {
cy.get('[data-cy="tvl"]').should('include.text', '$')
cy.get('[data-cy="volume-24h"]').should('include.text', '$')
cy.get('[data-cy="52w-low"]').should('include.text', '$')
cy.get('[data-cy="52w-high"]').should('include.text', '$')
})
// About section should have description of token.
cy.get(getTestSelector('token-details-about-section')).should('exist')
cy.contains('UNI is the governance token for Uniswap').should('exist')
cy.location('hash').should('equal', '#/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
})
it.skip('should show recent tokens and popular tokens with empty search term', () => {

@ -1,6 +1,7 @@
import { t, Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { useWeb3React } from '@web3-react/core'
import { useAccountDrawer } from 'components/AccountDrawer'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import Loader from 'components/Icons/LoadingSpinner'
@ -173,7 +174,8 @@ export default function Option({ connection }: OptionProps) {
const { activationState, tryActivation } = useActivationState()
const [wCPopoverOpen, setWCPopoverOpen] = useState(false)
const [accountDrawerOpen, toggleAccountDrawerOpen] = useAccountDrawer()
const activate = () => tryActivation(connection, toggleAccountDrawerOpen)
const { chainId } = useWeb3React()
const activate = () => tryActivation(connection, toggleAccountDrawerOpen, chainId)
useEffect(() => {
if (!accountDrawerOpen) setWCPopoverOpen(false)

@ -10,7 +10,7 @@ import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/tra
import useEagerlyConnect from 'hooks/useEagerlyConnect'
import useOrderedConnections from 'hooks/useOrderedConnections'
import usePrevious from 'hooks/usePrevious'
import { ReactNode, useEffect, useMemo } from 'react'
import { ReactNode, useEffect, useMemo, useState } from 'react'
import { useLocation } from 'react-router-dom'
import { useConnectedWallets } from 'state/wallets/hooks'
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
@ -20,7 +20,13 @@ export default function Web3Provider({ children }: { children: ReactNode }) {
const connections = useOrderedConnections()
const connectors: [Connector, Web3ReactHooks][] = connections.map(({ hooks, connector }) => [connector, hooks])
const key = useMemo(() => connections.map((connection) => connection.getName()).join('-'), [connections])
// Force a re-render when our connection state changes.
const [index, setIndex] = useState(0)
useEffect(() => setIndex((index) => index + 1), [connections])
const key = useMemo(
() => connections.map((connection) => connection.getName()).join('-') + index,
[connections, index]
)
return (
<Web3ReactProvider connectors={connectors} key={key}>

@ -15,22 +15,21 @@ const RPC_URLS_WITHOUT_FALLBACKS = Object.entries(RPC_URLS).reduce(
}),
{}
)
const optionalChains = [...L1_CHAIN_IDS, ...L2_CHAIN_IDS].filter((x) => x !== SupportedChainId.MAINNET)
export class WalletConnectV2 extends WalletConnect {
ANALYTICS_EVENT = 'Wallet Connect QR Scan'
constructor({
actions,
onError,
defaultChainId,
qrcode = true,
}: Omit<WalletConnectConstructorArgs, 'options'> & { qrcode?: boolean }) {
onError,
}: Omit<WalletConnectConstructorArgs, 'options'> & { defaultChainId: number; qrcode?: boolean }) {
const darkmode = Boolean(window.matchMedia('(prefers-color-scheme: dark)'))
super({
actions,
options: {
projectId: process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID as string,
optionalChains,
chains: [SupportedChainId.MAINNET],
chains: [defaultChainId],
optionalChains: [...L1_CHAIN_IDS, ...L2_CHAIN_IDS],
showQrModal: qrcode,
rpcMap: RPC_URLS_WITHOUT_FALLBACKS,
// as of 6/16/2023 there are no docs for `optionalMethods`
@ -70,7 +69,7 @@ export class UniwalletConnect extends WalletConnectV2 {
constructor({ actions, onError }: Omit<WalletConnectConstructorArgs, 'options'>) {
// disables walletconnect's proprietary qr code modal; instead UniwalletModal will listen for events to trigger our custom modal
super({ actions, qrcode: false, onError })
super({ actions, defaultChainId: SupportedChainId.MAINNET, qrcode: false, onError })
this.events.once(URI_AVAILABLE, () => {
this.provider?.events.on('disconnect', this.deactivate)

@ -1,5 +1,8 @@
import { Web3ReactHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { SupportedChainId } from 'constants/chains'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { updateSelectedWallet } from 'state/user/reducer'
import { createDeferredPromise } from 'test-utils/promise'
import { act, renderHook } from '../test-utils/render'
@ -35,6 +38,7 @@ function createMockConnection(
hooks: {} as unknown as Web3ReactHooks,
type,
shouldDisplay: () => true,
overrideActivate: jest.fn(),
connector: new MockConnector(activate, deactivate),
}
}
@ -54,18 +58,25 @@ it('Should call activate function on a connection', async () => {
const activationResponse = createDeferredPromise()
const mockConnection = createMockConnection(jest.fn().mockImplementation(() => activationResponse.promise))
renderHook(() => useAppDispatch()(updateSelectedWallet({ wallet: ConnectionType.INJECTED })))
const initialSelectedWallet = renderHook(() => useAppSelector((state) => state.user.selectedWallet))
expect(initialSelectedWallet.result.current).toBeDefined()
const result = renderHook(useActivationState).result
const onSuccess = jest.fn()
let activationCall: Promise<void> = new Promise(jest.fn())
act(() => {
activationCall = result.current.tryActivation(mockConnection, onSuccess)
activationCall = result.current.tryActivation(mockConnection, onSuccess, SupportedChainId.OPTIMISM)
})
expect(result.current.activationState).toEqual({ status: ActivationStatus.PENDING, connection: mockConnection })
expect(mockConnection.overrideActivate).toHaveBeenCalledWith(SupportedChainId.OPTIMISM)
expect(mockConnection.connector.activate).toHaveBeenCalledTimes(1)
expect(console.debug).toHaveBeenLastCalledWith(`Connection activating: ${mockConnection.getName()}`)
expect(onSuccess).toHaveBeenCalledTimes(0)
const pendingSelectedWallet = renderHook(() => useAppSelector((state) => state.user.selectedWallet))
expect(pendingSelectedWallet.result.current).toBeUndefined()
await act(async () => {
activationResponse.resolve()
@ -77,6 +88,8 @@ it('Should call activate function on a connection', async () => {
expect(console.debug).toHaveBeenLastCalledWith(`Connection activated: ${mockConnection.getName()}`)
expect(console.debug).toHaveBeenCalledTimes(2)
expect(onSuccess).toHaveBeenCalledTimes(1)
const finalSelectedWallet = renderHook(() => useAppSelector((state) => state.user.selectedWallet))
expect(finalSelectedWallet.result.current).toBeDefined()
})
it('Should properly deactivate pending connection attempts', async () => {

@ -1,6 +1,7 @@
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
import { Connection } from 'connection/types'
import { SupportedChainId } from 'constants/chains'
import { atom } from 'jotai'
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useCallback } from 'react'
@ -31,15 +32,16 @@ function useTryActivation() {
const currentPage = getCurrentPageFromLocation(pathname)
return useCallback(
async (connection: Connection, onSuccess: () => void) => {
async (connection: Connection, onSuccess: () => void, chainId?: SupportedChainId) => {
// Skips wallet connection if the connection should override the default
// behavior, i.e. install MetaMask or launch Coinbase app
if (connection.overrideActivate?.()) return
if (connection.overrideActivate?.(chainId)) return
try {
setActivationState({ status: ActivationStatus.PENDING, connection })
console.debug(`Connection activating: ${connection.getName()}`)
dispatch(updateSelectedWallet({ wallet: undefined }))
await connection.connector.activate()
console.debug(`Connection activated: ${connection.getName()}`)

@ -3,7 +3,7 @@ import { initializeConnector } from '@web3-react/core'
import { GnosisSafe } from '@web3-react/gnosis-safe'
import { MetaMask } from '@web3-react/metamask'
import { Network } from '@web3-react/network'
import { Connector } from '@web3-react/types'
import { Actions, Connector } from '@web3-react/types'
import GNOSIS_ICON from 'assets/images/gnosis.png'
import UNISWAP_LOGO from 'assets/svg/logo.svg'
import COINBASE_ICON from 'assets/wallets/coinbase-icon.svg'
@ -82,17 +82,28 @@ export const walletConnectV1Connection: Connection = {
shouldDisplay: () => !getIsInjectedMobileBrowser(),
}
const [web3WalletConnectV2, web3WalletConnectV2Hooks] = initializeConnector<WalletConnectV2>(
(actions) => new WalletConnectV2({ actions, onError })
)
export const walletConnectV2Connection: Connection = {
getName: () => 'WalletConnect',
connector: web3WalletConnectV2,
hooks: web3WalletConnectV2Hooks,
type: ConnectionType.WALLET_CONNECT_V2,
getIcon: () => WALLET_CONNECT_ICON,
shouldDisplay: () => !getIsInjectedMobileBrowser(),
}
export const walletConnectV2Connection: Connection = new (class implements Connection {
private initializer = (actions: Actions, defaultChainId = SupportedChainId.MAINNET) =>
new WalletConnectV2({ actions, defaultChainId, onError })
type = ConnectionType.WALLET_CONNECT_V2
getName = () => 'WalletConnect'
getIcon = () => WALLET_CONNECT_ICON
shouldDisplay = () => !getIsInjectedMobileBrowser()
private _connector = initializeConnector<WalletConnectV2>(this.initializer)
overrideActivate = (chainId?: SupportedChainId) => {
// Always re-create the connector, so that the chainId is updated.
this._connector = initializeConnector((actions) => this.initializer(actions, chainId))
return false
}
get connector() {
return this._connector[0]
}
get hooks() {
return this._connector[1]
}
})()
const [web3UniwalletConnect, web3UniwalletConnectHooks] = initializeConnector<UniwalletConnect>(
(actions) => new UniwalletConnect({ actions, onError })
@ -133,7 +144,6 @@ const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<Coinba
onError,
})
)
const coinbaseWalletConnection: Connection = {
getName: () => 'Coinbase Wallet',
connector: web3CoinbaseWallet,

@ -1,5 +1,6 @@
import { Web3ReactHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { SupportedChainId } from 'constants/chains'
export enum ConnectionType {
UNISWAP_WALLET = 'UNISWAP_WALLET',
@ -21,6 +22,6 @@ export interface Connection {
type: ConnectionType
getIcon?(isDarkMode: boolean): string
shouldDisplay(): boolean
overrideActivate?: () => boolean
overrideActivate?: (chainId?: SupportedChainId) => boolean
isNew?: boolean
}