Commit d6759b86 authored by Jordan Frankfurt's avatar Jordan Frankfurt Committed by GitHub

feat(wallet-connect): retire v1 (#6820)

* feat(wallet-connect): retire v1

* fix unit test

* Update src/state/user/reducer.ts
Co-authored-by: default avatarcartcrom <39385577+cartcrom@users.noreply.github.com>

* useToggleAccountDrawer

* fix test

---------
Co-authored-by: default avatarcartcrom <39385577+cartcrom@users.noreply.github.com>
parent df554564
import { Trans } from '@lingui/macro'
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { InterfaceElementName } from '@uniswap/analytics-events'
import { WalletConnect } from '@web3-react/walletconnect'
import { WalletConnect } 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 { ActivationStatus, useActivationState } from 'connection/activate'
import { ConnectionType } from 'connection/types'
import { UniwalletConnect } from 'connection/WalletConnect'
import { UniwalletConnect } from 'connection/WalletConnectV2'
import { QRCodeSVG } from 'qrcode.react'
import { useEffect, useState } from 'react'
import styled, { useTheme } from 'styled-components/macro'
......
......@@ -3,7 +3,6 @@ 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 { useWalletConnectV2Flag } from 'featureFlags/flags/walletConnectV2'
import { useUpdateAtom } from 'jotai/utils'
import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react'
import { X } from 'react-feather'
......@@ -222,12 +221,6 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.routingAPIPrice}
label="Use the URA or routing-api for price fetches"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useWalletConnectV2Flag()}
featureFlag={FeatureFlag.walletConnectV2}
label="Uses WalletConnect V2 as default wallet connect connection"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}
......
......@@ -6,13 +6,7 @@ import { MouseoverTooltip } from 'components/Tooltip'
import { getConnection } from 'connection'
import { ConnectionType } from 'connection/types'
import { getChainInfo } from 'constants/chainInfo'
import {
L1_CHAIN_IDS,
L2_CHAIN_IDS,
SupportedChainId,
TESTNET_CHAIN_IDS,
UniWalletSupportedChains,
} from 'constants/chains'
import { L1_CHAIN_IDS, L2_CHAIN_IDS, SupportedChainId, TESTNET_CHAIN_IDS } from 'constants/chains'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
......@@ -43,9 +37,8 @@ function useWalletSupportedChains(): SupportedChainId[] {
switch (connectionType) {
case ConnectionType.WALLET_CONNECT_V2:
return getSupportedChainIdsFromWalletConnectSession((connector as WalletConnect).provider?.session)
case ConnectionType.UNISWAP_WALLET:
return UniWalletSupportedChains
return getSupportedChainIdsFromWalletConnectSession((connector as WalletConnect).provider?.session)
default:
return NETWORK_SELECTOR_CHAINS
}
......
import { Connector } from '@web3-react/types'
import UNIWALLET_ICON from 'assets/images/uniwallet.png'
import { useAccountDrawer } from 'components/AccountDrawer'
import { useAccountDrawer, useToggleAccountDrawer } from 'components/AccountDrawer'
import { Connection, ConnectionType } from 'connection/types'
import { mocked } from 'test-utils/mocked'
import { createDeferredPromise } from 'test-utils/promise'
......@@ -14,6 +14,7 @@ jest.mock('components/AccountDrawer')
beforeEach(() => {
jest.spyOn(console, 'debug').mockReturnValue()
mocked(useAccountDrawer).mockReturnValue([true, mockToggleDrawer])
mocked(useToggleAccountDrawer).mockReturnValue(mockToggleDrawer)
})
const mockConnection1: Connection = {
......
import { t, Trans } from '@lingui/macro'
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { useAccountDrawer } from 'components/AccountDrawer'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import { useToggleAccountDrawer } from 'components/AccountDrawer'
import Loader from 'components/Icons/LoadingSpinner'
import { walletConnectV1Connection, walletConnectV2Connection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { Connection, ConnectionType } from 'connection/types'
import { useWalletConnectV2AsDefault } from 'featureFlags/flags/walletConnectV2'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { MouseEvent, useEffect, useRef, useState } from 'react'
import { MoreHorizontal } from 'react-feather'
import { Connection } from 'connection/types'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { flexColumnNoWrap, flexRowNoWrap } from 'theme/styles'
import { Z_INDEX } from 'theme/zIndex'
import NewBadge from './NewBadge'
......@@ -65,41 +56,6 @@ const IconWrapper = styled.div`
align-items: flex-end;
`};
`
const WCv1PopoverContent = styled(ThemeButton)`
background: ${({ theme }) => theme.backgroundSurface};
border: 1px solid ${({ theme }) => theme.backgroundOutline};
border-radius: 12px;
cursor: pointer;
display: flex;
max-width: 240px;
padding: 16px;
position: absolute;
right: 12px;
top: 52px;
z-index: ${Z_INDEX.popover};
`
const TOGGLE_SIZE = 24
const WCv1PopoverToggle = styled.button`
align-items: center;
background-color: transparent;
border: none;
color: ${({ theme }) => theme.textTertiary};
cursor: pointer;
display: flex;
height: ${TOGGLE_SIZE}px;
justify-content: center;
margin: 0;
max-width: 48px;
padding: 0;
position: absolute;
right: 16px;
top: calc(50% - ${TOGGLE_SIZE / 2}px);
width: ${TOGGLE_SIZE}px;
&:hover {
opacity: 0.6;
}
`
const Wrapper = styled.div<{ disabled: boolean }>`
align-items: stretch;
display: flex;
......@@ -119,86 +75,18 @@ const Wrapper = styled.div<{ disabled: boolean }>`
}
`
const WCv1Icon = styled.img`
height: 20px !important;
width: 20px !important;
`
const WCv1BodyText = styled(ThemedText.BodyPrimary)`
margin-bottom: 4px !important;
text-align: left;
`
const WCv1Caption = styled(ThemedText.Caption)`
text-align: left;
`
interface PopupButtonContentProps {
connection: Connection
isDarkMode: boolean
show: boolean
onClick: (e: MouseEvent<HTMLButtonElement>) => void
onClose: () => void
}
function PopupButtonContent({ connection, isDarkMode, show, onClick, onClose }: PopupButtonContentProps) {
const popoverElement = useRef<HTMLButtonElement>(null)
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
useOnClickOutside(popoverElement, onClose)
if (!show) return null
return (
<WCv1PopoverContent onClick={onClick} ref={popoverElement} size={ButtonSize.small} emphasis={ButtonEmphasis.medium}>
<IconWrapper>
<WCv1Icon src={connection.getIcon?.(isDarkMode)} alt={connection.getName()} />
</IconWrapper>
<div>
<WCv1BodyText>
<Trans>Connect with {walletConnectV2AsDefault ? t`v1` : t`v2`}</Trans>
</WCv1BodyText>
<WCv1Caption color="textSecondary">
{walletConnectV2AsDefault
? t`Support for v1 will be discontinued June 28.`
: t`Under development and unsupported by most wallets`}
</WCv1Caption>
</div>
</WCv1PopoverContent>
)
}
interface OptionProps {
connection: Connection
}
export default function Option({ connection }: OptionProps) {
const { activationState, tryActivation } = useActivationState()
const [WC1PromptOpen, setWC1PromptOpen] = useState(false)
const [accountDrawerOpen, toggleAccountDrawerOpen] = useAccountDrawer()
const toggleAccountDrawerOpen = useToggleAccountDrawer()
const activate = () => tryActivation(connection, toggleAccountDrawerOpen)
useEffect(() => {
if (!accountDrawerOpen) setWC1PromptOpen(false)
}, [accountDrawerOpen])
const isSomeOptionPending = activationState.status === ActivationStatus.PENDING
const isCurrentOptionPending = isSomeOptionPending && activationState.connection.type === connection.type
const isDarkMode = useIsDarkMode()
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
const handleClickConnectViaWCv1 = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
tryActivation(walletConnectV2AsDefault ? walletConnectV1Connection : walletConnectV2Connection, () => {
setWC1PromptOpen(false)
toggleAccountDrawerOpen()
})
}
const handleClickOpenWCv1Tooltip = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
setWC1PromptOpen(true)
}
const isWalletConnect =
connection.type === ConnectionType.WALLET_CONNECT || connection.type === ConnectionType.WALLET_CONNECT_V2
const showExtraMenuToggle = isWalletConnect && !isCurrentOptionPending
return (
<Wrapper disabled={isSomeOptionPending}>
<TraceEvent
......@@ -223,21 +111,6 @@ export default function Option({ connection }: OptionProps) {
{isCurrentOptionPending && <Loader />}
</OptionCardClickable>
</TraceEvent>
{showExtraMenuToggle && (
<>
<WCv1PopoverToggle onClick={handleClickOpenWCv1Tooltip} onMouseDown={handleClickOpenWCv1Tooltip}>
<MoreHorizontal />
</WCv1PopoverToggle>
<PopupButtonContent
connection={connection}
isDarkMode={isDarkMode}
show={WC1PromptOpen}
onClick={handleClickConnectViaWCv1}
onClose={() => setWC1PromptOpen(false)}
/>
</>
)}
</Wrapper>
)
}
......@@ -4,9 +4,7 @@ import { AutoColumn } from 'components/Column'
import { AutoRow } from 'components/Row'
import { getConnections, networkConnection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { ConnectionType } from 'connection/types'
import { isSupportedChain } from 'constants/chains'
import { useWalletConnectV2AsDefault } from 'featureFlags/flags/walletConnectV2'
import { useEffect } from 'react'
import { Settings } from 'react-feather'
import styled from 'styled-components/macro'
......@@ -46,11 +44,6 @@ export default function WalletModal({ openSettings }: { openSettings: () => void
const { activationState } = useActivationState()
const walletConnectV2AsDefault = useWalletConnectV2AsDefault()
const hiddenWalletConnectType = walletConnectV2AsDefault
? ConnectionType.WALLET_CONNECT
: ConnectionType.WALLET_CONNECT_V2
// Keep the network connector in sync with any active user connector to prevent chain-switching on wallet disconnection.
useEffect(() => {
if (chainId && isSupportedChain(chainId) && connector !== networkConnection.connector) {
......@@ -70,7 +63,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())
.map((connection) => (
<Option key={connection.getName()} connection={connection} />
))}
......
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { URI_AVAILABLE, WalletConnect, WalletConnectConstructorArgs } from '@web3-react/walletconnect'
import { isIOS } from 'utils/userAgent'
import { RPC_URLS } from '../constants/networks'
// Avoid testing for the best URL by only passing a single URL per chain.
// Otherwise, WC will not initialize until all URLs have been tested (see getBestUrl in web3-react).
const RPC_URLS_WITHOUT_FALLBACKS = Object.entries(RPC_URLS).reduce(
(map, [chainId, urls]) => ({
...map,
[chainId]: urls[0],
}),
{}
)
export class WalletConnectV1 extends WalletConnect {
ANALYTICS_EVENT = 'Wallet Connect QR Scan'
constructor({
actions,
onError,
qrcode = true,
}: Omit<WalletConnectConstructorArgs, 'options'> & { qrcode?: boolean }) {
super({ actions, options: { qrcode, rpc: RPC_URLS_WITHOUT_FALLBACKS }, onError })
}
activate(chainId?: number) {
sendAnalyticsEvent(this.ANALYTICS_EVENT)
return super.activate(chainId)
}
}
// Custom class for Uniswap Wallet specific functionality
export class UniwalletConnect extends WalletConnectV1 {
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?.connector.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()
}
}
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'
......@@ -63,3 +64,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()
}
}
......@@ -227,10 +227,10 @@ describe('Should gracefully handle intentional user-rejection errors', () => {
const wcConnection = createMockConnection(
jest
.fn()
.mockImplementationOnce(() => Promise.reject(ErrorCode.WC_MODAL_CLOSED))
.mockImplementationOnce(() => Promise.reject(ErrorCode.WC_V2_MODAL_CLOSED))
.mockImplementationOnce(() => Promise.resolve),
jest.fn(),
ConnectionType.WALLET_CONNECT
ConnectionType.WALLET_CONNECT_V2
)
const onSuccess = jest.fn()
......
......@@ -19,7 +19,7 @@ describe('connection utility/metadata tests', () => {
UserAgentMock.isMobile = isMobile
global.window.ethereum = ethereum
const displayed = getConnections().filter((c) => c.shouldDisplay() && c.type !== ConnectionType.WALLET_CONNECT)
const displayed = getConnections().filter((c) => c.shouldDisplay())
const injected = getConnection(ConnectionType.INJECTED)
const coinbase = getConnection(ConnectionType.COINBASE_WALLET)
const uniswap = getConnection(ConnectionType.UNISWAP_WALLET)
......
......@@ -16,8 +16,7 @@ import { RPC_URLS } from '../constants/networks'
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, WalletConnectV2 } from './WalletConnectV2'
function onError(error: Error) {
console.debug(`web3-react error: ${error}`)
......@@ -70,18 +69,6 @@ export const gnosisSafeConnection: Connection = {
shouldDisplay: () => false,
}
const [web3WalletConnect, web3WalletConnectHooks] = initializeConnector<WalletConnectV1>(
(actions) => new WalletConnectV1({ actions, onError })
)
export const walletConnectV1Connection: Connection = {
getName: () => 'WalletConnect',
connector: web3WalletConnect,
hooks: web3WalletConnectHooks,
type: ConnectionType.WALLET_CONNECT,
getIcon: () => WALLET_CONNECT_ICON,
shouldDisplay: () => !getIsInjectedMobileBrowser(),
}
const [web3WalletConnectV2, web3WalletConnectV2Hooks] = initializeConnector<WalletConnectV2>(
(actions) => new WalletConnectV2({ actions, onError })
)
......@@ -144,7 +131,6 @@ export function getConnections() {
uniwalletConnectConnection,
injectedConnection,
walletConnectV2Connection,
walletConnectV1Connection,
coinbaseWalletConnection,
gnosisSafeConnection,
networkConnection,
......@@ -164,8 +150,6 @@ export function getConnection(c: Connector | ConnectionType) {
return injectedConnection
case ConnectionType.COINBASE_WALLET:
return coinbaseWalletConnection
case ConnectionType.WALLET_CONNECT:
return walletConnectV1Connection
case ConnectionType.WALLET_CONNECT_V2:
return walletConnectV2Connection
case ConnectionType.UNIWALLET:
......
......@@ -7,7 +7,6 @@ export enum ConnectionType {
UNIWALLET = 'UNIWALLET',
INJECTED = 'INJECTED',
COINBASE_WALLET = 'COINBASE_WALLET',
WALLET_CONNECT = 'WALLET_CONNECT',
WALLET_CONNECT_V2 = 'WALLET_CONNECT_V2',
NETWORK = 'NETWORK',
GNOSIS_SAFE = 'GNOSIS_SAFE',
......
......@@ -65,7 +65,6 @@ export enum ErrorCode {
MM_ALREADY_PENDING = -32002,
WC_V2_MODAL_CLOSED = 'Error: Connection request reset. Please try again.',
WC_MODAL_CLOSED = 'Error: User closed modal',
CB_REJECTED_REQUEST = 'Error: User denied account authorization',
}
......@@ -74,7 +73,6 @@ export function didUserReject(connection: Connection, error: any): boolean {
return (
error?.code === ErrorCode.USER_REJECTED_REQUEST ||
(connection.type === ConnectionType.WALLET_CONNECT_V2 && error?.toString?.() === ErrorCode.WC_V2_MODAL_CLOSED) ||
(connection.type === ConnectionType.WALLET_CONNECT && error?.toString?.() === ErrorCode.WC_MODAL_CLOSED) ||
(connection.type === ConnectionType.COINBASE_WALLET && error?.toString?.() === ErrorCode.CB_REJECTED_REQUEST)
)
}
......@@ -26,13 +26,6 @@ export enum SupportedChainId {
BNB = 56,
}
export const UniWalletSupportedChains = [
SupportedChainId.MAINNET,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.OPTIMISM,
SupportedChainId.POLYGON,
]
export const CHAIN_IDS_TO_NAMES = {
[SupportedChainId.MAINNET]: 'mainnet',
[SupportedChainId.GOERLI]: 'goerli',
......
import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
export function useWalletConnectV2Flag(): BaseVariant {
return useBaseFlag(FeatureFlag.walletConnectV2)
}
export function useWalletConnectV2AsDefault(): boolean {
return useWalletConnectV2Flag() === BaseVariant.Enabled
}
......@@ -14,7 +14,6 @@ export enum FeatureFlag {
debounceSwapQuote = 'debounce_swap_quote',
nativeUsdcArbitrum = 'web_usdc_arbitrum',
routingAPIPrice = 'routing_api_price',
walletConnectV2 = 'walletconnect_v2',
}
interface FeatureFlagsContextType {
......
......@@ -6,7 +6,7 @@ import { useAppSelector } from 'state/hooks'
const SELECTABLE_WALLETS = [
ConnectionType.UNISWAP_WALLET,
ConnectionType.INJECTED,
ConnectionType.WALLET_CONNECT,
ConnectionType.WALLET_CONNECT_V2,
ConnectionType.COINBASE_WALLET,
]
......
import { Connector } from '@web3-react/types'
import {
networkConnection,
uniwalletConnectConnection,
walletConnectV1Connection,
walletConnectV2Connection,
} from 'connection'
import { networkConnection, uniwalletConnectConnection, walletConnectV2Connection } from 'connection'
import { getChainInfo } from 'constants/chainInfo'
import { isSupportedChain, SupportedChainId } from 'constants/chains'
import { FALLBACK_URLS, RPC_URLS } from 'constants/networks'
......@@ -38,7 +33,6 @@ export function useSwitchChain() {
try {
if (
[
walletConnectV1Connection.connector,
walletConnectV2Connection.connector,
uniwalletConnectConnection.connector,
networkConnection.connector,
......
......@@ -135,6 +135,11 @@ const userSlice = createSlice({
if (state.selectedWallet === 'UNIWALLET') {
state.selectedWallet = ConnectionType.UNISWAP_WALLET
}
// WALLET_CONNECT (v1) has been deprecated
// @ts-expect-error
if (state.selectedWallet === 'WALLET_CONNECT') {
state.selectedWallet = undefined
}
// If `userSlippageTolerance` is not present or its value is invalid, reset to default
if (
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment