feat: gate v1 retirement (#6845)

* Revert "feat(wallet-connect): retire v1 (#6820)"

This reverts commit d6759b86e3702a6da3df211752c06e4ee119d801.

* gate v1/v2 switch with v2 fallback on wc entry

* fix tests

* add a second flag--isolate ... menu and default values from each other

* Update src/components/WalletModal/Option.tsx

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* Update src/featureFlags/flags/walletConnectPopover.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* pr feedback

* pr feedback

* pr nit

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
This commit is contained in:
Jordan Frankfurt 2023-06-26 14:59:13 -05:00 committed by GitHub
parent 469a006088
commit 1c4a383a49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 144 additions and 54 deletions

@ -10,6 +10,8 @@ import useMultiChainPositions from './useMultiChainPositions'
jest.mock('./useMultiChainPositions')
jest.spyOn(console, 'warn').mockImplementation()
const owner = '0xf5b6bb25f5beaea03dd014c6ef9fa9f3926bf36c'
const pool = new Pool(

@ -2,13 +2,16 @@ import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceElementName } from '@uniswap/analytics-events'
import { WalletConnect } from '@web3-react/walletconnect'
import { WalletConnect as WalletConnectv2 } from '@web3-react/walletconnect-v2'
import Column, { AutoColumn } from 'components/Column'
import Modal from 'components/Modal'
import { RowBetween } from 'components/Row'
import { uniwalletConnectConnection } from 'connection'
import { uniwalletConnectConnection, uniwalletWCV2ConnectConnection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { ConnectionType } from 'connection/types'
import { UniwalletConnect } from 'connection/WalletConnect'
import { UniwalletConnect as UniwalletConnectV2 } from 'connection/WalletConnectV2'
import { useWalletConnectV2AsDefault } from 'featureFlags/flags/walletConnectV2'
import { QRCodeSVG } from 'qrcode.react'
import { useEffect, useState } from 'react'
import styled, { useTheme } from 'styled-components/macro'
@ -42,19 +45,27 @@ export default function UniwalletModal() {
const [uri, setUri] = useState<string>()
// Displays the modal if a Uniswap Wallet Connection is pending & qrcode URI is available
const uniswapWalletConnectors = [ConnectionType.UNISWAP_WALLET, ConnectionType.UNISWAP_WALLET_V2]
const open =
activationState.status === ActivationStatus.PENDING &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET &&
uniswapWalletConnectors.includes(activationState.connection.type) &&
!!uri
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
useEffect(() => {
;(uniwalletConnectConnection.connector as WalletConnect).events.addListener(
UniwalletConnect.UNI_URI_AVAILABLE,
(uri) => {
if (walletConnectV2AsDefault) {
const connectorV2 = uniwalletWCV2ConnectConnection.connector as WalletConnectv2
connectorV2.events.addListener(UniwalletConnectV2.UNI_URI_AVAILABLE, (uri: string) => {
uri && setUri(uri)
}
)
}, [])
})
} else {
const connectorV1 = uniwalletConnectConnection.connector as WalletConnect
connectorV1.events.addListener(UniwalletConnect.UNI_URI_AVAILABLE, (uri: string) => {
uri && setUri(uri)
})
}
}, [walletConnectV2AsDefault])
useEffect(() => {
if (open) sendAnalyticsEvent('Uniswap wallet modal opened')

@ -3,6 +3,7 @@ import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetail
import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi'
import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc'
import { UnifiedRouterVariant, useRoutingAPIV2Flag } from 'featureFlags/flags/unifiedRouter'
import { useWalletConnectFallbackFlag } from 'featureFlags/flags/walletConnectPopover'
import { useWalletConnectV2Flag } from 'featureFlags/flags/walletConnectV2'
import { useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
@ -228,6 +229,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.walletConnectV2}
label="Uses WalletConnect V2 as default wallet connect connection"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useWalletConnectFallbackFlag()}
featureFlag={FeatureFlag.walletConnectFallback}
label="Adds a ... menu to the connection option"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}

@ -15,14 +15,14 @@ describe('StatusIcon', () => {
describe('with no account', () => {
it('renders children in correct order', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} />)
expect(component.getByTestId('StatusIconRoot')).toMatchSnapshot()
})
it('renders without mini icons', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} showMiniIcons={false} />)
expect(component.getByTestId('StatusIconRoot').children.length).toEqual(0)
})
@ -38,14 +38,14 @@ describe('StatusIcon', () => {
it('renders children in correct order', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} />)
expect(component.getByTestId('StatusIconRoot')).toMatchSnapshot()
})
it('renders without mini icons', () => {
const supportedConnections = getConnections()
const injectedConnection = supportedConnections[1]
const injectedConnection = supportedConnections[2]
const component = render(<StatusIcon account={ACCOUNT} connection={injectedConnection} showMiniIcons={false} />)
expect(component.getByTestId('StatusIconRoot').children.length).toEqual(0)
})

@ -7,6 +7,7 @@ import Loader from 'components/Icons/LoadingSpinner'
import { walletConnectV1Connection, walletConnectV2Connection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { Connection, ConnectionType } from 'connection/types'
import { useWalletConnectFallback } from 'featureFlags/flags/walletConnectPopover'
import { useWalletConnectV2AsDefault } from 'featureFlags/flags/walletConnectV2'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { MouseEvent, useEffect, useRef, useState } from 'react'
@ -65,7 +66,7 @@ const IconWrapper = styled.div`
align-items: flex-end;
`};
`
const WCv1PopoverContent = styled(ThemeButton)`
const PopoverContent = styled(ThemeButton)`
background: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
@ -79,7 +80,7 @@ const WCv1PopoverContent = styled(ThemeButton)`
z-index: ${Z_INDEX.popover};
`
const TOGGLE_SIZE = 24
const WCv1PopoverToggle = styled.button`
const FallbackPopoverToggle = styled.button`
align-items: center;
background-color: transparent;
border: none;
@ -123,11 +124,11 @@ const WCv1Icon = styled.img`
height: 20px !important;
width: 20px !important;
`
const WCv1BodyText = styled(ThemedText.BodyPrimary)`
const PopoverBodyText = styled(ThemedText.BodyPrimary)`
margin-bottom: 4px !important;
text-align: left;
`
const WCv1Caption = styled(ThemedText.Caption)`
const PopoverCaption = styled(ThemedText.Caption)`
text-align: left;
`
@ -140,27 +141,28 @@ interface PopupButtonContentProps {
}
function PopupButtonContent({ connection, isDarkMode, show, onClick, onClose }: PopupButtonContentProps) {
const popoverElement = useRef<HTMLButtonElement>(null)
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
useOnClickOutside(popoverElement, onClose)
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
if (!show) return null
return (
<WCv1PopoverContent onClick={onClick} ref={popoverElement} size={ButtonSize.small} emphasis={ButtonEmphasis.medium}>
<PopoverContent onClick={onClick} ref={popoverElement} size={ButtonSize.small} emphasis={ButtonEmphasis.medium}>
<IconWrapper>
<WCv1Icon src={connection.getIcon?.(isDarkMode)} alt={connection.getName()} />
</IconWrapper>
<div>
<WCv1BodyText>
<PopoverBodyText>
<Trans>Connect with {walletConnectV2AsDefault ? t`v1` : t`v2`}</Trans>
</WCv1BodyText>
<WCv1Caption color="textSecondary">
</PopoverBodyText>
<PopoverCaption color="textSecondary">
{walletConnectV2AsDefault
? t`Support for v1 will be discontinued June 28.`
: t`Under development and unsupported by most wallets`}
</WCv1Caption>
</PopoverCaption>
</div>
</WCv1PopoverContent>
</PopoverContent>
)
}
@ -169,12 +171,12 @@ interface OptionProps {
}
export default function Option({ connection }: OptionProps) {
const { activationState, tryActivation } = useActivationState()
const [WC1PromptOpen, setWC1PromptOpen] = useState(false)
const [wCPopoverOpen, setWCPopoverOpen] = useState(false)
const [accountDrawerOpen, toggleAccountDrawerOpen] = useAccountDrawer()
const activate = () => tryActivation(connection, toggleAccountDrawerOpen)
useEffect(() => {
if (!accountDrawerOpen) setWC1PromptOpen(false)
if (!accountDrawerOpen) setWCPopoverOpen(false)
}, [accountDrawerOpen])
const isSomeOptionPending = activationState.status === ActivationStatus.PENDING
@ -182,22 +184,25 @@ export default function Option({ connection }: OptionProps) {
const isDarkMode = useIsDarkMode()
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
const shouldUseWalletConnectFallback = useWalletConnectFallback()
const handleClickConnectViaPopover = (e: MouseEvent<HTMLButtonElement>) => {
const connector = walletConnectV2AsDefault ? walletConnectV1Connection : walletConnectV2Connection
const handleClickConnectViaWCv1 = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
tryActivation(walletConnectV2AsDefault ? walletConnectV1Connection : walletConnectV2Connection, () => {
setWC1PromptOpen(false)
tryActivation(connector, () => {
setWCPopoverOpen(false)
toggleAccountDrawerOpen()
})
}
const handleClickOpenWCv1Tooltip = (e: MouseEvent<HTMLButtonElement>) => {
const handleClickOpenPopover = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
setWC1PromptOpen(true)
setWCPopoverOpen(true)
}
const isWalletConnect =
connection.type === ConnectionType.WALLET_CONNECT || connection.type === ConnectionType.WALLET_CONNECT_V2
const showExtraMenuToggle = isWalletConnect && !isCurrentOptionPending
const showExtraMenuToggle = isWalletConnect && !isCurrentOptionPending && shouldUseWalletConnectFallback
return (
<Wrapper disabled={isSomeOptionPending}>
@ -226,15 +231,15 @@ export default function Option({ connection }: OptionProps) {
{showExtraMenuToggle && (
<>
<WCv1PopoverToggle onClick={handleClickOpenWCv1Tooltip} onMouseDown={handleClickOpenWCv1Tooltip}>
<FallbackPopoverToggle onClick={handleClickOpenPopover} onMouseDown={handleClickOpenPopover}>
<MoreHorizontal />
</WCv1PopoverToggle>
</FallbackPopoverToggle>
<PopupButtonContent
connection={connection}
isDarkMode={isDarkMode}
show={WC1PromptOpen}
onClick={handleClickConnectViaWCv1}
onClose={() => setWC1PromptOpen(false)}
show={wCPopoverOpen}
onClick={handleClickConnectViaPopover}
onClose={() => setWCPopoverOpen(false)}
/>
</>
)}

@ -47,9 +47,10 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
const { activationState } = useActivationState()
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
const hiddenWalletConnectType = walletConnectV2AsDefault
? ConnectionType.WALLET_CONNECT
: ConnectionType.WALLET_CONNECT_V2
const hiddenWalletConnectTypes = [
walletConnectV2AsDefault ? ConnectionType.WALLET_CONNECT : ConnectionType.WALLET_CONNECT_V2,
walletConnectV2AsDefault ? ConnectionType.UNISWAP_WALLET : ConnectionType.UNISWAP_WALLET_V2,
]
// Keep the network connector in sync with any active user connector to prevent chain-switching on wallet disconnection.
useEffect(() => {
@ -70,7 +71,7 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
<AutoColumn gap="16px">
<OptionGrid data-testid="option-grid">
{connections
.filter((connection) => connection.shouldDisplay() && connection.type !== hiddenWalletConnectType)
.filter((connection) => connection.shouldDisplay() && !hiddenWalletConnectTypes.includes(connection.type))
.map((connection) => (
<Option key={connection.getName()} connection={connection} />
))}

@ -1,7 +1,8 @@
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { WalletConnect, WalletConnectConstructorArgs } from '@web3-react/walletconnect-v2'
import { URI_AVAILABLE, WalletConnect, WalletConnectConstructorArgs } from '@web3-react/walletconnect-v2'
import { L1_CHAIN_IDS, L2_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { Z_INDEX } from 'theme/zIndex'
import { isIOS } from 'utils/userAgent'
import { RPC_URLS } from '../constants/networks'
@ -61,3 +62,37 @@ export class WalletConnectV2 extends WalletConnect {
return super.activate(chainId)
}
}
// Custom class for Uniswap Wallet specific functionality
export class UniwalletConnect extends WalletConnectV2 {
ANALYTICS_EVENT = 'Uniswap Wallet QR Scan'
static UNI_URI_AVAILABLE = 'uni_uri_available'
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 })
this.events.once(URI_AVAILABLE, () => {
this.provider?.events.on('disconnect', this.deactivate)
})
this.events.on(URI_AVAILABLE, (uri) => {
if (!uri) return
// Emits custom wallet connect code, parseable by the Uniswap Wallet
this.events.emit(UniwalletConnect.UNI_URI_AVAILABLE, `hello_uniwallet:${uri}`)
// Opens deeplink to Uniswap Wallet if on iOS
if (isIOS) {
const newTab = window.open(`https://uniswap.org/app/wc?uri=${encodeURIComponent(uri)}`)
// Fixes blank tab opening on mobile Chrome
newTab?.close()
}
})
}
deactivate() {
this.events.emit(URI_AVAILABLE)
return super.deactivate()
}
}

