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:
parent
0891e67528
commit
5788385951
1
.env
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,17 +59,127 @@ 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 (
|
||||
<Wrapper disabled={isSomeOptionPending}>
|
||||
<TraceEvent
|
||||
events={[BrowserEvent.onClick]}
|
||||
name={InterfaceEventName.WALLET_SELECTED}
|
||||
@ -80,14 +187,14 @@ export default function Option({ connection }: { connection: Connection }) {
|
||||
element={InterfaceElementName.WALLET_TYPE_OPTION}
|
||||
>
|
||||
<OptionCardClickable
|
||||
onClick={activate}
|
||||
disabled={isSomeOptionPending}
|
||||
onClick={activate}
|
||||
selected={isCurrentOptionPending}
|
||||
data-testid={`wallet-option-${connection.type}`}
|
||||
>
|
||||
<OptionCardLeft>
|
||||
<IconWrapper>
|
||||
<img src={connection.getIcon?.(isDarkMode)} alt="Icon" />
|
||||
<img src={connection.getIcon?.(isDarkMode)} alt={connection.getName()} />
|
||||
</IconWrapper>
|
||||
<HeaderText>{connection.getName()}</HeaderText>
|
||||
{connection.isNew && <NewBadge />}
|
||||
@ -95,5 +202,21 @@ export default function Option({ connection }: { connection: Connection }) {
|
||||
{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>
|
||||
|
50
src/connection/WalletConnectV2.ts
Normal file
50
src/connection/WalletConnectV2.ts
Normal file
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user