feat(wallet-connect): add support for v2 (#6582)

* feat(wallet-connect): add support for v2

* use theme button to fix opacity issue

* fix lint

* add new web3-react v2 package

* add mainnet to chains list

* fix test

* yarn dedupe

* add new @walletconnect/ethereum-provider

* fix safari padding

* fix second-click flash on popover toggle

* add walletconnect theme

* add goerli to non-prod chain selector

* remove: debug

* remove vertical line

* WEB-2107 Fix modal close behavior

* remove logging

* clean up accountDrawerOpenAtom usage

* remove irrelevant comments

* remove unintentional whitespace diff

* yarn yarn-deduplicate --strategy=highest

* add conditional chain selector

* update wc package version

* goerli -> sepolia

* goerli -> sepolia

* yarn yarn-deduplicate --strategy=highest

* UNIWALLET -> UNISWAP_WALLET

* useWalletSupportsChain -> useWalletSupportedChains

* use TOGGLE_SIZE

* remove inline styles

* remove inline styles and use better alt text

* update Option.test

* use a named function for forwardRef arg

* fix types

---------

Co-authored-by: Jordan Frankfurt <jordan@CORN-Jordan-949.frankfurt>
This commit is contained in:
Jordan Frankfurt 2023-06-07 11:11:08 -05:00 committed by GitHub
parent 0891e67528
commit 5788385951
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1127 additions and 152 deletions

1
.env

@ -11,3 +11,4 @@ REACT_APP_MOONPAY_PUBLISHABLE_KEY="pk_test_DycfESRid31UaSxhI5yWKe1r5E5kKSz"
REACT_APP_SENTRY_DSN="https://a3c62e400b8748b5a8d007150e2f38b7@o1037921.ingest.sentry.io/4504255148851200"
REACT_APP_STATSIG_PROXY_URL="https://api.uniswap.org/v1/statsig-proxy"
REACT_APP_TEMP_API_URL="https://temp.api.uniswap.org/v1"
REACT_APP_WALLET_CONNECT_PROJECT_ID="c6c9bacd35afa3eb9e6cccf6d8464395"

@ -186,7 +186,7 @@
"@visx/react-spring": "^2.12.2",
"@visx/responsive": "^2.10.0",
"@visx/shape": "^2.11.1",
"@walletconnect/ethereum-provider": "^1.8.0",
"@walletconnect/ethereum-provider": "^2.7.8",
"@web3-react/coinbase-wallet": "^8.2.0",
"@web3-react/core": "^8.2.0",
"@web3-react/eip1193": "^8.2.0",
@ -197,6 +197,7 @@
"@web3-react/types": "^8.2.0",
"@web3-react/url": "^8.2.0",
"@web3-react/walletconnect": "^8.2.0",
"@web3-react/walletconnect-v2": "^8.3.1",
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"array.prototype.flat": "^1.2.4",

@ -44,7 +44,7 @@ export default function UniwalletModal() {
// Displays the modal if a Uniswap Wallet Connection is pending & qrcode URI is available
const open =
activationState.status === ActivationStatus.PENDING &&
activationState.connection.type === ConnectionType.UNIWALLET &&
activationState.connection.type === ConnectionType.UNISWAP_WALLET &&
!!uri
useEffect(() => {

@ -1,4 +1,5 @@
import { darken } from 'polished'
import { forwardRef } from 'react'
import { Check, ChevronDown } from 'react-feather'
import { Button as RebassButton, ButtonProps as ButtonPropsOriginal } from 'rebass/styled-components'
import styled, { DefaultTheme, useTheme } from 'styled-components/macro'
@ -524,15 +525,19 @@ const BaseThemeButton = styled.button<BaseThemeButtonProps>`
`
interface ThemeButtonProps extends React.ComponentPropsWithoutRef<'button'>, BaseThemeButtonProps {}
type ThemeButtonRef = HTMLButtonElement
export const ThemeButton = ({ children, ...rest }: ThemeButtonProps) => {
export const ThemeButton = forwardRef<ThemeButtonRef, ThemeButtonProps>(function ThemeButton(
{ children, ...rest },
ref
) {
return (
<BaseThemeButton {...rest}>
<BaseThemeButton {...rest} ref={ref}>
<ButtonOverlay />
{children}
</BaseThemeButton>
)
}
})
export const ButtonLight = ({ children, ...rest }: BaseButtonProps) => {
return (

@ -1,5 +1,6 @@
import { t } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import { WalletConnect } from '@web3-react/walletconnect-v2'
import { MouseoverTooltip } from 'components/Tooltip'
import { useGetConnection } from 'connection'
import { ConnectionType } from 'connection/types'
@ -15,6 +16,7 @@ import { useIsMobile } from 'nft/hooks'
import { useCallback, useRef, useState } from 'react'
import { AlertTriangle, ChevronDown, ChevronUp } from 'react-feather'
import { useTheme } from 'styled-components/macro'
import { isProductionEnv } from 'utils/env'
import * as styles from './ChainSelector.css'
import ChainSelectorRow from './ChainSelectorRow'
@ -29,12 +31,44 @@ const NETWORK_SELECTOR_CHAINS = [
SupportedChainId.BNB,
]
if (!isProductionEnv()) {
NETWORK_SELECTOR_CHAINS.push(SupportedChainId.SEPOLIA)
}
interface ChainSelectorProps {
leftAlign?: boolean
}
// accounts is an array of strings in the format of "eip155:<chainId>:<address>"
function getChainsFromEIP155Accounts(accounts?: string[]): SupportedChainId[] {
if (!accounts) return []
return accounts
.map((account) => {
const splitAccount = account.split(':')
return splitAccount[1] ? parseInt(splitAccount[1]) : undefined
})
.filter((x) => x !== undefined) as SupportedChainId[]
}
function useWalletSupportedChains() {
const { connector } = useWeb3React()
const getConnection = useGetConnection()
const connectionType = getConnection(connector).type
switch (connectionType) {
case ConnectionType.WALLET_CONNECT_V2:
return getChainsFromEIP155Accounts((connector as WalletConnect).provider?.session?.namespaces.eip155.accounts)
case ConnectionType.UNISWAP_WALLET:
return UniWalletSupportedChains
default:
return NETWORK_SELECTOR_CHAINS
}
}
export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
const { chainId, connector } = useWeb3React()
const { chainId } = useWeb3React()
const [isOpen, setIsOpen] = useState<boolean>(false)
const isMobile = useIsMobile()
@ -61,9 +95,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
[selectChain, setIsOpen]
)
const getConnection = useGetConnection()
const connectionType = getConnection(connector).type
const isUniWallet = connectionType === ConnectionType.UNIWALLET
const walletSupportsChain = useWalletSupportedChains()
if (!chainId) {
return null
@ -74,13 +106,13 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
const dropdown = (
<NavDropdown top="56" left={leftAlign ? '0' : 'auto'} right={leftAlign ? 'auto' : '0'} ref={modalRef}>
<Column paddingX="8">
{NETWORK_SELECTOR_CHAINS.map((chainId: SupportedChainId) => (
{NETWORK_SELECTOR_CHAINS.map((selectorChain: SupportedChainId) => (
<ChainSelectorRow
disabled={isUniWallet && !UniWalletSupportedChains.includes(chainId)}
disabled={!walletSupportsChain.includes(selectorChain)}
onSelectChain={onSelectChain}
targetChain={chainId}
key={chainId}
isPending={chainId === pendingChainId}
targetChain={selectorChain}
key={selectorChain}
isPending={selectorChain === pendingChainId}
/>
))}
</Column>

@ -1,6 +1,6 @@
import { Connector } from '@web3-react/types'
import UNIWALLET_ICON from 'assets/images/uniwallet.png'
import { useCloseAccountDrawer } from 'components/AccountDrawer'
import { useAccountDrawer } from 'components/AccountDrawer'
import { Connection, ConnectionType } from 'connection/types'
import { mocked } from 'test-utils/mocked'
import { createDeferredPromise } from 'test-utils/promise'
@ -8,12 +8,12 @@ import { act, render } from 'test-utils/render'
import Option from './Option'
const mockCloseDrawer = jest.fn()
const mockToggleDrawer = jest.fn()
jest.mock('components/AccountDrawer')
beforeEach(() => {
jest.spyOn(console, 'debug').mockReturnValue()
mocked(useCloseAccountDrawer).mockReturnValue(mockCloseDrawer)
mocked(useAccountDrawer).mockReturnValue([true, mockToggleDrawer])
})
const mockConnection1: Connection = {
@ -23,7 +23,7 @@ const mockConnection1: Connection = {
deactivate: jest.fn(),
} as unknown as Connector,
getIcon: () => UNIWALLET_ICON,
type: ConnectionType.UNIWALLET,
type: ConnectionType.UNISWAP_WALLET,
} as unknown as Connection
const mockConnection2: Connection = {
@ -39,7 +39,7 @@ const mockConnection2: Connection = {
describe('Wallet Option', () => {
it('renders default state', () => {
const component = render(<Option connection={mockConnection1} />)
const option = component.getByTestId('wallet-option-UNIWALLET')
const option = component.getByTestId('wallet-option-UNISWAP_WALLET')
expect(option).toBeEnabled()
expect(option).toHaveProperty('selected', false)
@ -56,7 +56,7 @@ describe('Wallet Option', () => {
<Option connection={mockConnection2} />
</>
)
const option1 = component.getByTestId('wallet-option-UNIWALLET')
const option1 = component.getByTestId('wallet-option-UNISWAP_WALLET')
const option2 = component.getByTestId('wallet-option-INJECTED')
expect(option1).toBeEnabled()
@ -71,12 +71,8 @@ describe('Wallet Option', () => {
expect(option2).toBeDisabled()
expect(option2).toHaveProperty('selected', false)
expect(mockCloseDrawer).toHaveBeenCalledTimes(0)
await act(async () => activationResponse.resolve())
expect(mockCloseDrawer).toHaveBeenCalledTimes(1)
expect(option1).toBeEnabled()
expect(option1).toHaveProperty('selected', false)
expect(option2).toBeEnabled()

@ -1,12 +1,19 @@
import { TraceEvent } from '@uniswap/analytics'
import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap/analytics-events'
import { useCloseAccountDrawer } from 'components/AccountDrawer'
import { useAccountDrawer } from 'components/AccountDrawer'
import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button'
import Loader from 'components/Icons/LoadingSpinner'
import { walletConnectV2Connection } from 'connection'
import { ActivationStatus, useActivationState } from 'connection/activate'
import { Connection } from 'connection/types'
import { Connection, ConnectionType } from 'connection/types'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { MouseEvent, useEffect, useRef, useState } from 'react'
import { MoreHorizontal } from 'react-feather'
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'
@ -17,26 +24,17 @@ const OptionCardLeft = styled.div`
`
const OptionCardClickable = styled.button<{ selected: boolean }>`
background-color: ${({ theme }) => theme.backgroundModule};
border: none;
width: 100% !important;
display: flex;
flex-direction: row;
align-items: center;
background-color: unset;
border: none;
cursor: pointer;
display: flex;
flex: 1 1 auto;
flex-direction: row;
justify-content: space-between;
padding: 18px;
transition: ${({ theme }) => theme.transition.duration.fast};
opacity: ${({ disabled, selected }) => (disabled && !selected ? '0.5' : '1')};
&:hover {
cursor: ${({ disabled }) => !disabled && 'pointer'};
background-color: ${({ theme, disabled }) => !disabled && theme.hoverState};
}
&:focus {
background-color: ${({ theme, disabled }) => !disabled && theme.hoverState};
}
padding: 18px;
transition: ${({ theme }) => theme.transition.duration.fast};
`
const HeaderText = styled.div`
@ -48,7 +46,6 @@ const HeaderText = styled.div`
font-weight: 600;
padding: 0 8px;
`
const IconWrapper = styled.div`
${flexColumnNoWrap};
align-items: center;
@ -62,38 +59,164 @@ const IconWrapper = styled.div`
align-items: flex-end;
`};
`
const WCv2PopoverContent = 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 WCv2PopoverToggle = 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;
export default function Option({ connection }: { connection: Connection }) {
&:hover {
opacity: 0.6;
}
`
const Wrapper = styled.div<{ disabled: boolean }>`
align-items: stretch;
display: flex;
flex-direction: row;
justify-content: space-between;
position: relative;
width: 100%;
background-color: ${({ theme }) => theme.backgroundModule};
&:hover {
cursor: ${({ disabled }) => !disabled && 'pointer'};
background-color: ${({ theme, disabled }) => !disabled && theme.hoverState};
}
&:focus {
background-color: ${({ theme, disabled }) => !disabled && theme.hoverState};
}
`
const WCv2Icon = styled.img`
height: 20px !important;
width: 20px !important;
`
const WCv2BodyText = styled(ThemedText.BodyPrimary)`
margin-bottom: 4px;
text-align: left;
`
const WCv2Caption = 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)
useOnClickOutside(popoverElement, onClose)
if (!show) return null
return (
<WCv2PopoverContent onClick={onClick} ref={popoverElement} size={ButtonSize.small} emphasis={ButtonEmphasis.medium}>
<IconWrapper>
<WCv2Icon src={connection.getIcon?.(isDarkMode)} alt={connection.getName()} />
</IconWrapper>
<div>
<WCv2BodyText>Connect with v2</WCv2BodyText>
<WCv2Caption color="textSecondary">Under development and unsupported by most wallets</WCv2Caption>
</div>
</WCv2PopoverContent>
)
}
interface OptionProps {
connection: Connection
}
export default function Option({ connection }: OptionProps) {
const { activationState, tryActivation } = useActivationState()
const closeDrawer = useCloseAccountDrawer()
const activate = () => tryActivation(connection, closeDrawer)
const [WC2PromptOpen, setWC2PromptOpen] = useState(false)
const [accountDrawerOpen, toggleAccountDrawerOpen] = useAccountDrawer()
const activate = () => tryActivation(connection, toggleAccountDrawerOpen)
useEffect(() => {
if (!accountDrawerOpen) setWC2PromptOpen(false)
}, [accountDrawerOpen])
const isSomeOptionPending = activationState.status === ActivationStatus.PENDING
const isCurrentOptionPending = isSomeOptionPending && activationState.connection.type === connection.type
const isDarkMode = useIsDarkMode()
const handleClickConnectViaWCv2 = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
tryActivation(walletConnectV2Connection, () => {
setWC2PromptOpen(false)
toggleAccountDrawerOpen()
})
}
const handleClickOpenWCv2Tooltip = (e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
setWC2PromptOpen(true)
}
return (
<TraceEvent
events={[BrowserEvent.onClick]}
name={InterfaceEventName.WALLET_SELECTED}
properties={{ wallet_type: connection.getName() }}
element={InterfaceElementName.WALLET_TYPE_OPTION}
>
<OptionCardClickable
onClick={activate}
disabled={isSomeOptionPending}
selected={isCurrentOptionPending}
data-testid={`wallet-option-${connection.type}`}
<Wrapper disabled={isSomeOptionPending}>
<TraceEvent
events={[BrowserEvent.onClick]}
name={InterfaceEventName.WALLET_SELECTED}
properties={{ wallet_type: connection.getName() }}
element={InterfaceElementName.WALLET_TYPE_OPTION}
>
<OptionCardLeft>
<IconWrapper>
<img src={connection.getIcon?.(isDarkMode)} alt="Icon" />
</IconWrapper>
<HeaderText>{connection.getName()}</HeaderText>
{connection.isNew && <NewBadge />}
</OptionCardLeft>
{isCurrentOptionPending && <Loader />}
</OptionCardClickable>
</TraceEvent>
<OptionCardClickable
disabled={isSomeOptionPending}
onClick={activate}
selected={isCurrentOptionPending}
data-testid={`wallet-option-${connection.type}`}
>
<OptionCardLeft>
<IconWrapper>
<img src={connection.getIcon?.(isDarkMode)} alt={connection.getName()} />
</IconWrapper>
<HeaderText>{connection.getName()}</HeaderText>
{connection.isNew && <NewBadge />}
</OptionCardLeft>
{isCurrentOptionPending && <Loader />}
</OptionCardClickable>
</TraceEvent>
{connection.type === ConnectionType.WALLET_CONNECT && (
<>
<WCv2PopoverToggle onClick={handleClickOpenWCv2Tooltip} onMouseDown={handleClickOpenWCv2Tooltip}>
<MoreHorizontal />
</WCv2PopoverToggle>
<PopupButtonContent
connection={connection}
isDarkMode={isDarkMode}
show={WC2PromptOpen}
onClick={handleClickConnectViaWCv2}
onClose={() => setWC2PromptOpen(false)}
/>
</>
)}
</Wrapper>
)
}

@ -19,37 +19,31 @@ exports[`Wallet Option renders default state 1`] = `
}
.c0 {
background-color: #F5F6FC;
border: none;
width: 100% !important;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
background-color: unset;
border: none;
cursor: pointer;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-box-pack: justify;
-webkit-justify-content: space-between;
-ms-flex-pack: justify;
justify-content: space-between;
opacity: 1;
padding: 18px;
-webkit-transition: 125ms;
transition: 125ms;
opacity: 1;
}
.c0:hover {
cursor: pointer;
background-color: #ADBCFF3d;
}
.c0:focus {
background-color: #ADBCFF3d;
}
.c3 {
@ -109,7 +103,7 @@ exports[`Wallet Option renders default state 1`] = `
<button
class="c0"
data-testid="wallet-option-UNIWALLET"
data-testid="wallet-option-UNISWAP_WALLET"
>
<div
class="c1"
@ -118,7 +112,7 @@ exports[`Wallet Option renders default state 1`] = `
class="c2"
>
<img
alt="Icon"
alt="Mock Connection 1"
src="uniwallet.png"
/>
</div>

@ -0,0 +1,50 @@
import { sendAnalyticsEvent } from '@uniswap/analytics'
import { WalletConnect, WalletConnectConstructorArgs } from '@web3-react/walletconnect-v2'
import { SupportedChainId } from 'constants/chains'
import { Z_INDEX } from 'theme/zIndex'
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 WalletConnectV2Popup extends WalletConnect {
ANALYTICS_EVENT = 'Wallet Connect QR Scan'
constructor({
actions,
onError,
qrcode = true,
}: Omit<WalletConnectConstructorArgs, 'options'> & { 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: Object.keys(RPC_URLS_WITHOUT_FALLBACKS).map((key) => Number(key)),
chains: [SupportedChainId.MAINNET],
showQrModal: qrcode,
rpcMap: RPC_URLS_WITHOUT_FALLBACKS,
qrModalOptions: {
themeMode: darkmode ? 'dark' : 'light',
themeVariables: {
'--w3m-font-family': '"Inter custom", sans-serif',
'--w3m-z-index': Z_INDEX.modal.toString(),
},
},
},
onError,
})
}
activate(chainId?: number) {
sendAnalyticsEvent(this.ANALYTICS_EVENT)
return super.activate(chainId)
}
}

@ -19,7 +19,7 @@ describe('connection utility/metadata tests', () => {
const getConnection = renderHook(() => useGetConnection()).result.current
const injected = getConnection(ConnectionType.INJECTED)
const coinbase = getConnection(ConnectionType.COINBASE_WALLET)
const uniswap = getConnection(ConnectionType.UNIWALLET)
const uniswap = getConnection(ConnectionType.UNISWAP_WALLET)
const walletconnect = getConnection(ConnectionType.WALLET_CONNECT)
return { displayed, injected, coinbase, uniswap, walletconnect }

@ -21,6 +21,7 @@ import { RPC_PROVIDERS } from '../constants/providers'
import { Connection, ConnectionType } from './types'
import { getIsCoinbaseWallet, getIsInjected, getIsMetaMaskWallet } from './utils'
import { UniwalletConnect, WalletConnectPopup } from './WalletConnect'
import { WalletConnectV2Popup } from './WalletConnectV2'
function onError(error: Error) {
console.debug(`web3-react error: ${error}`)
@ -87,6 +88,18 @@ export const walletConnectConnection: Connection = {
shouldDisplay: () => !getIsInjectedMobileBrowser(),
}
const [web3WalletConnectV2, web3WalletConnectV2Hooks] = initializeConnector<WalletConnectV2Popup>(
(actions) => new WalletConnectV2Popup({ actions, onError })
)
export const walletConnectV2Connection: Connection = {
getName: () => 'WalletConnectV2',
connector: web3WalletConnectV2,
hooks: web3WalletConnectV2Hooks,
type: ConnectionType.WALLET_CONNECT_V2,
getIcon: () => WALLET_CONNECT_ICON,
shouldDisplay: () => false,
}
const [web3UniwalletConnect, web3UniwalletConnectHooks] = initializeConnector<UniwalletConnect>(
(actions) => new UniwalletConnect({ actions, onError })
)
@ -94,7 +107,7 @@ export const uniwalletConnectConnection: Connection = {
getName: () => 'Uniswap Wallet',
connector: web3UniwalletConnect,
hooks: web3UniwalletConnectHooks,
type: ConnectionType.UNIWALLET,
type: ConnectionType.UNISWAP_WALLET,
getIcon: () => UNIWALLET_ICON,
shouldDisplay: () => Boolean(!getIsInjectedMobileBrowser() && !isNonIOSPhone),
isNew: true,
@ -137,6 +150,7 @@ export function getConnections() {
uniwalletConnectConnection,
injectedConnection,
walletConnectConnection,
walletConnectV2Connection,
coinbaseWalletConnection,
gnosisSafeConnection,
networkConnection,
@ -159,7 +173,9 @@ export function useGetConnection() {
return coinbaseWalletConnection
case ConnectionType.WALLET_CONNECT:
return walletConnectConnection
case ConnectionType.UNIWALLET:
case ConnectionType.WALLET_CONNECT_V2:
return walletConnectV2Connection
case ConnectionType.UNISWAP_WALLET:
return uniwalletConnectConnection
case ConnectionType.NETWORK:
return networkConnection

@ -2,10 +2,11 @@ import { Web3ReactHooks } from '@web3-react/core'
import { Connector } from '@web3-react/types'
export enum ConnectionType {
UNIWALLET = 'UNIWALLET',
UNISWAP_WALLET = 'UNISWAP_WALLET',
INJECTED = 'INJECTED',
COINBASE_WALLET = 'COINBASE_WALLET',
WALLET_CONNECT = 'WALLET_CONNECT',
WALLET_CONNECT_V2 = 'WALLET_CONNECT_V2',
NETWORK = 'NETWORK',
GNOSIS_SAFE = 'GNOSIS_SAFE',
}

@ -24,6 +24,7 @@ export enum ErrorCode {
CHAIN_NOT_ADDED = 4902,
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',
}
@ -32,6 +33,7 @@ export enum ErrorCode {
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)
)

@ -123,6 +123,7 @@ export function validateUrlChainParam(chainName: string | undefined) {
export const CHAIN_NAME_TO_CHAIN_ID: { [key in Chain]: SupportedChainId } = {
[Chain.Ethereum]: SupportedChainId.MAINNET,
[Chain.EthereumGoerli]: SupportedChainId.GOERLI,
[Chain.EthereumSepolia]: SupportedChainId.SEPOLIA,
[Chain.Polygon]: SupportedChainId.POLYGON,
[Chain.Celo]: SupportedChainId.CELO,
[Chain.Optimism]: SupportedChainId.OPTIMISM,

@ -4,7 +4,7 @@ import { useMemo } from 'react'
import { useAppSelector } from 'state/hooks'
const SELECTABLE_WALLETS = [
ConnectionType.UNIWALLET,
ConnectionType.UNISWAP_WALLET,
ConnectionType.INJECTED,
ConnectionType.WALLET_CONNECT,
ConnectionType.COINBASE_WALLET,

@ -30,13 +30,11 @@ import { useSwapCallback } from 'hooks/useSwapCallback'
import { useUSDPrice } from 'hooks/useUSDPrice'
import JSBI from 'jsbi'
import { formatSwapQuoteReceivedEventProperties } from 'lib/utils/analytics'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { ReactNode } from 'react'
import { ReactNode, useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { ArrowDown } from 'react-feather'
import { useNavigate } from 'react-router-dom'
import { Text } from 'rebass'
import { InterfaceTrade } from 'state/routing/types'
import { TradeState } from 'state/routing/types'
import { InterfaceTrade, TradeState } from 'state/routing/types'
import styled, { useTheme } from 'styled-components/macro'
import { currencyAmountToPreciseFloat, formatTransactionAmount } from 'utils/formatNumbers'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'

@ -7,9 +7,10 @@ interface ConnectionState {
const initialState: ConnectionState = {
errorByConnectionType: {
[ConnectionType.UNIWALLET]: undefined,
[ConnectionType.UNISWAP_WALLET]: undefined,
[ConnectionType.INJECTED]: undefined,
[ConnectionType.WALLET_CONNECT]: undefined,
[ConnectionType.WALLET_CONNECT_V2]: undefined,
[ConnectionType.COINBASE_WALLET]: undefined,
[ConnectionType.NETWORK]: undefined,
[ConnectionType.GNOSIS_SAFE]: undefined,

@ -1,5 +1,10 @@
import { Connector } from '@web3-react/types'
import { networkConnection, uniwalletConnectConnection, walletConnectConnection } from 'connection'
import {
networkConnection,
uniwalletConnectConnection,
walletConnectConnection,
walletConnectV2Connection,
} from 'connection'
import { getChainInfo } from 'constants/chainInfo'
import { isSupportedChain, SupportedChainId } from 'constants/chains'
import { FALLBACK_URLS, RPC_URLS } from 'constants/networks'
@ -22,9 +27,12 @@ export const switchChain = async (connector: Connector, chainId: SupportedChainI
if (!isSupportedChain(chainId)) {
throw new Error(`Chain ${chainId} not supported for connector (${typeof connector})`)
} else if (
connector === walletConnectConnection.connector ||
connector === uniwalletConnectConnection.connector ||
connector === networkConnection.connector
[
walletConnectV2Connection.connector,
walletConnectConnection.connector,
uniwalletConnectConnection.connector,
networkConnection.connector,
].includes(connector)
) {
await connector.activate(chainId)
} else {

842
yarn.lock

File diff suppressed because it is too large Load Diff