@ -39,7 +39,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Install MetaMask')
expect(injected.overrideActivate?.()).toBeTruthy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('MetaMask-Injected Desktop', async () => {
@ -49,7 +49,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('MetaMask')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Coinbase-Injected Desktop', async () => {
@ -60,7 +60,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Install MetaMask')
expect(injected.overrideActivate?.()).toBeTruthy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Coinbase and MetaMask Injected Desktop', async () => {
@ -71,7 +71,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('MetaMask')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Trust Wallet Injected Desktop', async () => {
@ -81,7 +81,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Trust Wallet')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Rabby Wallet Injected Desktop', async () => {
@ -91,7 +91,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Rabby')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('LedgerConnect Wallet Injected Desktop', async () => {
@ -101,7 +101,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Ledger')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Brave Browser Wallet Injected Desktop', async () => {
@ -111,7 +111,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Brave')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Phantom Wallet Injected Desktop', async () => {
@ -122,7 +122,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('Phantom')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
const UNKNOWN_MM_INJECTOR = { isRandomWallet: true, isMetaMask: true } as Window['window']['ethereum']
@ -133,7 +133,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getName()).toBe('MetaMask')
expect(injected.overrideActivate?.()).toBeFalsy()
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
const UNKNOWN_INJECTOR = { isRandomWallet: true } as Window['window']['ethereum']
@ -148,7 +148,7 @@ describe('connection utility/metadata tests', () => {
expect(injected.getIcon?.(/* isDarkMode= */ true)).toBe(INJECTED_DARK_ICON)
// Ensures we provide multiple connection options if in an unknown injected browser
expect(displayed.length).toEqual(4)
expect(displayed.length).toEqual(5)
})
it('Generic Wallet Browser with delayed injection', async () => {
@ -191,6 +191,6 @@ describe('connection utility/metadata tests', () => {
// Expect coinbase option to launch coinbase app in a regular mobile browser
expect(coinbase.overrideActivate?.()).toBeTruthy()
expect(displayed.length).toEqual(3)
expect(displayed.length).toEqual(4)
})
})

@ -17,7 +17,7 @@ import { RPC_PROVIDERS } from '../constants/providers'
import { Connection, ConnectionType } from './types'
import { getInjection, getIsCoinbaseWallet, getIsInjected, getIsMetaMaskWallet } from './utils'
import { UniwalletConnect, WalletConnectV1 } from './WalletConnect'
import { WalletConnectV2 } from './WalletConnectV2'
import { UniwalletConnect as UniwalletWCV2Connect, WalletConnectV2 } from './WalletConnectV2'
function onError(error: Error) {
console.debug(`web3-react error: ${error}`)
@ -107,6 +107,19 @@ export const uniwalletConnectConnection: Connection = {
isNew: true,
}
const [web3WCV2UniwalletConnect, web3WCV2UniwalletConnectHooks] = initializeConnector<UniwalletWCV2Connect>(
(actions) => new UniwalletWCV2Connect({ actions, onError })
)
export const uniwalletWCV2ConnectConnection: Connection = {
getName: () => 'Uniswap Wallet',
connector: web3WCV2UniwalletConnect,
hooks: web3WCV2UniwalletConnectHooks,
type: ConnectionType.UNISWAP_WALLET_V2,
getIcon: () => UNIWALLET_ICON,
shouldDisplay: () => Boolean(!getIsInjectedMobileBrowser() && !isNonIOSPhone),
isNew: true,
}
const [web3CoinbaseWallet, web3CoinbaseWalletHooks] = initializeConnector<CoinbaseWallet>(
(actions) =>
new CoinbaseWallet({
@ -142,6 +155,7 @@ const coinbaseWalletConnection: Connection = {
export function getConnections() {
return [
uniwalletConnectConnection,
uniwalletWCV2ConnectConnection,
injectedConnection,
walletConnectV2Connection,
walletConnectV1Connection,
@ -171,6 +185,8 @@ export function getConnection(c: Connector | ConnectionType) {
case ConnectionType.UNIWALLET:
case ConnectionType.UNISWAP_WALLET:
return uniwalletConnectConnection
case ConnectionType.UNISWAP_WALLET_V2:
return uniwalletWCV2ConnectConnection
case ConnectionType.NETWORK:
return networkConnection
case ConnectionType.GNOSIS_SAFE:

@ -3,6 +3,7 @@ import { Connector } from '@web3-react/types'
export enum ConnectionType {
UNISWAP_WALLET = 'UNISWAP_WALLET',
UNISWAP_WALLET_V2 = 'UNISWAP_WALLET_V2',
/** @deprecated - Use {@link UNISWAP_WALLET} instead. */
UNIWALLET = 'UNIWALLET',
INJECTED = 'INJECTED',

@ -0,0 +1,9 @@
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useWalletConnectFallbackFlag(): BaseVariant {
return useBaseFlag(FeatureFlag.walletConnectFallback)
}
export function useWalletConnectFallback(): boolean {
return useWalletConnectFallbackFlag() === BaseVariant.Enabled
}

@ -15,6 +15,7 @@ export enum FeatureFlag {
nativeUsdcArbitrum = 'web_usdc_arbitrum',
routingAPIPrice = 'routing_api_price',
walletConnectV2 = 'walletconnect_v2',
walletConnectFallback = 'walletconnect_fallback',
}
interface FeatureFlagsContextType {

@ -2,6 +2,7 @@ import { Connector } from '@web3-react/types'
import {
networkConnection,
uniwalletConnectConnection,
uniwalletWCV2ConnectConnection,
walletConnectV1Connection,
walletConnectV2Connection,
} from 'connection'
@ -41,6 +42,7 @@ export function useSwitchChain() {
walletConnectV1Connection.connector,
walletConnectV2Connection.connector,
uniwalletConnectConnection.connector,
uniwalletWCV2ConnectConnection.connector,
networkConnection.connector,
].includes(connector)
) {