Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
302af21a22 | ||
|
|
b61a2d4111 | ||
|
|
9be26788a2 | ||
|
|
ed393de481 | ||
|
|
cf5c393d97 | ||
|
|
68d81a0040 | ||
|
|
53caa51ac3 | ||
|
|
409ba72f9f | ||
|
|
9d9b3dca78 | ||
|
|
a11c7e9573 | ||
|
|
31bbcae1ed | ||
|
|
a1f6c7270e |
@@ -12,9 +12,10 @@ module.exports = {
|
||||
jest: {
|
||||
configure(jestConfig) {
|
||||
return Object.assign({}, jestConfig, {
|
||||
transformIgnorePatterns: ['@uniswap/conedison/format'],
|
||||
transformIgnorePatterns: ['@uniswap/conedison/format', '@uniswap/conedison/provider'],
|
||||
moduleNameMapper: {
|
||||
'@uniswap/conedison/format': '@uniswap/conedison/dist/format',
|
||||
'@uniswap/conedison/provider': '@uniswap/conedison/dist/provider',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('Universal search bar', () => {
|
||||
.should('be.eq', 3)
|
||||
})
|
||||
|
||||
it('should show blocked badge when blocked token is searched for', () => {
|
||||
it.skip('should show blocked badge when blocked token is searched for', () => {
|
||||
// Search for mTSLA, which is a blocked token.
|
||||
cy.get('[data-cy="search-bar-input"]').last().clear().type('mtsla')
|
||||
cy.get('[data-cy="searchbar-token-row-mTSLA"]').find('[data-cy="blocked-icon"]').should('exist')
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@uniswap/analytics": "1.2.0",
|
||||
"@uniswap/analytics-events": "^2.1.0",
|
||||
"@uniswap/conedison": "^1.1.1",
|
||||
"@uniswap/conedison": "^1.2.1",
|
||||
"@uniswap/governance": "^1.0.2",
|
||||
"@uniswap/liquidity-staker": "^1.0.2",
|
||||
"@uniswap/merkle-distributor": "1.0.1",
|
||||
@@ -145,14 +145,14 @@
|
||||
"@uniswap/sdk-core": "^3.0.1",
|
||||
"@uniswap/smart-order-router": "^2.10.0",
|
||||
"@uniswap/token-lists": "^1.0.0-beta.30",
|
||||
"@uniswap/universal-router-sdk": "1.3.0",
|
||||
"@uniswap/universal-router-sdk": "1.3.4",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@uniswap/v3-sdk": "^3.9.0",
|
||||
"@uniswap/widgets": "2.25.1",
|
||||
"@uniswap/widgets": "^2.26.0",
|
||||
"@vanilla-extract/css": "^1.7.2",
|
||||
"@vanilla-extract/css-utils": "^0.1.2",
|
||||
"@vanilla-extract/dynamic": "^2.0.2",
|
||||
@@ -170,7 +170,7 @@
|
||||
"@web3-react/eip1193": "8.0.26-beta.0",
|
||||
"@web3-react/empty": "8.0.20-beta.0",
|
||||
"@web3-react/gnosis-safe": "8.0.7-beta.0",
|
||||
"@web3-react/metamask": "8.0.28-beta.0",
|
||||
"@web3-react/metamask": "8.0.29-beta.0",
|
||||
"@web3-react/network": "8.0.27-beta.0",
|
||||
"@web3-react/types": "8.0.20-beta.0",
|
||||
"@web3-react/url": "8.0.25-beta.0",
|
||||
|
||||
@@ -120,7 +120,7 @@ const LogoSectionContent = () => {
|
||||
<>
|
||||
<StyledLogo src={isDarkMode ? darkUnicornImgSrc : lightUnicornImgSrc} alt="Uniswap Logo" />
|
||||
<SocialLinks>
|
||||
<SocialLink href="https://github.com/Uniswap" target="_blank" rel="noopener noreferrer">
|
||||
<SocialLink href="https://discord.gg/FCfyBSbCU5" target="_blank" rel="noopener noreferrer">
|
||||
<DiscordIcon size={32} />
|
||||
</SocialLink>
|
||||
<TraceEvent
|
||||
@@ -132,7 +132,7 @@ const LogoSectionContent = () => {
|
||||
<TwitterIcon size={32} />
|
||||
</SocialLink>
|
||||
</TraceEvent>
|
||||
<SocialLink href="https://discord.gg/FCfyBSbCU5" target="_blank" rel="noopener noreferrer">
|
||||
<SocialLink href="https://github.com/Uniswap" target="_blank" rel="noopener noreferrer">
|
||||
<GithubIcon size={32} />
|
||||
</SocialLink>
|
||||
</SocialLinks>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import {
|
||||
getConnection,
|
||||
getConnectionName,
|
||||
getHasCoinbaseExtensionInstalled,
|
||||
getHasMetaMaskExtensionInstalled,
|
||||
} from 'connection/utils'
|
||||
import { getConnection, getConnectionName, getIsCoinbaseWallet, getIsMetaMaskWallet } from 'connection/utils'
|
||||
import { useCallback } from 'react'
|
||||
import { ExternalLink as LinkIcon } from 'react-feather'
|
||||
import { useAppDispatch } from 'state/hooks'
|
||||
@@ -215,8 +210,8 @@ export default function AccountDetails({
|
||||
const theme = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const hasMetaMaskExtension = getHasMetaMaskExtensionInstalled()
|
||||
const hasCoinbaseExtension = getHasCoinbaseExtensionInstalled()
|
||||
const hasMetaMaskExtension = getIsMetaMaskWallet()
|
||||
const hasCoinbaseExtension = getIsCoinbaseWallet()
|
||||
const isInjectedMobileBrowser = (hasMetaMaskExtension || hasCoinbaseExtension) && isMobile
|
||||
|
||||
function formatConnectorName() {
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { ButtonPrimary } from 'components/Button'
|
||||
import { AutoColumn } from 'components/Column'
|
||||
import Modal from 'components/Modal'
|
||||
import { RowBetween } from 'components/Row'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components/macro'
|
||||
import { CloseIcon, ThemedText } from 'theme'
|
||||
|
||||
import { useModalIsOpen, useToggleMetaMaskConnectionErrorModal } from '../../state/application/hooks'
|
||||
import { ApplicationModal } from '../../state/application/reducer'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
padding: 32px 32px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const ShortColumn = styled(AutoColumn)`
|
||||
margin-top: 10px;
|
||||
`
|
||||
|
||||
const InfoText = styled(Text)`
|
||||
padding: 0 12px 0 12px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const StyledButton = styled(ButtonPrimary)`
|
||||
margin-top: 24px;
|
||||
width: 100%;
|
||||
font-weight: 600;
|
||||
`
|
||||
|
||||
const WarningIcon = styled(AlertTriangle)`
|
||||
width: 76px;
|
||||
height: 76px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 28px;
|
||||
stroke-width: 1px;
|
||||
margin-right: 4px;
|
||||
color: ${({ theme }) => theme.accentCritical};
|
||||
`
|
||||
|
||||
const onReconnect = () => window.location.reload()
|
||||
|
||||
export default function MetaMaskConnectionErrorModal() {
|
||||
const modalOpen = useModalIsOpen(ApplicationModal.METAMASK_CONNECTION_ERROR)
|
||||
const toggleModal = useToggleMetaMaskConnectionErrorModal()
|
||||
|
||||
return (
|
||||
<Modal isOpen={modalOpen} onDismiss={toggleModal} minHeight={false} maxHeight={90}>
|
||||
<Wrapper>
|
||||
<RowBetween style={{ padding: '1rem' }}>
|
||||
<div />
|
||||
<CloseIcon onClick={toggleModal} />
|
||||
</RowBetween>
|
||||
<Container>
|
||||
<AutoColumn>
|
||||
<LogoContainer>
|
||||
<WarningIcon />
|
||||
</LogoContainer>
|
||||
</AutoColumn>
|
||||
<ShortColumn>
|
||||
<InfoText>
|
||||
<ThemedText.HeadlineSmall marginBottom="8px">
|
||||
<Trans>Wallet disconnected</Trans>
|
||||
</ThemedText.HeadlineSmall>
|
||||
<ThemedText.BodySmall>
|
||||
<Trans>A MetaMask error caused your wallet to disconnect. Reload the page to reconnect.</Trans>
|
||||
</ThemedText.BodySmall>
|
||||
</InfoText>
|
||||
</ShortColumn>
|
||||
<StyledButton onClick={onReconnect}>
|
||||
<Trans>Reload</Trans>
|
||||
</StyledButton>
|
||||
</Container>
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { getChainInfoOrDefault, L2ChainInfo } from 'constants/chainInfo'
|
||||
import { SupportedChainId } from 'constants/chains'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
|
||||
|
||||
const BodyRow = styled.div`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
`
|
||||
const CautionTriangle = styled(AlertTriangle)`
|
||||
color: ${({ theme }) => theme.accentWarning};
|
||||
`
|
||||
const Link = styled(ExternalLink)`
|
||||
color: ${({ theme }) => theme.black};
|
||||
text-decoration: underline;
|
||||
`
|
||||
const TitleRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const TitleText = styled.div`
|
||||
color: ${({ theme }) => theme.textPrimary};
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin: 0px 12px;
|
||||
`
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
border-radius: 12px;
|
||||
border: 1px solid ${({ theme }) => theme.backgroundOutline};
|
||||
bottom: 60px;
|
||||
display: none;
|
||||
max-width: 348px;
|
||||
padding: 16px 20px;
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
@media screen and (min-width: ${MEDIA_WIDTHS.deprecated_upToMedium}px) {
|
||||
display: block;
|
||||
}
|
||||
`
|
||||
|
||||
export function ChainConnectivityWarning() {
|
||||
const { chainId } = useWeb3React()
|
||||
const info = getChainInfoOrDefault(chainId)
|
||||
const label = info?.label
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<TitleRow>
|
||||
<CautionTriangle />
|
||||
<TitleText>
|
||||
<Trans>Network Warning</Trans>
|
||||
</TitleText>
|
||||
</TitleRow>
|
||||
<BodyRow>
|
||||
{chainId === SupportedChainId.MAINNET ? (
|
||||
<Trans>You may have lost your network connection.</Trans>
|
||||
) : (
|
||||
<Trans>{label} might be down right now, or you may have lost your network connection.</Trans>
|
||||
)}{' '}
|
||||
{(info as L2ChainInfo).statusPage !== undefined && (
|
||||
<span>
|
||||
<Trans>Check network status</Trans>{' '}
|
||||
<Link href={(info as L2ChainInfo).statusPage || ''}>
|
||||
<Trans>here.</Trans>
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
</BodyRow>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { RowFixed } from 'components/Row'
|
||||
import { getChainInfo } from 'constants/chainInfo'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import useGasPrice from 'hooks/useGasPrice'
|
||||
import { useIsLandingPage } from 'hooks/useIsLandingPage'
|
||||
import { useIsNftPage } from 'hooks/useIsNftPage'
|
||||
import useMachineTimeMs from 'hooks/useMachineTime'
|
||||
import JSBI from 'jsbi'
|
||||
import useBlockNumber from 'lib/hooks/useBlockNumber'
|
||||
import ms from 'ms.macro'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import styled, { keyframes } from 'styled-components/macro'
|
||||
import { ExternalLink, ThemedText } from 'theme'
|
||||
import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink'
|
||||
|
||||
import { MouseoverTooltip } from '../Tooltip'
|
||||
import { ChainConnectivityWarning } from './ChainConnectivityWarning'
|
||||
|
||||
const StyledPolling = styled.div`
|
||||
align-items: center;
|
||||
bottom: 0;
|
||||
color: ${({ theme }) => theme.textTertiary};
|
||||
display: none;
|
||||
padding: 1rem;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
transition: 250ms ease color;
|
||||
|
||||
a {
|
||||
color: unset;
|
||||
}
|
||||
a:hover {
|
||||
color: unset;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: ${({ theme }) => theme.breakpoint.md}px) {
|
||||
display: flex;
|
||||
}
|
||||
`
|
||||
const StyledPollingBlockNumber = styled(ThemedText.DeprecatedSmall)<{
|
||||
breathe: boolean
|
||||
hovering: boolean
|
||||
warning: boolean
|
||||
}>`
|
||||
color: ${({ theme, warning }) => (warning ? theme.deprecated_yellow3 : theme.accentSuccess)};
|
||||
transition: opacity 0.25s ease;
|
||||
opacity: ${({ breathe, hovering }) => (hovering ? 0.7 : breathe ? 1 : 0.5)};
|
||||
:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: unset;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
color: unset;
|
||||
}
|
||||
`
|
||||
const StyledPollingDot = styled.div<{ warning: boolean }>`
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
min-height: 8px;
|
||||
min-width: 8px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
background-color: ${({ theme, warning }) => (warning ? theme.deprecated_yellow3 : theme.accentSuccess)};
|
||||
transition: 250ms ease background-color;
|
||||
`
|
||||
|
||||
const StyledGasDot = styled.div`
|
||||
background-color: ${({ theme }) => theme.textTertiary};
|
||||
border-radius: 50%;
|
||||
height: 4px;
|
||||
min-height: 4px;
|
||||
min-width: 4px;
|
||||
position: relative;
|
||||
transition: 250ms ease background-color;
|
||||
width: 4px;
|
||||
`
|
||||
|
||||
const rotate360 = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`
|
||||
|
||||
const Spinner = styled.div<{ warning: boolean }>`
|
||||
animation: ${rotate360} 1s cubic-bezier(0.83, 0, 0.17, 1) infinite;
|
||||
transform: translateZ(0);
|
||||
|
||||
border-top: 1px solid transparent;
|
||||
border-right: 1px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
border-left: 2px solid ${({ theme, warning }) => (warning ? theme.deprecated_yellow3 : theme.accentSuccess)};
|
||||
background: transparent;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
transition: 250ms ease border-color;
|
||||
|
||||
left: -3px;
|
||||
top: -3px;
|
||||
`
|
||||
|
||||
const DEFAULT_MS_BEFORE_WARNING = ms`10m`
|
||||
const NETWORK_HEALTH_CHECK_MS = ms`10s`
|
||||
|
||||
export default function Polling() {
|
||||
const { chainId } = useWeb3React()
|
||||
const blockNumber = useBlockNumber()
|
||||
const [isMounting, setIsMounting] = useState(false)
|
||||
const [isHover, setIsHover] = useState(false)
|
||||
const machineTime = useMachineTimeMs(NETWORK_HEALTH_CHECK_MS)
|
||||
const blockTime = useCurrentBlockTimestamp()
|
||||
const isNftPage = useIsNftPage()
|
||||
const isLandingPage = useIsLandingPage()
|
||||
|
||||
const ethGasPrice = useGasPrice()
|
||||
const priceGwei = ethGasPrice ? JSBI.divide(ethGasPrice, JSBI.BigInt(1000000000)) : undefined
|
||||
|
||||
const waitMsBeforeWarning =
|
||||
(chainId ? getChainInfo(chainId)?.blockWaitMsBeforeWarning : DEFAULT_MS_BEFORE_WARNING) ?? DEFAULT_MS_BEFORE_WARNING
|
||||
|
||||
const warning = Boolean(!!blockTime && machineTime - blockTime.mul(1000).toNumber() > waitMsBeforeWarning)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if (!blockNumber) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsMounting(true)
|
||||
const mountingTimer = setTimeout(() => setIsMounting(false), 1000)
|
||||
|
||||
// this will clear Timeout when component unmount like in willComponentUnmount
|
||||
return () => {
|
||||
clearTimeout(mountingTimer)
|
||||
}
|
||||
},
|
||||
[blockNumber] //useEffect will run only one time
|
||||
//if you pass a value to array, like this [data] than clearTimeout will run every time this value changes (useEffect re-run)
|
||||
)
|
||||
|
||||
//TODO - chainlink gas oracle is really slow. Can we get a better data source?
|
||||
|
||||
const blockExternalLinkHref = useMemo(() => {
|
||||
if (!chainId || !blockNumber) return ''
|
||||
return getExplorerLink(chainId, blockNumber.toString(), ExplorerDataType.BLOCK)
|
||||
}, [blockNumber, chainId])
|
||||
|
||||
if (isNftPage || isLandingPage) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<RowFixed>
|
||||
<StyledPolling onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}>
|
||||
<ExternalLink href="https://etherscan.io/gastracker">
|
||||
{!!priceGwei && (
|
||||
<RowFixed style={{ marginRight: '8px' }}>
|
||||
<ThemedText.DeprecatedMain fontSize="11px" mr="8px">
|
||||
<MouseoverTooltip
|
||||
text={
|
||||
<Trans>
|
||||
The current fast gas amount for sending a transaction on L1. Gas fees are paid in Ethereum's
|
||||
native currency Ether (ETH) and denominated in GWEI.
|
||||
</Trans>
|
||||
}
|
||||
>
|
||||
{priceGwei.toString()} <Trans>gwei</Trans>
|
||||
</MouseoverTooltip>
|
||||
</ThemedText.DeprecatedMain>
|
||||
<StyledGasDot />
|
||||
</RowFixed>
|
||||
)}
|
||||
</ExternalLink>
|
||||
<StyledPollingBlockNumber breathe={isMounting} hovering={isHover} warning={warning}>
|
||||
<ExternalLink href={blockExternalLinkHref}>
|
||||
<MouseoverTooltip
|
||||
text={<Trans>The most recent block number on this network. Prices update on every block.</Trans>}
|
||||
>
|
||||
{blockNumber} 
|
||||
</MouseoverTooltip>
|
||||
</ExternalLink>
|
||||
</StyledPollingBlockNumber>
|
||||
<StyledPollingDot warning={warning}>{isMounting && <Spinner warning={warning} />}</StyledPollingDot>{' '}
|
||||
</StyledPolling>
|
||||
{warning && <ChainConnectivityWarning />}
|
||||
</RowFixed>
|
||||
)
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { ApplicationModal } from 'state/application/reducer'
|
||||
const Bag = lazy(() => import('nft/components/bag/Bag'))
|
||||
const TransactionCompleteModal = lazy(() => import('nft/components/collection/TransactionCompleteModal'))
|
||||
const AirdropModal = lazy(() => import('components/AirdropModal'))
|
||||
const MetaMaskConnectionErrorModal = lazy(() => import('components/MetaMaskConnectionErrorModal'))
|
||||
|
||||
export default function TopLevelModals() {
|
||||
const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM)
|
||||
@@ -30,7 +29,6 @@ export default function TopLevelModals() {
|
||||
<Bag />
|
||||
<TransactionCompleteModal />
|
||||
<AirdropModal />
|
||||
<MetaMaskConnectionErrorModal />
|
||||
{fiatOnrampFlagEnabled && <FiatOnrampModal />}
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -126,15 +126,6 @@ const FiatOnrampAvailabilityExternalLink = styled(ExternalLink)`
|
||||
margin-left: 6px;
|
||||
width: 14px;
|
||||
`
|
||||
const FlexContainer = styled.div`
|
||||
display: flex;
|
||||
`
|
||||
|
||||
const StatusWrapper = styled.div`
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
width: 70%;
|
||||
`
|
||||
|
||||
const TruncatedTextStyle = css`
|
||||
text-overflow: ellipsis;
|
||||
@@ -142,8 +133,14 @@ const TruncatedTextStyle = css`
|
||||
white-space: nowrap;
|
||||
`
|
||||
|
||||
const AccountNamesWrapper = styled.div`
|
||||
const FlexContainer = styled.div`
|
||||
${TruncatedTextStyle}
|
||||
padding-right: 4px;
|
||||
display: inline-flex;
|
||||
`
|
||||
|
||||
const AccountNamesWrapper = styled.div`
|
||||
min-width: 0;
|
||||
margin-right: 8px;
|
||||
`
|
||||
|
||||
@@ -280,19 +277,17 @@ const AuthenticatedHeader = () => {
|
||||
return (
|
||||
<>
|
||||
<HeaderWrapper>
|
||||
<StatusWrapper>
|
||||
<FlexContainer>
|
||||
<StatusIcon connectionType={connectionType} size={24} />
|
||||
{ENSName ? (
|
||||
<AccountNamesWrapper>
|
||||
<ENSNameContainer>{ENSName}</ENSNameContainer>
|
||||
<AccountContainer>{account && shortenAddress(account, 2, 4)}</AccountContainer>
|
||||
</AccountNamesWrapper>
|
||||
) : (
|
||||
<ThemedText.SubHeader marginTop="2.5px">{account && shortenAddress(account, 2, 4)}</ThemedText.SubHeader>
|
||||
)}
|
||||
</FlexContainer>
|
||||
</StatusWrapper>
|
||||
<FlexContainer>
|
||||
<StatusIcon connectionType={connectionType} size={24} />
|
||||
{ENSName ? (
|
||||
<AccountNamesWrapper>
|
||||
<ENSNameContainer>{ENSName}</ENSNameContainer>
|
||||
<AccountContainer>{account && shortenAddress(account, 2, 4)}</AccountContainer>
|
||||
</AccountNamesWrapper>
|
||||
) : (
|
||||
<ThemedText.SubHeader marginTop="2.5px">{account && shortenAddress(account, 2, 4)}</ThemedText.SubHeader>
|
||||
)}
|
||||
</FlexContainer>
|
||||
<IconContainer>
|
||||
<IconButton onClick={copy} Icon={Copy}>
|
||||
{isCopied ? <Trans>Copied!</Trans> : <Trans>Copy</Trans>}
|
||||
|
||||
@@ -57,7 +57,7 @@ const StyledChevron = styled(ChevronLeft)`
|
||||
const BackSection = styled.div`
|
||||
position: absolute;
|
||||
background-color: ${({ theme }) => theme.backgroundSurface};
|
||||
width: -webkit-fill-available;
|
||||
width: fill-available;
|
||||
margin: 0px 2vw 0px 0px;
|
||||
padding: 0px 0px 2vh 0px;
|
||||
color: ${({ theme }) => theme.textSecondary};
|
||||
|
||||
@@ -22,9 +22,6 @@ jest.mock('.../../state/application/hooks', () => {
|
||||
useToggleWalletModal: () => {
|
||||
return
|
||||
},
|
||||
useToggleMetaMaskConnectionErrorModal: () => {
|
||||
return
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -73,8 +70,8 @@ it('loads Wallet Modal on desktop', async () => {
|
||||
|
||||
it('loads Wallet Modal on desktop with generic Injected', async () => {
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Browser Wallet')).toBeInTheDocument()
|
||||
@@ -85,8 +82,8 @@ it('loads Wallet Modal on desktop with generic Injected', async () => {
|
||||
|
||||
it('loads Wallet Modal on desktop with MetaMask installed', async () => {
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('MetaMask')).toBeInTheDocument()
|
||||
@@ -99,8 +96,8 @@ it('loads Wallet Modal on mobile', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Open in Coinbase Wallet')).toBeInTheDocument()
|
||||
@@ -112,8 +109,8 @@ it('loads Wallet Modal on MetaMask browser', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(false)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('MetaMask')).toBeInTheDocument()
|
||||
@@ -124,8 +121,8 @@ it('loads Wallet Modal on Coinbase Wallet browser', async () => {
|
||||
UserAgentMock.isMobile = true
|
||||
|
||||
jest.spyOn(connectionUtils, 'getIsInjected').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getHasMetaMaskExtensionInstalled').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getHasCoinbaseExtensionInstalled').mockReturnValue(true)
|
||||
jest.spyOn(connectionUtils, 'getIsMetaMaskWallet').mockReturnValue(false)
|
||||
jest.spyOn(connectionUtils, 'getIsCoinbaseWallet').mockReturnValue(true)
|
||||
|
||||
render(<WalletModal pendingTransactions={[]} confirmedTransactions={[]} />)
|
||||
expect(screen.getByText('Coinbase Wallet')).toBeInTheDocument()
|
||||
|
||||
@@ -10,9 +10,9 @@ import { networkConnection } from 'connection'
|
||||
import {
|
||||
getConnection,
|
||||
getConnectionName,
|
||||
getHasCoinbaseExtensionInstalled,
|
||||
getHasMetaMaskExtensionInstalled,
|
||||
getIsCoinbaseWallet,
|
||||
getIsInjected,
|
||||
getIsMetaMaskWallet,
|
||||
} from 'connection/utils'
|
||||
import usePrevious from 'hooks/usePrevious'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
@@ -253,8 +253,8 @@ export default function WalletModal({
|
||||
|
||||
function getOptions() {
|
||||
const isInjected = getIsInjected()
|
||||
const hasMetaMaskExtension = getHasMetaMaskExtensionInstalled()
|
||||
const hasCoinbaseExtension = getHasCoinbaseExtensionInstalled()
|
||||
const hasMetaMaskExtension = getIsMetaMaskWallet()
|
||||
const hasCoinbaseExtension = getIsCoinbaseWallet()
|
||||
|
||||
const isCoinbaseWalletBrowser = isMobile && hasCoinbaseExtension
|
||||
const isMetaMaskBrowser = isMobile && hasMetaMaskExtension
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useWeb3React, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core'
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { Connection } from 'connection'
|
||||
import { setMetMaskErrorHandler } from 'connection'
|
||||
import { getConnectionName } from 'connection/utils'
|
||||
import { isSupportedChain } from 'constants/chains'
|
||||
import { RPC_PROVIDERS } from 'constants/providers'
|
||||
@@ -9,19 +8,8 @@ import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/tra
|
||||
import useEagerlyConnect from 'hooks/useEagerlyConnect'
|
||||
import useOrderedConnections from 'hooks/useOrderedConnections'
|
||||
import { ReactNode, useEffect, useMemo } from 'react'
|
||||
import { useToggleMetaMaskConnectionErrorModal } from 'state/application/hooks'
|
||||
|
||||
export default function Web3Provider({ children }: { children: ReactNode }) {
|
||||
// https://github.com/MetaMask/metamask-extension/issues/13375
|
||||
const toggleMetaMaskConnectionErrorModal = useToggleMetaMaskConnectionErrorModal()
|
||||
useEffect(() => {
|
||||
setMetMaskErrorHandler((error) => {
|
||||
if (error.code === 1013) {
|
||||
toggleMetaMaskConnectionErrorModal()
|
||||
}
|
||||
})
|
||||
}, [toggleMetaMaskConnectionErrorModal])
|
||||
|
||||
useEagerlyConnect()
|
||||
const connections = useOrderedConnections()
|
||||
const connectors: [Connector, Web3ReactHooks][] = connections.map(({ hooks, connector }) => [connector, hooks])
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
getTokenAddress,
|
||||
} from 'lib/utils/analytics'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useToggleWalletModal } from 'state/application/hooks'
|
||||
import { useIsDarkMode } from 'state/user/hooks'
|
||||
import { computeRealizedPriceImpact } from 'utils/prices'
|
||||
import { switchChain } from 'utils/switchChain'
|
||||
@@ -57,6 +58,12 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
|
||||
const { settings } = useSyncWidgetSettings()
|
||||
const { transactions } = useSyncWidgetTransactions()
|
||||
|
||||
const toggleWalletModal = useToggleWalletModal()
|
||||
const onConnectWalletClick = useCallback(() => {
|
||||
toggleWalletModal()
|
||||
return false // prevents the in-widget wallet modal from opening
|
||||
}, [toggleWalletModal])
|
||||
|
||||
const onSwitchChain = useCallback(
|
||||
// TODO(WEB-1757): Widget should not break if this rejects - upstream the catch to ignore it.
|
||||
({ chainId }: AddEthereumChainParameter) => switchChain(connector, Number(chainId)).catch(() => undefined),
|
||||
@@ -154,6 +161,7 @@ export default function Widget({ token, onTokenChange, onReviewSwapClick }: Widg
|
||||
theme={theme}
|
||||
width={WIDGET_WIDTH}
|
||||
// defaultChainId is excluded - it is always inferred from the passed provider
|
||||
onConnectWalletClick={onConnectWalletClick}
|
||||
provider={provider}
|
||||
onSwitchChain={onSwitchChain}
|
||||
tokenList={EMPTY_TOKEN_LIST} // prevents loading the default token list, as we use our own token selector UI
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { Slippage, SwapController, SwapEventHandlers } from '@uniswap/widgets'
|
||||
import { RouterPreference, Slippage, SwapController, SwapEventHandlers } from '@uniswap/widgets'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useUserSlippageTolerance, useUserTransactionTTL } from 'state/user/hooks'
|
||||
@@ -37,6 +37,8 @@ export function useSyncWidgetSettings() {
|
||||
[setAppSlippage]
|
||||
)
|
||||
|
||||
const [routerPreference, onRouterPreferenceChange] = useState(RouterPreference.API)
|
||||
|
||||
const onSettingsReset = useCallback(() => {
|
||||
setWidgetTtl(undefined)
|
||||
setAppTtl(DEFAULT_DEADLINE_FROM_NOW)
|
||||
@@ -46,11 +48,15 @@ export function useSyncWidgetSettings() {
|
||||
|
||||
const settings: SwapController['settings'] = useMemo(() => {
|
||||
const auto = appSlippage === 'auto'
|
||||
return { slippage: { auto, max: widgetSlippage }, transactionTtl: widgetTtl }
|
||||
}, [widgetSlippage, widgetTtl, appSlippage])
|
||||
return {
|
||||
slippage: { auto, max: widgetSlippage },
|
||||
transactionTtl: widgetTtl,
|
||||
routerPreference,
|
||||
}
|
||||
}, [appSlippage, widgetSlippage, widgetTtl, routerPreference])
|
||||
const settingsHandlers: SwapEventHandlers = useMemo(
|
||||
() => ({ onSettingsReset, onSlippageChange, onTransactionDeadlineChange }),
|
||||
[onSettingsReset, onSlippageChange, onTransactionDeadlineChange]
|
||||
() => ({ onSettingsReset, onSlippageChange, onTransactionDeadlineChange, onRouterPreferenceChange }),
|
||||
[onSettingsReset, onSlippageChange, onTransactionDeadlineChange, onRouterPreferenceChange]
|
||||
)
|
||||
|
||||
return { settings: { settings, ...settingsHandlers } }
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
import { Theme } from '@uniswap/widgets'
|
||||
import { darkTheme, lightTheme } from 'theme/colors'
|
||||
|
||||
const fonts = {
|
||||
fontFamily: 'Inter custom',
|
||||
}
|
||||
|
||||
export const LIGHT_THEME = {
|
||||
export const LIGHT_THEME: Theme = {
|
||||
// surface
|
||||
container: lightTheme.backgroundSurface,
|
||||
interactive: lightTheme.backgroundInteractive,
|
||||
module: lightTheme.backgroundModule,
|
||||
accent: lightTheme.accentAction,
|
||||
dialog: lightTheme.backgroundBackdrop,
|
||||
accentSoft: lightTheme.accentActionSoft,
|
||||
container: lightTheme.backgroundSurface,
|
||||
module: lightTheme.backgroundModule,
|
||||
interactive: lightTheme.backgroundInteractive,
|
||||
outline: lightTheme.backgroundOutline,
|
||||
dialog: lightTheme.backgroundBackdrop,
|
||||
scrim: lightTheme.backgroundScrim,
|
||||
// text
|
||||
onAccent: lightTheme.white,
|
||||
primary: lightTheme.textPrimary,
|
||||
secondary: lightTheme.textSecondary,
|
||||
hint: lightTheme.textTertiary,
|
||||
onInteractive: lightTheme.accentTextDarkPrimary,
|
||||
// shadow
|
||||
deepShadow: lightTheme.deepShadow,
|
||||
networkDefaultShadow: lightTheme.networkDefaultShadow,
|
||||
|
||||
// state
|
||||
success: lightTheme.accentSuccess,
|
||||
warning: lightTheme.accentWarning,
|
||||
@@ -24,18 +33,25 @@ export const LIGHT_THEME = {
|
||||
...fonts,
|
||||
}
|
||||
|
||||
export const DARK_THEME = {
|
||||
export const DARK_THEME: Theme = {
|
||||
// surface
|
||||
container: darkTheme.backgroundSurface,
|
||||
interactive: darkTheme.backgroundInteractive,
|
||||
module: darkTheme.backgroundModule,
|
||||
accent: darkTheme.accentAction,
|
||||
dialog: darkTheme.backgroundBackdrop,
|
||||
accentSoft: darkTheme.accentActionSoft,
|
||||
container: darkTheme.backgroundSurface,
|
||||
module: darkTheme.backgroundModule,
|
||||
interactive: darkTheme.backgroundInteractive,
|
||||
outline: darkTheme.backgroundOutline,
|
||||
dialog: darkTheme.backgroundBackdrop,
|
||||
scrim: darkTheme.backgroundScrim,
|
||||
// text
|
||||
onAccent: darkTheme.white,
|
||||
primary: darkTheme.textPrimary,
|
||||
secondary: darkTheme.textSecondary,
|
||||
hint: darkTheme.textTertiary,
|
||||
onInteractive: darkTheme.accentTextLightPrimary,
|
||||
// shadow
|
||||
deepShadow: darkTheme.deepShadow,
|
||||
networkDefaultShadow: darkTheme.networkDefaultShadow,
|
||||
// state
|
||||
success: darkTheme.accentSuccess,
|
||||
warning: darkTheme.accentWarning,
|
||||
|
||||
@@ -25,22 +25,10 @@ export interface Connection {
|
||||
type: ConnectionType
|
||||
}
|
||||
|
||||
type MetaMaskError = Error & { code: number }
|
||||
|
||||
let metaMaskErrorHandler: (error: MetaMaskError) => void | undefined
|
||||
export function setMetMaskErrorHandler(errorHandler: typeof metaMaskErrorHandler) {
|
||||
metaMaskErrorHandler = errorHandler
|
||||
}
|
||||
|
||||
function onError(error: Error) {
|
||||
console.debug(`web3-react error: ${error}`)
|
||||
}
|
||||
|
||||
function onMetaMaskError(error: Error) {
|
||||
onError(error)
|
||||
metaMaskErrorHandler?.(error as MetaMaskError)
|
||||
}
|
||||
|
||||
const [web3Network, web3NetworkHooks] = initializeConnector<Network>(
|
||||
(actions) => new Network({ actions, urlMap: RPC_PROVIDERS, defaultChainId: 1 })
|
||||
)
|
||||
@@ -50,9 +38,7 @@ export const networkConnection: Connection = {
|
||||
type: ConnectionType.NETWORK,
|
||||
}
|
||||
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>(
|
||||
(actions) => new MetaMask({ actions, onError: onMetaMaskError })
|
||||
)
|
||||
const [web3Injected, web3InjectedHooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions, onError }))
|
||||
export const injectedConnection: Connection = {
|
||||
connector: web3Injected,
|
||||
hooks: web3InjectedHooks,
|
||||
|
||||
@@ -12,18 +12,21 @@ export function getIsInjected(): boolean {
|
||||
return Boolean(window.ethereum)
|
||||
}
|
||||
|
||||
export function getHasMetaMaskExtensionInstalled(): boolean {
|
||||
return window.ethereum?.isMetaMask ?? false
|
||||
export function getIsBraveWallet(): boolean {
|
||||
return window.ethereum?.isBraveWallet ?? false
|
||||
}
|
||||
|
||||
export function getHasCoinbaseExtensionInstalled(): boolean {
|
||||
export function getIsMetaMaskWallet(): boolean {
|
||||
// When using Brave browser, `isMetaMask` is set to true when using the built-in wallet
|
||||
// This function should return true only when using the MetaMask extension
|
||||
// https://wallet-docs.brave.com/ethereum/wallet-detection#compatability-with-metamask
|
||||
return (window.ethereum?.isMetaMask ?? false) && !getIsBraveWallet()
|
||||
}
|
||||
|
||||
export function getIsCoinbaseWallet(): boolean {
|
||||
return window.ethereum?.isCoinbaseWallet ?? false
|
||||
}
|
||||
|
||||
export function getIsMetaMask(connectionType: ConnectionType): boolean {
|
||||
return connectionType === ConnectionType.INJECTED && getHasMetaMaskExtensionInstalled()
|
||||
}
|
||||
|
||||
const CONNECTIONS = [
|
||||
gnosisSafeConnection,
|
||||
injectedConnection,
|
||||
@@ -56,7 +59,7 @@ export function getConnection(c: Connector | ConnectionType) {
|
||||
|
||||
export function getConnectionName(
|
||||
connectionType: ConnectionType,
|
||||
hasMetaMaskExtension: boolean = getHasMetaMaskExtensionInstalled()
|
||||
hasMetaMaskExtension: boolean = getIsMetaMaskWallet()
|
||||
) {
|
||||
switch (connectionType) {
|
||||
case ConnectionType.INJECTED:
|
||||
|
||||
@@ -42,7 +42,7 @@ interface L1ChainInfo extends BaseChainInfo {
|
||||
readonly defaultListUrl?: string
|
||||
}
|
||||
|
||||
export interface L2ChainInfo extends BaseChainInfo {
|
||||
interface L2ChainInfo extends BaseChainInfo {
|
||||
readonly networkType: NetworkType.L2
|
||||
readonly bridge: string
|
||||
readonly statusPage?: string
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
export function useIsLandingPage() {
|
||||
const { pathname } = useLocation()
|
||||
return pathname.endsWith('/')
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import useInterval from 'lib/hooks/useInterval'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
const useMachineTimeMs = (updateInterval: number): number => {
|
||||
const [now, setNow] = useState(Date.now())
|
||||
|
||||
useInterval(
|
||||
useCallback(() => {
|
||||
setNow(Date.now())
|
||||
}, []),
|
||||
updateInterval
|
||||
)
|
||||
return now
|
||||
}
|
||||
|
||||
export default useMachineTimeMs
|
||||
@@ -57,7 +57,7 @@ export function useUniversalRouterSwapCallback(
|
||||
gasEstimate = await provider.estimateGas(tx)
|
||||
} catch (gasError) {
|
||||
console.warn(gasError)
|
||||
throw new InvalidSwapError('Gas estimation failed. Wait a few minutes and try again.')
|
||||
throw new Error('Your swap is expected to fail')
|
||||
}
|
||||
const gasLimit = calculateGasMargin(gasEstimate)
|
||||
const response = await provider
|
||||
|
||||
@@ -3,6 +3,7 @@ import { parseEther } from '@ethersproject/units'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TraceEvent } from '@uniswap/analytics'
|
||||
import { BrowserEvent, InterfaceElementName, NFTEventName } from '@uniswap/analytics-events'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import Column from 'components/Column'
|
||||
import Loader from 'components/Loader'
|
||||
@@ -12,6 +13,7 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import { PayWithAnyTokenVariant, usePayWithAnyTokenFlag } from 'featureFlags/flags/payWithAnyToken'
|
||||
import { useCurrency } from 'hooks/Tokens'
|
||||
import { useBag } from 'nft/hooks/useBag'
|
||||
import { useTokenInput } from 'nft/hooks/useTokenInput'
|
||||
import { useWalletBalance } from 'nft/hooks/useWalletBalance'
|
||||
import { BagStatus } from 'nft/types'
|
||||
import { ethNumberStandardFormatter, formatWeiToDecimal } from 'nft/utils'
|
||||
@@ -141,7 +143,9 @@ export const BagFooter = ({
|
||||
const { account, chainId, connector } = useWeb3React()
|
||||
const connected = Boolean(account && chainId)
|
||||
const shouldUsePayWithAnyToken = usePayWithAnyTokenFlag() === PayWithAnyTokenVariant.Enabled
|
||||
const inputCurrency = useCurrency('ETH')
|
||||
const inputCurrency = useTokenInput((state) => state.inputCurrency)
|
||||
const setInputCurrency = useTokenInput((state) => state.setInputCurrency)
|
||||
const defaultCurrency = useCurrency('ETH')
|
||||
|
||||
const setBagExpanded = useBag((state) => state.setBagExpanded)
|
||||
const [showTokenSelector, toggleTokenSelector] = useReducer((state) => !state, false)
|
||||
@@ -193,6 +197,7 @@ export const BagFooter = ({
|
||||
}, [bagStatus, chainId, connected, connector, fetchAssets, setBagExpanded, sufficientBalance, toggleWalletModal])
|
||||
|
||||
const isPending = PENDING_BAG_STATUSES.includes(bagStatus)
|
||||
const activeCurrency = inputCurrency ?? defaultCurrency
|
||||
|
||||
return (
|
||||
<FooterContainer>
|
||||
@@ -204,9 +209,9 @@ export const BagFooter = ({
|
||||
<Trans>Pay with</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<CurrencyInput onClick={toggleTokenSelector}>
|
||||
<CurrencyLogo currency={inputCurrency} size="24px" />
|
||||
<CurrencyLogo currency={activeCurrency} size="24px" />
|
||||
<ThemedText.HeadlineSmall fontWeight={500} lineHeight="24px">
|
||||
{inputCurrency?.symbol}
|
||||
{activeCurrency?.symbol}
|
||||
</ThemedText.HeadlineSmall>
|
||||
<ChevronDown size={20} color={theme.textSecondary} />
|
||||
</CurrencyInput>
|
||||
@@ -216,7 +221,7 @@ export const BagFooter = ({
|
||||
<Trans>Total</Trans>
|
||||
</ThemedText.SubHeaderSmall>
|
||||
<ThemedText.HeadlineSmall>
|
||||
{formatWeiToDecimal(totalEthPrice.toString())} ETH
|
||||
{formatWeiToDecimal(totalEthPrice.toString())} {activeCurrency?.symbol ?? 'ETH'}
|
||||
</ThemedText.HeadlineSmall>
|
||||
<ThemedText.BodySmall color="textSecondary" lineHeight="20px">{`${ethNumberStandardFormatter(
|
||||
totalUsdPrice,
|
||||
@@ -259,7 +264,16 @@ export const BagFooter = ({
|
||||
</ActionButton>
|
||||
</TraceEvent>
|
||||
</Footer>
|
||||
{showTokenSelector && <BagTokenSelectorModal overlayClick={toggleTokenSelector} />}
|
||||
{showTokenSelector && (
|
||||
<BagTokenSelectorModal
|
||||
selectedCurrency={activeCurrency ?? undefined}
|
||||
handleCurrencySelect={(currency: Currency) => {
|
||||
setInputCurrency(currency)
|
||||
toggleTokenSelector()
|
||||
}}
|
||||
overlayClick={toggleTokenSelector}
|
||||
/>
|
||||
)}
|
||||
</FooterContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,7 +47,17 @@ const TokenSelectorContainer = styled(Column)`
|
||||
}
|
||||
`
|
||||
|
||||
export const BagTokenSelectorModal = ({ overlayClick }: { overlayClick: () => void }) => {
|
||||
interface BagTokenSelectorModalProps {
|
||||
selectedCurrency: Currency | undefined
|
||||
handleCurrencySelect: (currency: Currency) => void
|
||||
overlayClick: () => void
|
||||
}
|
||||
|
||||
export const BagTokenSelectorModal = ({
|
||||
selectedCurrency,
|
||||
handleCurrencySelect,
|
||||
overlayClick,
|
||||
}: BagTokenSelectorModalProps) => {
|
||||
const defaultTokens = useAllTokens()
|
||||
const [balances, balancesAreLoading] = useAllTokenBalances()
|
||||
const sortedTokens: Token[] = useMemo(
|
||||
@@ -90,7 +100,16 @@ export const BagTokenSelectorModal = ({ overlayClick }: { overlayClick: () => vo
|
||||
</TitleRow>
|
||||
<TokenSelectorContainer>
|
||||
{currencies.map((currency) => {
|
||||
return <CurrencyRow key={currency.isToken ? currency.wrapped.address : currency.name} currency={currency} />
|
||||
return (
|
||||
<CurrencyRow
|
||||
key={currency.isToken ? currency.wrapped.address : currency.name}
|
||||
currency={currency}
|
||||
selected={
|
||||
(!selectedCurrency && currency.isNative) || (!!selectedCurrency && selectedCurrency.equals(currency))
|
||||
}
|
||||
selectCurrency={handleCurrencySelect}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</TokenSelectorContainer>
|
||||
</ModalWrapper>
|
||||
|
||||
@@ -4,12 +4,15 @@ import Column from 'components/Column'
|
||||
import CurrencyLogo from 'components/Logo/CurrencyLogo'
|
||||
import Row from 'components/Row'
|
||||
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
|
||||
import { Check } from 'react-feather'
|
||||
import styled from 'styled-components/macro'
|
||||
import { ThemedText } from 'theme'
|
||||
|
||||
const TokenRow = styled(Row)`
|
||||
padding: 8px 0px;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const TokenInfoRow = styled(Row)`
|
||||
@@ -24,12 +27,23 @@ const StyledBalanceText = styled(ThemedText.SubHeader)`
|
||||
text-align: right;
|
||||
`
|
||||
|
||||
export const CurrencyRow = ({ currency }: { currency: Currency }) => {
|
||||
const StyledCheck = styled(Check)`
|
||||
color: ${({ theme }) => theme.accentAction};
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
interface CurrencyRowProps {
|
||||
currency: Currency
|
||||
selected: boolean
|
||||
selectCurrency: (currency: Currency) => void
|
||||
}
|
||||
|
||||
export const CurrencyRow = ({ currency, selected, selectCurrency }: CurrencyRowProps) => {
|
||||
const { account } = useWeb3React()
|
||||
const balance = useCurrencyBalance(account ?? undefined, currency)
|
||||
|
||||
return (
|
||||
<TokenRow>
|
||||
<TokenRow onClick={() => selectCurrency(currency)}>
|
||||
<TokenInfoRow>
|
||||
<CurrencyLogo currency={currency} size="36px" />
|
||||
<Column>
|
||||
@@ -42,6 +56,7 @@ export const CurrencyRow = ({ currency }: { currency: Currency }) => {
|
||||
</Column>
|
||||
</TokenInfoRow>
|
||||
{balance && <Balance balance={balance} />}
|
||||
{selected && <StyledCheck size={20} />}
|
||||
</TokenRow>
|
||||
)
|
||||
}
|
||||
|
||||
20
src/nft/hooks/useTokenInput.ts
Normal file
20
src/nft/hooks/useTokenInput.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import create from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
interface TokenInputState {
|
||||
inputCurrency: Currency | undefined
|
||||
setInputCurrency: (currency: Currency | undefined) => void
|
||||
clearInputCurrency: () => void
|
||||
}
|
||||
|
||||
export const useTokenInput = create<TokenInputState>()(
|
||||
devtools(
|
||||
(set) => ({
|
||||
inputCurrency: undefined,
|
||||
setInputCurrency: (currency) => set(() => ({ inputCurrency: currency })),
|
||||
clearInputCurrency: () => set(() => ({ inputCurrency: undefined })),
|
||||
}),
|
||||
{ name: 'useTokenInput' }
|
||||
)
|
||||
)
|
||||
@@ -20,7 +20,6 @@ import { useAnalyticsReporter } from '../components/analytics'
|
||||
import ErrorBoundary from '../components/ErrorBoundary'
|
||||
import { PageTabs } from '../components/NavBar'
|
||||
import NavBar from '../components/NavBar'
|
||||
import Polling from '../components/Polling'
|
||||
import Popups from '../components/Popups'
|
||||
import { useIsExpertMode } from '../state/user/hooks'
|
||||
import DarkModeQueryParamReader from '../theme/components/DarkModeQueryParamReader'
|
||||
@@ -194,7 +193,6 @@ export default function App() {
|
||||
</HeaderWrapper>
|
||||
<BodyWrapper>
|
||||
<Popups />
|
||||
<Polling />
|
||||
<TopLevelModals />
|
||||
<Suspense fallback={<Loader />}>
|
||||
{isLoaded ? (
|
||||
|
||||
5
src/react-app-env.d.ts
vendored
5
src/react-app-env.d.ts
vendored
@@ -8,8 +8,11 @@ interface Window {
|
||||
// walletLinkExtension is injected by the Coinbase Wallet extension
|
||||
walletLinkExtension?: any
|
||||
ethereum?: {
|
||||
// value that is populated and returns true by the Coinbase Wallet mobile dapp browser
|
||||
// set by the Coinbase Wallet mobile dapp browser
|
||||
isCoinbaseWallet?: true
|
||||
// set by the Brave browser when using built-in wallet
|
||||
isBraveWallet?: true
|
||||
// set by the MetaMask browser extension (also set by Brave browser when using built-in wallet)
|
||||
isMetaMask?: true
|
||||
autoRefreshOnNetworkChange?: boolean
|
||||
}
|
||||
|
||||
@@ -93,10 +93,6 @@ export function useCloseModal(): () => void {
|
||||
return useCallback(() => dispatch(setOpenModal(null)), [dispatch])
|
||||
}
|
||||
|
||||
export function useToggleMetaMaskConnectionErrorModal(): () => void {
|
||||
return useToggleModal(ApplicationModal.METAMASK_CONNECTION_ERROR)
|
||||
}
|
||||
|
||||
export function useOpenModal(modal: ApplicationModal): () => void {
|
||||
const dispatch = useAppDispatch()
|
||||
return useCallback(() => dispatch(setOpenModal(modal)), [dispatch, modal])
|
||||
|
||||
@@ -46,7 +46,7 @@ export function swapErrorToUserReadableMessage(error: any): string {
|
||||
if (reason?.indexOf('undefined is not an object') !== -1) {
|
||||
return t`An error occurred when trying to execute this swap. You may need to increase your slippage tolerance. If that does not work, there may be an incompatibility with the token you are trading. Note: fee on transfer and rebase tokens are incompatible with Uniswap V3.`
|
||||
}
|
||||
return t`Unknown error${reason ? `: "${reason}"` : ''}. Try increasing your slippage tolerance.
|
||||
Note: fee on transfer and rebase tokens are incompatible with Uniswap V3.`
|
||||
return t`${reason ? reason : 'Unknown error'}. Try increasing your slippage tolerance.
|
||||
Note: fee-on-transfer and rebase tokens are incompatible with Uniswap V3.`
|
||||
}
|
||||
}
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@@ -4945,10 +4945,10 @@
|
||||
react "^18.2.0"
|
||||
react-dom "^18.2.0"
|
||||
|
||||
"@uniswap/conedison@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.1.1.tgz#affec246613d1f52da3cdd0571ef8195b7b54d17"
|
||||
integrity sha512-xFHAcWRrU+/+/BInXy6SRiiNwUG0vxLWsoYgod66wWifUvnjfpItzlvJHUer1OOpLDsz0CL5Fb70vFJOGAGi8w==
|
||||
"@uniswap/conedison@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.2.1.tgz#c3dbfe14f4320fc5c60cde23c4bd70ed8a39c782"
|
||||
integrity sha512-ir6j7RQOyREXtW5YlmPjskfl7oDeHWtMFai57snThAkKgrb+8KTX5b0a5nbXeIuaW2RNHAaWTGoSoTneIHCAnQ==
|
||||
|
||||
"@uniswap/default-token-list@^2.0.0":
|
||||
version "2.2.0"
|
||||
@@ -5090,10 +5090,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.30.tgz#2103ca23b8007c59ec71718d34cdc97861c409e5"
|
||||
integrity sha512-HwY2VvkQ8lNR6ks5NqQfAtg+4IZqz3KV1T8d2DlI8emIn9uMmaoFbIOg0nzjqAVKKnZSbMTRRtUoAh6mmjRvog==
|
||||
|
||||
"@uniswap/universal-router-sdk@1.3.0", "@uniswap/universal-router-sdk@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.3.0.tgz#f44262eafe729651d383f46a647399658d45baf7"
|
||||
integrity sha512-Q7/Gw059JQDO7exxV791QzghEOiWomdvxvqidozDvkiZE7paIlSWq1vDVF4H3zB2GYy5Hu7HM8krl2l0KS9X5g==
|
||||
"@uniswap/universal-router-sdk@1.3.4", "@uniswap/universal-router-sdk@^1.3.4":
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.3.4.tgz#7b6b8e30d6faff812f224d32a832385378568160"
|
||||
integrity sha512-RIWZm48N/fiAssMOj0nMLoeN5JATKOMfbFwyVnCaFHIVMJmKEZtZLKe3QOkl2LMVnQ/nP4LVCDwHU+mdP68jCQ==
|
||||
dependencies:
|
||||
"@uniswap/permit2-sdk" "^1.2.0"
|
||||
"@uniswap/router-sdk" "^1.4.0"
|
||||
@@ -5194,24 +5194,24 @@
|
||||
"@uniswap/v3-core" "1.0.0"
|
||||
"@uniswap/v3-periphery" "^1.0.1"
|
||||
|
||||
"@uniswap/widgets@2.25.1":
|
||||
version "2.25.1"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/widgets/-/widgets-2.25.1.tgz#53d03da44587c0851d2f39516d7f9037d0c65b9d"
|
||||
integrity sha512-Jth8qd7g58W1SVRpSFNiTKjs0HmcOacpxdSKfRSOIJOcYq8vgjZ0k4W3i/bzjf8PDRtLGh4Nn09VLugtIMUVIw==
|
||||
"@uniswap/widgets@^2.26.0":
|
||||
version "2.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/widgets/-/widgets-2.26.0.tgz#ed2d2b0965fc2139874a5eaca44c8070184d9e74"
|
||||
integrity sha512-LIfd3wgJxNiPXduyxXAihOzye5LrayyPDt0/OErSr1hF3BIiQqdVc3KyHDFJ4PC0pCp4+dfE2HPH1FrClO54Uw==
|
||||
dependencies:
|
||||
"@babel/runtime" ">=7.17.0"
|
||||
"@fontsource/ibm-plex-mono" "^4.5.1"
|
||||
"@fontsource/inter" "^4.5.1"
|
||||
"@popperjs/core" "^2.4.4"
|
||||
"@reduxjs/toolkit" "^1.6.1"
|
||||
"@uniswap/conedison" "^1.1.1"
|
||||
"@uniswap/conedison" "^1.2.1"
|
||||
"@uniswap/permit2-sdk" "^1.2.0"
|
||||
"@uniswap/redux-multicall" "^1.1.8"
|
||||
"@uniswap/router-sdk" "^1.3.0"
|
||||
"@uniswap/sdk-core" "^3.0.1"
|
||||
"@uniswap/smart-order-router" "^2.10.0"
|
||||
"@uniswap/token-lists" "^1.0.0-beta.30"
|
||||
"@uniswap/universal-router-sdk" "^1.3.0"
|
||||
"@uniswap/universal-router-sdk" "^1.3.4"
|
||||
"@uniswap/v2-sdk" "^3.0.1"
|
||||
"@uniswap/v3-sdk" "^3.8.2"
|
||||
"@web3-react/core" "8.0.35-beta.0"
|
||||
@@ -5792,6 +5792,14 @@
|
||||
"@metamask/detect-provider" "^1.2.0"
|
||||
"@web3-react/types" "^8.0.20-beta.0"
|
||||
|
||||
"@web3-react/metamask@8.0.29-beta.0":
|
||||
version "8.0.29-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/metamask/-/metamask-8.0.29-beta.0.tgz#536536b8d4f22f21d3e109efaa8149939833f21b"
|
||||
integrity sha512-UPaVmNum6cJ/CwW5WYFMrm6GwiuY1hnuCYB+bV1Bs0xghdag2Laj8/mSfpFCsCHcvg1ZWTcr4bH+WyuYAHgUxw==
|
||||
dependencies:
|
||||
"@metamask/detect-provider" "^1.2.0"
|
||||
"@web3-react/types" "^8.0.20-beta.0"
|
||||
|
||||
"@web3-react/network@8.0.27-beta.0":
|
||||
version "8.0.27-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/network/-/network-8.0.27-beta.0.tgz#7cb522b02efc9d0f877ac285f350810fbf322292"
|
||||
|
||||
Reference in New Issue
Block a user