feat(polygon): polygon mainnet and testnet support (#3015)

* feat(polygon): polygon mainnet and testnet support

WIP! DO NOT USE

* fix unit test

* fix explorer links

* compute usdc prices

* - fix the header currency label
- fix unit tests

* polygon gradient colors

* chore: adding weth to common bases (#3025)

* adding weth to common bases

* adding usdc and dai

* adding usdt and wbtc

* fix a bunch of polish issues
- 3085 detection
- some wrapping stuff
- the network selector dropdown

* fix wrap/unwrap notification text on polygon

* background color per the figma

* subgraph url

* fix the re-render blinking on the network selector

* failed network switch

* clean up duplicate code in the network switching functions

* fix text color in the privacy notice on light mode

* add some routing constants for polygon

* do not show the separator in the trade route if auto router is not supported

* - network switching without a wallet connected
- remove v2 stuff from pool page when n/a
- remove WMATIC from common bases on polygon

* background colors of network alert

* oops fix background on network alert

* clean up optimism labels

* fix alignment of text on downtime warning

* finish the network alert styles

Co-authored-by: Sara Reynolds <30504811+snreynolds@users.noreply.github.com>
This commit is contained in:
Moody Salem 2021-12-22 14:25:10 -05:00 committed by GitHub
parent 7ac1ed3f52
commit f047f0d196
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1154 additions and 828 deletions

@ -0,0 +1,4 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="512" cy="512" r="512" fill="#8247E5"/>
<path d="M681.469 402.456C669.189 395.312 653.224 395.312 639.716 402.456L543.928 457.228L478.842 492.949L383.055 547.721C370.774 554.865 354.81 554.865 341.301 547.721L265.162 504.856C252.882 497.712 244.286 484.614 244.286 470.325V385.786C244.286 371.498 251.654 358.4 265.162 351.256L340.073 309.581C352.353 302.437 368.318 302.437 381.827 309.581L456.737 351.256C469.018 358.4 477.614 371.498 477.614 385.786V440.558L542.7 403.646V348.874C542.7 334.586 535.332 321.488 521.824 314.344L383.055 235.758C370.774 228.614 354.81 228.614 341.301 235.758L200.076 314.344C186.567 321.488 179.199 334.586 179.199 348.874V507.237C179.199 521.525 186.567 534.623 200.076 541.767L341.301 620.353C353.582 627.498 369.546 627.498 383.055 620.353L478.842 566.772L543.928 529.86L639.716 476.279C651.996 469.135 667.961 469.135 681.469 476.279L756.38 517.953C768.66 525.098 777.257 538.195 777.257 552.484V637.023C777.257 651.312 769.888 664.409 756.38 671.553L681.469 714.419C669.189 721.563 653.224 721.563 639.716 714.419L564.805 672.744C552.525 665.6 543.928 652.502 543.928 638.214V583.442L478.842 620.353V675.125C478.842 689.414 486.21 702.512 499.719 709.656L640.944 788.242C653.224 795.386 669.189 795.386 682.697 788.242L823.922 709.656C836.203 702.512 844.799 689.414 844.799 675.125V516.763C844.799 502.474 837.431 489.377 823.922 482.232L681.469 402.456Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 38.4 33.5" style="enable-background:new 0 0 38.4 33.5;" xml:space="preserve">
<style type="text/css">
.st0{fill:#8247E5;}
</style>
<g>
<path class="st0" d="M29,10.2c-0.7-0.4-1.6-0.4-2.4,0L21,13.5l-3.8,2.1l-5.5,3.3c-0.7,0.4-1.6,0.4-2.4,0L5,16.3
c-0.7-0.4-1.2-1.2-1.2-2.1v-5c0-0.8,0.4-1.6,1.2-2.1l4.3-2.5c0.7-0.4,1.6-0.4,2.4,0L16,7.2c0.7,0.4,1.2,1.2,1.2,2.1v3.3l3.8-2.2V7
c0-0.8-0.4-1.6-1.2-2.1l-8-4.7c-0.7-0.4-1.6-0.4-2.4,0L1.2,5C0.4,5.4,0,6.2,0,7v9.4c0,0.8,0.4,1.6,1.2,2.1l8.1,4.7
c0.7,0.4,1.6,0.4,2.4,0l5.5-3.2l3.8-2.2l5.5-3.2c0.7-0.4,1.6-0.4,2.4,0l4.3,2.5c0.7,0.4,1.2,1.2,1.2,2.1v5c0,0.8-0.4,1.6-1.2,2.1
L29,28.8c-0.7,0.4-1.6,0.4-2.4,0l-4.3-2.5c-0.7-0.4-1.2-1.2-1.2-2.1V21l-3.8,2.2v3.3c0,0.8,0.4,1.6,1.2,2.1l8.1,4.7
c0.7,0.4,1.6,0.4,2.4,0l8.1-4.7c0.7-0.4,1.2-1.2,1.2-2.1V17c0-0.8-0.4-1.6-1.2-2.1L29,10.2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { Fraction, TradeType } from '@uniswap/sdk-core' import { Fraction, TradeType } from '@uniswap/sdk-core'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { nativeOnChain } from '../../constants/tokens'
import { useCurrency, useToken } from '../../hooks/Tokens' import { useCurrency, useToken } from '../../hooks/Tokens'
import useENSName from '../../hooks/useENSName' import useENSName from '../../hooks/useENSName'
import { VoteOption } from '../../state/governance/types' import { VoteOption } from '../../state/governance/types'
@ -130,18 +131,33 @@ function DelegateSummary({ info: { delegatee } }: { info: DelegateTransactionInf
return <Trans>Delegate voting power to {ENSName ?? delegatee}</Trans> return <Trans>Delegate voting power to {ENSName ?? delegatee}</Trans>
} }
function WrapSummary({ info: { currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) { function WrapSummary({ info: { chainId, currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) {
const native = chainId ? nativeOnChain(chainId) : undefined
if (unwrapped) { if (unwrapped) {
return ( return (
<Trans> <Trans>
Unwrap <FormattedCurrencyAmount rawAmount={currencyAmountRaw} symbol={'WETH'} decimals={18} sigFigs={6} /> to Unwrap{' '}
ETH <FormattedCurrencyAmount
rawAmount={currencyAmountRaw}
symbol={native?.wrapped?.symbol ?? 'WETH'}
decimals={18}
sigFigs={6}
/>{' '}
to {native?.symbol ?? 'ETH'}
</Trans> </Trans>
) )
} else { } else {
return ( return (
<Trans> <Trans>
Wrap <FormattedCurrencyAmount rawAmount={currencyAmountRaw} symbol={'ETH'} decimals={18} sigFigs={6} /> to WETH Wrap{' '}
<FormattedCurrencyAmount
rawAmount={currencyAmountRaw}
symbol={native?.symbol ?? 'ETH'}
decimals={18}
sigFigs={6}
/>{' '}
to {native?.wrapped?.symbol ?? 'WETH'}
</Trans> </Trans>
) )
} }

@ -1,11 +1,12 @@
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import EthereumLogo from 'assets/images/ethereum-logo.png'
import MaticLogo from 'assets/svg/matic-token-icon.svg'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import useHttpLocations from 'hooks/useHttpLocations'
import React, { useMemo } from 'react' import React, { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import EthereumLogo from '../../assets/images/ethereum-logo.png'
import useHttpLocations from '../../hooks/useHttpLocations'
import { WrappedTokenInfo } from '../../state/lists/wrappedTokenInfo'
import Logo from '../Logo' import Logo from '../Logo'
type Network = 'ethereum' | 'arbitrum' | 'optimism' type Network = 'ethereum' | 'arbitrum' | 'optimism'
@ -34,7 +35,7 @@ export const getTokenLogoURL = (
} }
} }
const StyledEthereumLogo = styled.img<{ size: string }>` const StyledNativeLogo = styled.img<{ size: string }>`
width: ${({ size }) => size}; width: ${({ size }) => size};
height: ${({ size }) => size}; height: ${({ size }) => size};
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%); background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
@ -87,7 +88,17 @@ export default function CurrencyLogo({
}, [currency, uriLocations]) }, [currency, uriLocations])
if (currency?.isNative) { if (currency?.isNative) {
return <StyledEthereumLogo src={EthereumLogo} alt="ethereum logo" size={size} style={style} {...rest} /> let nativeLogoUrl: string
switch (currency.chainId) {
case SupportedChainId.POLYGON_MUMBAI:
case SupportedChainId.POLYGON:
nativeLogoUrl = MaticLogo
break
default:
nativeLogoUrl = EthereumLogo
break
}
return <StyledNativeLogo src={nativeLogoUrl} alt="ethereum logo" size={size} style={style} {...rest} />
} }
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} {...rest} /> return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} {...rest} />

@ -1,10 +1,12 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { L2_CHAIN_IDS, SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3' import { useActiveWeb3React } from 'hooks/web3'
import { AlertOctagon } from 'react-feather' import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ExternalLink } from 'theme' import { ExternalLink } from 'theme'
import { isL2ChainId } from '../../utils/chains'
const Root = styled.div` const Root = styled.div`
background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')}; background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')};
border-radius: 18px; border-radius: 18px;
@ -18,7 +20,6 @@ const Root = styled.div`
max-width: 880px; max-width: 880px;
` `
const WarningIcon = styled(AlertOctagon)` const WarningIcon = styled(AlertOctagon)`
display: block;
margin: auto 16px auto 0; margin: auto 16px auto 0;
min-height: 22px; min-height: 22px;
min-width: 22px; min-width: 22px;
@ -28,50 +29,54 @@ const ReadMoreLink = styled(ExternalLink)`
text-decoration: underline; text-decoration: underline;
` `
export default function DowntimeWarning() { function Wrapper({ children }: { children: React.ReactNode }) {
const { chainId } = useActiveWeb3React()
if (!chainId || !L2_CHAIN_IDS.includes(chainId)) {
return null
}
const Content = () => {
switch (chainId) {
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
return (
<div>
<Trans>
Optimistic Ethereum is in Beta and may experience downtime. Optimism expects planned downtime to upgrade
the network in the near future. During downtime, your position will not earn fees and you will be unable
to remove liquidity.{' '}
<ReadMoreLink href="https://help.uniswap.org/en/articles/5406082-what-happens-if-the-optimistic-ethereum-network-experiences-downtime">
Read more.
</ReadMoreLink>
</Trans>
</div>
)
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
return (
<div>
<Trans>
Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you
will be unable to remove liquidity.{' '}
<ReadMoreLink href="https://help.uniswap.org/en/articles/5576122-arbitrum-network-downtime">
Read more.
</ReadMoreLink>
</Trans>
</div>
)
default:
return null
}
}
return ( return (
<Root> <Root>
<WarningIcon /> <WarningIcon />
<Content /> <div>{children}</div>
</Root> </Root>
) )
} }
/**
* Shows a downtime warning for the network if it's relevant
*/
export default function DowntimeWarning() {
const { chainId } = useActiveWeb3React()
if (!isL2ChainId(chainId)) {
return null
}
switch (chainId) {
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
return (
<Wrapper>
<Trans>
Optimism is in Beta and may experience downtime. Optimism expects planned downtime to upgrade the network in
the near future. During downtime, your position will not earn fees and you will be unable to remove
liquidity.{' '}
<ReadMoreLink href="https://help.uniswap.org/en/articles/5406082-what-happens-if-the-optimistic-ethereum-network-experiences-downtime">
Read more.
</ReadMoreLink>
</Trans>
</Wrapper>
)
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
return (
<Wrapper>
<Trans>
Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you
will be unable to remove liquidity.{' '}
<ReadMoreLink href="https://help.uniswap.org/en/articles/5576122-arbitrum-network-downtime">
Read more.
</ReadMoreLink>
</Trans>
</Wrapper>
)
default:
return null
}
}

@ -1,22 +1,16 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
ARBITRUM_HELP_CENTER_LINK,
CHAIN_INFO,
L2_CHAIN_IDS,
OPTIMISM_HELP_CENTER_LINK,
SupportedChainId,
SupportedL2ChainId,
} from 'constants/chains'
import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useActiveWeb3React } from 'hooks/web3' import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useRef } from 'react' import { useCallback, useRef } from 'react'
import { ArrowDownCircle, ChevronDown } from 'react-feather' import { ArrowDownCircle, ChevronDown } from 'react-feather'
import { useModalOpen, useToggleModal } from 'state/application/hooks' import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer' import { addPopup, ApplicationModal } from 'state/application/reducer'
import { useAppSelector } from 'state/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme' import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { switchToNetwork } from 'utils/switchToNetwork'
import { useAppDispatch } from '../../state/hooks'
import { switchToNetwork } from '../../utils/switchToNetwork'
const ActiveRowLinkList = styled.div` const ActiveRowLinkList = styled.div`
display: flex; display: flex;
@ -136,7 +130,7 @@ const SelectorWrapper = styled.div`
const StyledChevronDown = styled(ChevronDown)` const StyledChevronDown = styled(ChevronDown)`
width: 12px; width: 12px;
` `
const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => { const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
switch (chainId) { switch (chainId) {
case SupportedChainId.ARBITRUM_ONE: case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY: case SupportedChainId.ARBITRUM_RINKEBY:
@ -144,11 +138,14 @@ const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
case SupportedChainId.OPTIMISM: case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN: case SupportedChainId.OPTIMISTIC_KOVAN:
return <Trans>Optimism Gateway</Trans> return <Trans>Optimism Gateway</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygon Bridge</Trans>
default: default:
return <Trans>Bridge</Trans> return <Trans>Bridge</Trans>
} }
} }
const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => { const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
switch (chainId) { switch (chainId) {
case SupportedChainId.ARBITRUM_ONE: case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY: case SupportedChainId.ARBITRUM_RINKEBY:
@ -156,91 +153,108 @@ const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
case SupportedChainId.OPTIMISM: case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN: case SupportedChainId.OPTIMISTIC_KOVAN:
return <Trans>Optimistic Etherscan</Trans> return <Trans>Optimistic Etherscan</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygonscan</Trans>
default: default:
return <Trans>Explorer</Trans> return <Trans>Etherscan</Trans>
} }
} }
function Row({
targetChain,
onSelectChain,
}: {
targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void
}) {
const { library, chainId } = useActiveWeb3React()
if (!library || !chainId) {
return null
}
const active = chainId === targetChain
const { helpCenterUrl, explorer, bridge, label, logoUrl } = CHAIN_INFO[targetChain]
const rowContent = (
<FlyoutRow onClick={() => onSelectChain(targetChain)} active={active}>
<Logo src={logoUrl} />
<NetworkLabel>{label}</NetworkLabel>
{chainId === targetChain && <FlyoutRowActiveIndicator />}
</FlyoutRow>
)
if (active) {
return (
<ActiveRowWrapper>
{rowContent}
<ActiveRowLinkList>
{bridge ? (
<ExternalLink href={bridge}>
<BridgeLabel chainId={chainId} /> <LinkOutCircle />
</ExternalLink>
) : null}
{explorer ? (
<ExternalLink href={explorer}>
<ExplorerLabel chainId={chainId} /> <LinkOutCircle />
</ExternalLink>
) : null}
{helpCenterUrl ? (
<ExternalLink href={helpCenterUrl}>
<Trans>Help Center</Trans> <LinkOutCircle />
</ExternalLink>
) : null}
</ActiveRowLinkList>
</ActiveRowWrapper>
)
}
return rowContent
}
export default function NetworkSelector() { export default function NetworkSelector() {
const { chainId, library } = useActiveWeb3React() const { chainId, library } = useActiveWeb3React()
const node = useRef<HTMLDivElement>() const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR) const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR)
const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR) const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
useOnClickOutside(node, open ? toggle : undefined) useOnClickOutside(node, open ? toggle : undefined)
const implements3085 = useAppSelector((state) => state.application.implements3085)
const info = chainId ? CHAIN_INFO[chainId] : undefined const info = chainId ? CHAIN_INFO[chainId] : undefined
const isOnL2 = chainId ? L2_CHAIN_IDS.includes(chainId) : false const dispatch = useAppDispatch()
const showSelector = Boolean(implements3085 || isOnL2)
const mainnetInfo = CHAIN_INFO[SupportedChainId.MAINNET]
const conditionalToggle = useCallback(() => { const handleRowClick = useCallback(
if (showSelector) { (targetChain: number) => {
toggle() if (!library) return
} switchToNetwork({ library, chainId: targetChain })
}, [showSelector, toggle]) .then(() => toggle())
.catch((error) => {
console.error('Failed to switch networks', error)
toggle()
dispatch(addPopup({ content: { failedSwitchNetwork: targetChain }, key: `failed-network-switch` }))
})
},
[dispatch, library, toggle]
)
if (!chainId || !info || !library) { if (!chainId || !info || !library) {
return null return null
} }
function Row({ targetChain }: { targetChain: number }) {
if (!library || !chainId || (!implements3085 && targetChain !== chainId)) {
return null
}
const handleRowClick = () => {
switchToNetwork({ library, chainId: targetChain })
toggle()
}
const active = chainId === targetChain
const hasExtendedInfo = L2_CHAIN_IDS.includes(targetChain)
const isOptimism = targetChain === SupportedChainId.OPTIMISM
const rowText = `${CHAIN_INFO[targetChain].label}${isOptimism ? ' (Optimism)' : ''}`
const RowContent = () => (
<FlyoutRow onClick={handleRowClick} active={active}>
<Logo src={CHAIN_INFO[targetChain].logoUrl} />
<NetworkLabel>{rowText}</NetworkLabel>
{chainId === targetChain && <FlyoutRowActiveIndicator />}
</FlyoutRow>
)
const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
if (active && hasExtendedInfo) {
return (
<ActiveRowWrapper>
<RowContent />
<ActiveRowLinkList>
<ExternalLink href={CHAIN_INFO[targetChain as SupportedL2ChainId].bridge}>
<BridgeText chainId={chainId} /> <LinkOutCircle />
</ExternalLink>
<ExternalLink href={CHAIN_INFO[targetChain].explorer}>
<ExplorerText chainId={chainId} /> <LinkOutCircle />
</ExternalLink>
<ExternalLink href={helpCenterLink}>
<Trans>Help Center</Trans> <LinkOutCircle />
</ExternalLink>
</ActiveRowLinkList>
</ActiveRowWrapper>
)
}
return <RowContent />
}
return ( return (
<SelectorWrapper ref={node as any}> <SelectorWrapper ref={node as any}>
<SelectorControls onClick={conditionalToggle} interactive={showSelector}> <SelectorControls onClick={toggle} interactive>
<SelectorLogo interactive={showSelector} src={info.logoUrl || mainnetInfo.logoUrl} /> <SelectorLogo interactive src={info.logoUrl} />
<SelectorLabel>{info.label}</SelectorLabel> <SelectorLabel>{info.label}</SelectorLabel>
{showSelector && <StyledChevronDown />} <StyledChevronDown />
</SelectorControls> </SelectorControls>
{open && ( {open && (
<FlyoutMenu> <FlyoutMenu>
<FlyoutHeader> <FlyoutHeader>
<Trans>Select a network</Trans> <Trans>Select a network</Trans>
</FlyoutHeader> </FlyoutHeader>
<Row targetChain={SupportedChainId.MAINNET} /> <Row onSelectChain={handleRowClick} targetChain={SupportedChainId.MAINNET} />
<Row targetChain={SupportedChainId.OPTIMISM} /> <Row onSelectChain={handleRowClick} targetChain={SupportedChainId.POLYGON} />
<Row targetChain={SupportedChainId.ARBITRUM_ONE} /> <Row onSelectChain={handleRowClick} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.ARBITRUM_ONE} />
</FlyoutMenu> </FlyoutMenu>
)} )}
</SelectorWrapper> </SelectorWrapper>

@ -147,8 +147,8 @@ export default function Polling() {
<MouseoverTooltip <MouseoverTooltip
text={ text={
<Trans> <Trans>
{`The current fast gas amount for sending a transaction on L1. The current fast gas amount for sending a transaction on L1. Gas fees are paid in
Gas fees are paid in Ethereum's native currency Ether (ETH) and denominated in gwei. `} Ethereum&apos;s native currency Ether (ETH) and denominated in GWEI.
</Trans> </Trans>
} }
> >
@ -166,7 +166,7 @@ export default function Polling() {
} }
> >
<MouseoverTooltip <MouseoverTooltip
text={<Trans>{`The most recent block number on this network. Prices update on every block.`}</Trans>} text={<Trans>The most recent block number on this network. Prices update on every block.</Trans>}
> >
{blockNumber}&ensp; {blockNumber}&ensp;
</MouseoverTooltip> </MouseoverTooltip>

@ -1,144 +0,0 @@
import { Trans } from '@lingui/macro'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { useMemo } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components/macro'
import tokenLogo from '../../assets/images/token-logo.png'
import { UNI } from '../../constants/tokens'
import { useMerkleDistributorContract } from '../../hooks/useContract'
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import useUSDCPrice from '../../hooks/useUSDCPrice'
import { useActiveWeb3React } from '../../hooks/web3'
import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, ThemedText, UniTokenAnimated } from '../../theme'
import { computeUniCirculation } from '../../utils/computeUniCirculation'
import { AutoColumn } from '../Column'
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
import { RowBetween } from '../Row'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
`
const ModalUpper = styled(DataCard)`
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%);
padding: 0.5rem;
`
const StyledClose = styled(X)`
position: absolute;
right: 16px;
top: 16px;
:hover {
cursor: pointer;
}
`
/**
* Content for balance stats modal
*/
export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowUniBalanceModal: any }) {
const { account, chainId } = useActiveWeb3React()
const uni = chainId ? UNI[chainId] : undefined
const total = useAggregateUniBalance()
const uniBalance: CurrencyAmount<Token> | undefined = useTokenBalance(account ?? undefined, uni)
const uniToClaim: CurrencyAmount<Token> | undefined = useTotalUniEarned()
const totalSupply: CurrencyAmount<Token> | undefined = useTotalSupply(uni)
const uniPrice = useUSDCPrice(uni)
const blockTimestamp = useCurrentBlockTimestamp()
const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni)
const circulation: CurrencyAmount<Token> | undefined = useMemo(
() =>
blockTimestamp && uni && chainId === 1 ? computeUniCirculation(uni, blockTimestamp, unclaimedUni) : totalSupply,
[blockTimestamp, chainId, totalSupply, unclaimedUni, uni]
)
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
return (
<ContentWrapper gap="lg">
<ModalUpper>
<CardBGImage />
<CardNoise />
<CardSection gap="md">
<RowBetween>
<ThemedText.White color="white">
<Trans>Your UNI Breakdown</Trans>
</ThemedText.White>
<StyledClose stroke="white" onClick={() => setShowUniBalanceModal(false)} />
</RowBetween>
</CardSection>
<Break />
{account && (
<>
<CardSection gap="sm">
<AutoColumn gap="md" justify="center">
<UniTokenAnimated width="48px" src={tokenLogo} />{' '}
<ThemedText.White fontSize={48} fontWeight={600} color="white">
{total?.toFixed(2, { groupSeparator: ',' })}
</ThemedText.White>
</AutoColumn>
<AutoColumn gap="md">
<RowBetween>
<ThemedText.White color="white">
<Trans>Balance:</Trans>
</ThemedText.White>
<ThemedText.White color="white">{uniBalance?.toFixed(2, { groupSeparator: ',' })}</ThemedText.White>
</RowBetween>
<RowBetween>
<ThemedText.White color="white">
<Trans>Unclaimed:</Trans>
</ThemedText.White>
<ThemedText.White color="white">
{uniToClaim?.toFixed(4, { groupSeparator: ',' })}{' '}
{uniToClaim && uniToClaim.greaterThan('0') && (
<StyledInternalLink onClick={() => setShowUniBalanceModal(false)} to="/uni">
<Trans>(claim)</Trans>
</StyledInternalLink>
)}
</ThemedText.White>
</RowBetween>
</AutoColumn>
</CardSection>
<Break />
</>
)}
<CardSection gap="sm">
<AutoColumn gap="md">
<RowBetween>
<ThemedText.White color="white">
<Trans>UNI price:</Trans>
</ThemedText.White>
<ThemedText.White color="white">${uniPrice?.toFixed(2) ?? '-'}</ThemedText.White>
</RowBetween>
<RowBetween>
<ThemedText.White color="white">
<Trans>UNI in circulation:</Trans>
</ThemedText.White>
<ThemedText.White color="white">{circulation?.toFixed(0, { groupSeparator: ',' })}</ThemedText.White>
</RowBetween>
<RowBetween>
<ThemedText.White color="white">
<Trans>Total Supply</Trans>
</ThemedText.White>
<ThemedText.White color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</ThemedText.White>
</RowBetween>
{uni && uni.chainId === 1 ? (
<ExternalLink href={`${infoLink}/token/${uni.address}`}>
<Trans>View UNI Analytics</Trans>
</ExternalLink>
) : null}
</AutoColumn>
</CardSection>
</ModalUpper>
</ContentWrapper>
)
}

@ -3,14 +3,13 @@ import useScrollPosition from '@react-hook/window-scroll'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains' import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import useTheme from 'hooks/useTheme' import useTheme from 'hooks/useTheme'
import { darken } from 'polished' import { darken } from 'polished'
import { useState } from 'react'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks' import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
import { useUserHasAvailableClaim } from 'state/claim/hooks' import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/hooks' import { useUserHasSubmittedClaim } from 'state/transactions/hooks'
import { useDarkModeManager } from 'state/user/hooks' import { useDarkModeManager } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks' import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ReactComponent as Logo } from '../../assets/svg/logo.svg' import { ReactComponent as Logo } from '../../assets/svg/logo.svg'
@ -19,12 +18,10 @@ import { ExternalLink, ThemedText } from '../../theme'
import ClaimModal from '../claim/ClaimModal' import ClaimModal from '../claim/ClaimModal'
import { CardNoise } from '../earn/styled' import { CardNoise } from '../earn/styled'
import Menu from '../Menu' import Menu from '../Menu'
import Modal from '../Modal'
import Row from '../Row' import Row from '../Row'
import { Dots } from '../swap/styleds' import { Dots } from '../swap/styleds'
import Web3Status from '../Web3Status' import Web3Status from '../Web3Status'
import NetworkSelector from './NetworkSelector' import NetworkSelector from './NetworkSelector'
import UniBalanceContent from './UniBalanceContent'
const HeaderFrame = styled.div<{ showBackground: boolean }>` const HeaderFrame = styled.div<{ showBackground: boolean }>`
display: grid; display: grid;
@ -246,7 +243,7 @@ const StyledExternalLink = styled(ExternalLink).attrs({
export default function Header() { export default function Header() {
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? ''] const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
const [darkMode] = useDarkModeManager() const [darkMode] = useDarkModeManager()
const { white, black } = useTheme() const { white, black } = useTheme()
@ -256,18 +253,20 @@ export default function Header() {
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined) const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
const showClaimPopup = useShowClaimPopup() const showClaimPopup = useShowClaimPopup()
const scrollY = useScrollPosition() const scrollY = useScrollPosition()
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET] const {
infoLink,
addNetworkInfo: {
nativeCurrency: { symbol: nativeCurrencySymbol },
},
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
return ( return (
<HeaderFrame showBackground={scrollY > 45}> <HeaderFrame showBackground={scrollY > 45}>
<ClaimModal /> <ClaimModal />
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
</Modal>
<Title href="."> <Title href=".">
<UniIcon> <UniIcon>
<Logo fill={darkMode ? white : black} width="24px" height="100%" title="logo" /> <Logo fill={darkMode ? white : black} width="24px" height="100%" title="logo" />
@ -325,7 +324,9 @@ export default function Header() {
<AccountElement active={!!account}> <AccountElement active={!!account}>
{account && userEthBalance ? ( {account && userEthBalance ? (
<BalanceText style={{ flexShrink: 0, userSelect: 'none' }} pl="0.75rem" pr="0.5rem" fontWeight={500}> <BalanceText style={{ flexShrink: 0, userSelect: 'none' }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
<Trans>{userEthBalance?.toSignificant(3)} ETH</Trans> <Trans>
{userEthBalance?.toSignificant(3)} {nativeCurrencySymbol}
</Trans>
</BalanceText> </BalanceText>
) : null} ) : null}
<Web3Status /> <Web3Status />

@ -1,32 +1,22 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { import { SupportedChainId } from 'constants/chains'
ARBITRUM_HELP_CENTER_LINK,
L2_CHAIN_IDS,
OPTIMISM_HELP_CENTER_LINK,
SupportedChainId,
SupportedL2ChainId,
} from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3' import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { ArrowDownCircle, X } from 'react-feather' import { ArrowDownCircle, X } from 'react-feather'
import { useArbitrumAlphaAlert, useDarkModeManager, useOptimismAlphaAlert } from 'state/user/hooks' import { useDarkModeManager, useNetworkAlertStatus } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks' import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import styled, { css } from 'styled-components/macro' import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme' import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { CHAIN_INFO } from '../../constants/chains' import { CHAIN_INFO } from '../../constants/chains'
import { ThemedText } from '../../theme'
export const DesktopTextBreak = styled.div` import { AutoRow } from '../Row'
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
display: block;
}
`
const L2Icon = styled.img` const L2Icon = styled.img`
width: 36px; width: 36px;
height: 36px; height: 36px;
justify-self: center; justify-self: center;
margin-right: 14px;
` `
const BetaTag = styled.span<{ color: string }>` const BetaTag = styled.span<{ color: string }>`
align-items: center; align-items: center;
@ -44,24 +34,11 @@ const BetaTag = styled.span<{ color: string }>`
width: 60px; width: 60px;
z-index: 1; z-index: 1;
` `
const Body = styled.p` export const Controls = styled.div`
font-size: 12px;
grid-column: 1 / 3;
line-height: 143%;
margin: 0;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
grid-column: 2 / 3;
}
`
export const Controls = styled.div<{ thin?: boolean }>`
align-items: center; align-items: center;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
${({ thin }) => padding: 0 20px 20px 20px;
thin &&
css`
margin: auto 32px auto 0;
`}
` `
const CloseIcon = styled(X)` const CloseIcon = styled(X)`
cursor: pointer; cursor: pointer;
@ -71,17 +48,13 @@ const CloseIcon = styled(X)`
` `
const BodyText = styled.div` const BodyText = styled.div`
align-items: center; align-items: center;
display: grid;
grid-gap: 4px;
grid-template-columns: 40px 4fr;
grid-template-rows: auto auto;
margin: 20px 16px; margin: 20px 16px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) { @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
grid-template-columns: 42px 4fr; grid-template-columns: 42px 4fr;
grid-gap: 8px; grid-gap: 8px;
} }
` `
const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>` const LearnMoreLink = styled(ExternalLink)`
align-items: center; align-items: center;
background-color: transparent; background-color: transparent;
border: 1px solid rgba(255, 255, 255, 0.4); border: 1px solid rgba(255, 255, 255, 0.4);
@ -91,7 +64,6 @@ const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
font-size: 16px; font-size: 16px;
height: 44px; height: 44px;
justify-content: space-between; justify-content: space-between;
margin: 0 0 20px 0;
padding: 12px 16px; padding: 12px 16px;
text-decoration: none; text-decoration: none;
width: auto; width: auto;
@ -101,57 +73,51 @@ const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
background-color: rgba(255, 255, 255, 0.05); background-color: rgba(255, 255, 255, 0.05);
} }
transition: background-color 150ms ease-in-out; transition: background-color 150ms ease-in-out;
${({ thin }) =>
thin &&
css`
font-size: 14px;
margin: auto;
width: 112px;
`}
` `
const RootWrapper = styled.div` const RootWrapper = styled.div`
position: relative; position: relative;
` `
export const ArbitrumWrapperBackgroundDarkMode = css` const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%), [darkMode in 'dark' | 'light']: { [chainId in SupportedChainId]?: string }
radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1); } = {
` dark: {
export const ArbitrumWrapperBackgroundLightMode = css` [SupportedChainId.POLYGON]:
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%), 'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.3) 0%, rgba(82, 32, 166, 0.3) 100%)',
radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1); [SupportedChainId.POLYGON_MUMBAI]:
` 'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.3) 0%, rgba(82, 32, 166, 0.3) 100%)',
export const OptimismWrapperBackgroundDarkMode = css` [SupportedChainId.OPTIMISM]:
background: radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%), 'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%)',
radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%); [SupportedChainId.OPTIMISTIC_KOVAN]:
` 'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.5) 0%, rgba(235, 0, 255, 0.345) 96%)',
export const OptimismWrapperBackgroundLightMode = css` [SupportedChainId.ARBITRUM_ONE]:
background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%), 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1)',
radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5); [SupportedChainId.ARBITRUM_RINKEBY]:
` 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1)',
const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string; thin?: boolean }>` },
${({ chainId, darkMode }) => light: {
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) [SupportedChainId.POLYGON]:
? darkMode 'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.3) 0%, rgba(167, 202, 255, 0.3) 100%)',
? OptimismWrapperBackgroundDarkMode [SupportedChainId.POLYGON_MUMBAI]:
: OptimismWrapperBackgroundLightMode 'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.3) 0%, rgba(167, 202, 255, 0.3) 100%)',
: darkMode [SupportedChainId.OPTIMISM]:
? ArbitrumWrapperBackgroundDarkMode 'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5)',
: ArbitrumWrapperBackgroundLightMode}; [SupportedChainId.OPTIMISTIC_KOVAN]:
'radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5)',
[SupportedChainId.ARBITRUM_ONE]:
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)',
[SupportedChainId.ARBITRUM_RINKEBY]:
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)',
},
}
const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
background: ${({ chainId, darkMode }) => BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID[darkMode ? 'dark' : 'light'][chainId]};
border-radius: 20px; border-radius: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-width: 480px;
min-height: 174px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
width: 100%; width: 100%;
${({ thin }) =>
thin &&
css`
flex-direction: row;
max-width: max-content;
min-height: min-content;
`}
:before { :before {
background-image: url(${({ logoUrl }) => logoUrl}); background-image: url(${({ logoUrl }) => logoUrl});
background-repeat: no-repeat; background-repeat: no-repeat;
@ -165,12 +131,11 @@ const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean
z-index: -1; z-index: -1;
} }
` `
const Header = styled.h2<{ thin?: boolean }>` const Header = styled.h2`
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
margin: 0; margin: 0;
padding-right: 30px; padding-right: 30px;
display: ${({ thin }) => (thin ? 'none' : 'block')};
` `
const LinkOutCircle = styled(ArrowDownCircle)` const LinkOutCircle = styled(ArrowDownCircle)`
margin-left: 12px; margin-left: 12px;
@ -178,7 +143,7 @@ const LinkOutCircle = styled(ArrowDownCircle)`
width: 20px; width: 20px;
height: 20px; height: 20px;
` `
const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>` const LinkOutToBridge = styled(ExternalLink)`
align-items: center; align-items: center;
background-color: black; background-color: black;
border-radius: 8px; border-radius: 8px;
@ -187,8 +152,8 @@ const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
font-size: 16px; font-size: 16px;
height: 44px; height: 44px;
justify-content: space-between; justify-content: space-between;
margin: 0 12px 20px 18px;
padding: 12px 16px; padding: 12px 16px;
margin-right: 20px;
text-decoration: none; text-decoration: none;
width: auto; width: auto;
:hover, :hover,
@ -196,85 +161,154 @@ const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
:active { :active {
background-color: black; background-color: black;
} }
${({ thin }) =>
thin &&
css`
font-size: 14px;
margin: auto 10px;
width: 168px;
`}
` `
interface NetworkAlertProps { const DisclaimerText = styled(ThemedText.Body)`
thin?: boolean padding: 0 0.5em;
font-size: 14px !important;
`
const BETA_TAG_COLORS: { [chainId in SupportedChainId]?: string } = {
[SupportedChainId.OPTIMISM]: '#ff0420',
[SupportedChainId.OPTIMISTIC_KOVAN]: '#ff0420',
[SupportedChainId.ARBITRUM_ONE]: '#0490ed',
[SupportedChainId.ARBITRUM_RINKEBY]: '#0490ed',
} }
export function NetworkAlert(props: NetworkAlertProps) { const SHOULD_SHOW_ALERT: { [chainId in SupportedChainId]?: true } = {
[SupportedChainId.OPTIMISM]: true,
[SupportedChainId.OPTIMISTIC_KOVAN]: true,
[SupportedChainId.ARBITRUM_ONE]: true,
[SupportedChainId.ARBITRUM_RINKEBY]: true,
[SupportedChainId.POLYGON]: true,
[SupportedChainId.POLYGON_MUMBAI]: true,
}
function shouldShowAlert(chainId: number | undefined): chainId is SupportedChainId {
return Boolean(chainId && SHOULD_SHOW_ALERT[chainId as SupportedChainId])
}
export function NetworkAlert() {
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const [darkMode] = useDarkModeManager() const [darkMode] = useDarkModeManager()
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert() const [alertAcknowledged, acknowledgeAlert] = useNetworkAlertStatus(chainId)
const [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged] = useOptimismAlphaAlert()
const [locallyDismissed, setLocallyDimissed] = useState(false) const [locallyDismissed, setLocallyDimissed] = useState(false)
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? ''] const accounts = useMemo(() => (account ? [account] : []), [account])
const userNativeCurrencyBalance = useNativeCurrencyBalances(accounts)?.[account ?? '']
const dismiss = useCallback(() => { const dismiss = useCallback(() => {
if (userEthBalance?.greaterThan(0)) { setLocallyDimissed(true)
switch (chainId) { if (!alertAcknowledged) acknowledgeAlert()
case SupportedChainId.OPTIMISM: }, [acknowledgeAlert, alertAcknowledged])
setOptimismAlphaAcknowledged(true)
break
case SupportedChainId.ARBITRUM_ONE:
setArbitrumAlphaAcknowledged(true)
break
}
} else {
setLocallyDimissed(true)
}
}, [chainId, setArbitrumAlphaAcknowledged, setOptimismAlphaAcknowledged, userEthBalance])
const onOptimismAndOptimismAcknowledged = SupportedChainId.OPTIMISM === chainId && optimismAlphaAcknowledged if (!shouldShowAlert(chainId) || alertAcknowledged || locallyDismissed) {
const onArbitrumAndArbitrumAcknowledged = SupportedChainId.ARBITRUM_ONE === chainId && arbitrumAlphaAcknowledged
if (
!chainId ||
!L2_CHAIN_IDS.includes(chainId) ||
onArbitrumAndArbitrumAcknowledged ||
onOptimismAndOptimismAcknowledged ||
locallyDismissed
) {
return null return null
} }
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId) const { label, logoUrl, bridge, helpCenterUrl } = CHAIN_INFO[chainId]
const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge const showCloseIcon = Boolean(userNativeCurrencyBalance?.greaterThan(0))
const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK const betaColor = BETA_TAG_COLORS[chainId]
const showCloseIcon = Boolean(userEthBalance?.greaterThan(0) && !props.thin)
return ( return (
<RootWrapper> <RootWrapper>
<BetaTag color={isOptimism ? '#ff0420' : '#0490ed'}>Beta</BetaTag> {betaColor ? <BetaTag color={betaColor}>Beta</BetaTag> : null}
<ContentWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl} thin={props.thin}> <ContentWrapper chainId={chainId} darkMode={darkMode} logoUrl={logoUrl}>
{showCloseIcon && <CloseIcon onClick={dismiss} />} {showCloseIcon && <CloseIcon onClick={dismiss} />}
<BodyText> <BodyText>
<L2Icon src={info.logoUrl} /> <AutoRow style={{ marginBottom: '1em' }}>
<Header thin={props.thin}> <L2Icon src={logoUrl} />
<Trans>Uniswap on {info.label}</Trans> <Header>
</Header> <Trans>Uniswap on {label}</Trans>
<Body> </Header>
<Trans> </AutoRow>
To start trading on {info.label}, first bridge your assets from L1 to L2. Please treat this as a beta <DisclaimerText>
release and learn about the risks before using {info.label}. {betaColor ? (
</Trans> <Trans>
</Body> Please treat this as a beta release and learn about the risks before using {label}. To start trading on{' '}
{label}, first bridge your assets from L1 to L2.
</Trans>
) : (
<Trans>To start trading on {label}, first bridge your assets from L1 to L2.</Trans>
)}
</DisclaimerText>
</BodyText> </BodyText>
<Controls thin={props.thin}> <Controls>
<LinkOutToBridge href={depositUrl} thin={props.thin}> {bridge ? (
<Trans>Deposit Assets</Trans> <LinkOutToBridge href={bridge}>
<LinkOutCircle /> <Trans>Deposit Assets</Trans>
</LinkOutToBridge> <LinkOutCircle />
<LearnMoreLink href={helpCenterLink} thin={props.thin}> </LinkOutToBridge>
<Trans>Learn More</Trans> ) : null}
</LearnMoreLink> {helpCenterUrl ? (
<LearnMoreLink href={helpCenterUrl}>
<Trans>Learn More</Trans>
</LearnMoreLink>
) : null}
</Controls> </Controls>
</ContentWrapper> </ContentWrapper>
</RootWrapper> </RootWrapper>
) )
} }
const AlertRow = styled.div`
display: flex;
padding: 1em;
align-items: center;
`
const ButtonContainer = styled.div`
flex-shrink: 0;
flex-grow: 0;
display: flex;
height: 100%;
`
const FlexGrow = styled.div`
flex-grow: 1;
`
export function SingleRowNetworkAlert() {
const { chainId } = useActiveWeb3React()
const [darkMode] = useDarkModeManager()
if (!shouldShowAlert(chainId)) {
return null
}
const { label, logoUrl, bridge, helpCenterUrl } = CHAIN_INFO[chainId]
const betaColor = BETA_TAG_COLORS[chainId]
return (
<RootWrapper>
{betaColor ? <BetaTag color={betaColor}>Beta</BetaTag> : null}
<ContentWrapper chainId={chainId} darkMode={darkMode} logoUrl={logoUrl}>
<AlertRow>
<L2Icon src={logoUrl} />
<FlexGrow>
<DisclaimerText>
{betaColor ? (
<Trans>
Please treat this as a beta release and learn about the risks before using {label}. To start trading
on {label}, first bridge your assets from L1 to L2.
</Trans>
) : (
<Trans>To start trading on {label}, first bridge your assets from L1 to L2.</Trans>
)}
</DisclaimerText>
</FlexGrow>
<ButtonContainer>
{bridge ? (
<LinkOutToBridge href={bridge}>
<Trans>Deposit Assets</Trans>
<LinkOutCircle />
</LinkOutToBridge>
) : null}
{helpCenterUrl ? (
<LearnMoreLink href={helpCenterUrl}>
<Trans>Learn More</Trans>
</LearnMoreLink>
) : null}
</ButtonContainer>
</AlertRow>
</ContentWrapper>
</RootWrapper>
)
}

@ -0,0 +1,34 @@
import { Trans } from '@lingui/macro'
import { useContext } from 'react'
import { AlertCircle } from 'react-feather'
import styled, { ThemeContext } from 'styled-components/macro'
import { CHAIN_INFO, SupportedChainId } from '../../constants/chains'
import { ThemedText } from '../../theme'
import { AutoColumn } from '../Column'
import { AutoRow } from '../Row'
const RowNoFlex = styled(AutoRow)`
flex-wrap: nowrap;
`
export default function FailedNetworkSwitchPopup({ chainId }: { chainId: SupportedChainId }) {
const chainInfo = CHAIN_INFO[chainId]
const theme = useContext(ThemeContext)
return (
<RowNoFlex>
<div style={{ paddingRight: 16 }}>
<AlertCircle color={theme.red1} size={24} />
</div>
<AutoColumn gap="8px">
<ThemedText.Body fontWeight={500}>
<Trans>
Your wallet does not support switching networks from the Uniswap Interface. In order to use Uniswap on{' '}
{chainInfo.label}, you must change the network in your wallet.
</Trans>
</ThemedText.Body>
</AutoColumn>
</RowNoFlex>
)
}

@ -6,6 +6,7 @@ import styled, { ThemeContext } from 'styled-components/macro'
import { useRemovePopup } from '../../state/application/hooks' import { useRemovePopup } from '../../state/application/hooks'
import { PopupContent } from '../../state/application/reducer' import { PopupContent } from '../../state/application/reducer'
import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup'
import TransactionPopup from './TransactionPopup' import TransactionPopup from './TransactionPopup'
const StyledClose = styled(X)` const StyledClose = styled(X)`
@ -77,6 +78,8 @@ export default function PopupItem({
txn: { hash }, txn: { hash },
} = content } = content
popupContent = <TransactionPopup hash={hash} /> popupContent = <TransactionPopup hash={hash} />
} else if ('failedSwitchNetwork' in content) {
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
} }
const faderStyle = useSpring({ const faderStyle = useSpring({

@ -19,7 +19,7 @@ import { PositionDetails } from 'types/position'
import { formatTickPrice } from 'utils/formatTickPrice' import { formatTickPrice } from 'utils/formatTickPrice'
import { unwrappedToken } from 'utils/unwrappedToken' import { unwrappedToken } from 'utils/unwrappedToken'
import { DAI, USDC, USDT, WBTC, WETH9_EXTENDED } from '../../constants/tokens' import { DAI, USDC, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
const LinkRow = styled(Link)` const LinkRow = styled(Link)`
align-items: center; align-items: center;
@ -156,7 +156,7 @@ export function getPriceOrderingFromPositionForUI(position?: Position): {
} }
// if token1 is an ETH-/BTC-stable asset, set it as the base token // if token1 is an ETH-/BTC-stable asset, set it as the base token
const bases = [...Object.values(WETH9_EXTENDED), WBTC] const bases = [...Object.values(WRAPPED_NATIVE_CURRENCY), WBTC]
if (bases.some((base) => base.equals(token1))) { if (bases.some((base) => base.equals(token1))) {
return { return {
priceLower: position.token0PriceUpper.invert(), priceLower: position.token0PriceUpper.invert(),

@ -13,8 +13,13 @@ import { FixedSizeList } from 'react-window'
import { Text } from 'rebass' import { Text } from 'rebass'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ExtendedEther } from '../../constants/tokens' import {
import { useAllTokens, useIsUserAddedToken, useSearchInactiveTokenLists, useToken } from '../../hooks/Tokens' useAllTokens,
useIsUserAddedToken,
useNativeCurrency,
useSearchInactiveTokenLists,
useToken,
} from '../../hooks/Tokens'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { ButtonText, CloseIcon, IconWrapper, ThemedText } from '../../theme' import { ButtonText, CloseIcon, IconWrapper, ThemedText } from '../../theme'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
@ -112,15 +117,17 @@ export function CurrencySearch({
const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery) const filteredSortedTokens = useSortedTokensByQuery(sortedTokens, debouncedQuery)
const ether = useMemo(() => chainId && ExtendedEther.onChain(chainId), [chainId]) const native = useNativeCurrency()
const filteredSortedTokensWithETH: Currency[] = useMemo(() => { const filteredSortedTokensWithETH: Currency[] = useMemo(() => {
if (!native) return filteredSortedTokens
const s = debouncedQuery.toLowerCase().trim() const s = debouncedQuery.toLowerCase().trim()
if (s === '' || s === 'e' || s === 'et' || s === 'eth') { if (native.symbol?.toLowerCase()?.indexOf(s) !== -1) {
return ether ? [ether, ...filteredSortedTokens] : filteredSortedTokens return native ? [native, ...filteredSortedTokens] : filteredSortedTokens
} }
return filteredSortedTokens return filteredSortedTokens
}, [debouncedQuery, ether, filteredSortedTokens]) }, [debouncedQuery, native, filteredSortedTokens])
const handleCurrencySelect = useCallback( const handleCurrencySelect = useCallback(
(currency: Currency) => { (currency: Currency) => {
@ -148,8 +155,8 @@ export function CurrencySearch({
(e: KeyboardEvent<HTMLInputElement>) => { (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
const s = debouncedQuery.toLowerCase().trim() const s = debouncedQuery.toLowerCase().trim()
if (s === 'eth' && ether) { if (s === native?.symbol?.toLowerCase()) {
handleCurrencySelect(ether) handleCurrencySelect(native)
} else if (filteredSortedTokensWithETH.length > 0) { } else if (filteredSortedTokensWithETH.length > 0) {
if ( if (
filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() || filteredSortedTokensWithETH[0].symbol?.toLowerCase() === debouncedQuery.trim().toLowerCase() ||
@ -160,7 +167,7 @@ export function CurrencySearch({
} }
} }
}, },
[debouncedQuery, ether, filteredSortedTokensWithETH, handleCurrencySelect] [debouncedQuery, native, filteredSortedTokensWithETH, handleCurrencySelect]
) )
// menu ui // menu ui

@ -111,7 +111,7 @@ const HoverText = styled.div`
const LinkCard = styled(Card)` const LinkCard = styled(Card)`
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg1};
color: ${({ theme }) => theme.white}; color: ${({ theme }) => theme.text3};
:hover { :hover {
cursor: pointer; cursor: pointer;
@ -416,9 +416,9 @@ export default function WalletModal({
<RowBetween> <RowBetween>
<AutoRow gap="4px"> <AutoRow gap="4px">
<Info size={20} /> <Info size={20} />
<ThemedText.White fontSize={14}> <ThemedText.Label fontSize={14}>
<Trans>How this app uses APIs</Trans> <Trans>How this app uses APIs</Trans>
</ThemedText.White> </ThemedText.Label>
</AutoRow> </AutoRow>
<ArrowRight size={16} /> <ArrowRight size={16} />
</RowBetween> </RowBetween>

@ -83,23 +83,27 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
routes={routes} routes={routes}
/> />
)} )}
<Separator />
{autoRouterSupported && {autoRouterSupported && (
(syncing ? ( <>
<LoadingRows> <Separator />
<div style={{ width: '250px', height: '15px' }} /> {syncing ? (
</LoadingRows> <LoadingRows>
) : ( <div style={{ width: '250px', height: '15px' }} />
<ThemedText.Main fontSize={12} width={400} margin={0}> </LoadingRows>
{trade?.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? ( ) : (
<Trans>Best price route costs ~{formattedGasPriceString} in gas. </Trans> <ThemedText.Main fontSize={12} width={400} margin={0}>
) : null}{' '} {trade?.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? (
<Trans> <Trans>Best price route costs ~{formattedGasPriceString} in gas. </Trans>
This route optimizes your total output by considering split routes, multiple hops, and the gas cost of ) : null}{' '}
each step. <Trans>
</Trans> This route optimizes your total output by considering split routes, multiple hops, and the gas cost
</ThemedText.Main> of each step.
))} </Trans>
</ThemedText.Main>
)}
</>
)}
</AutoRow> </AutoRow>
</AnimatedDropdown> </AnimatedDropdown>
</Wrapper> </Wrapper>

@ -36,11 +36,14 @@ class MiniRpcProvider implements AsyncSendable {
public readonly path: string public readonly path: string
public readonly batchWaitTimeMs: number public readonly batchWaitTimeMs: number
private readonly connector: NetworkConnector
private nextId = 1 private nextId = 1
private batchTimeoutId: ReturnType<typeof setTimeout> | null = null private batchTimeoutId: ReturnType<typeof setTimeout> | null = null
private batch: BatchItem[] = [] private batch: BatchItem[] = []
constructor(chainId: number, url: string, batchWaitTimeMs?: number) { constructor(connector: NetworkConnector, chainId: number, url: string, batchWaitTimeMs?: number) {
this.connector = connector
this.chainId = chainId this.chainId = chainId
this.url = url this.url = url
const parsed = new URL(url) const parsed = new URL(url)
@ -52,7 +55,21 @@ class MiniRpcProvider implements AsyncSendable {
public readonly clearBatch = async () => { public readonly clearBatch = async () => {
console.debug('Clearing batch', this.batch) console.debug('Clearing batch', this.batch)
const batch = this.batch let batch = this.batch
batch = batch.filter((b) => {
if (b.request.method === 'wallet_switchEthereumChain') {
try {
this.connector.changeChainId(parseInt((b.request.params as [{ chainId: string }])[0].chainId))
b.resolve({ id: b.request.id })
} catch (error) {
b.reject(error)
}
return false
}
return true
})
this.batch = [] this.batch = []
this.batchTimeoutId = null this.batchTimeoutId = null
let response: Response let response: Response
@ -148,9 +165,9 @@ export class NetworkConnector extends AbstractConnector {
invariant(defaultChainId || Object.keys(urls).length === 1, 'defaultChainId is a required argument with >1 url') invariant(defaultChainId || Object.keys(urls).length === 1, 'defaultChainId is a required argument with >1 url')
super({ supportedChainIds: Object.keys(urls).map((k): number => Number(k)) }) super({ supportedChainIds: Object.keys(urls).map((k): number => Number(k)) })
this.currentChainId = defaultChainId || Number(Object.keys(urls)[0]) this.currentChainId = defaultChainId ?? Number(Object.keys(urls)[0])
this.providers = Object.keys(urls).reduce<{ [chainId: number]: MiniRpcProvider }>((accumulator, chainId) => { this.providers = Object.keys(urls).reduce<{ [chainId: number]: MiniRpcProvider }>((accumulator, chainId) => {
accumulator[Number(chainId)] = new MiniRpcProvider(Number(chainId), urls[Number(chainId)]) accumulator[Number(chainId)] = new MiniRpcProvider(this, Number(chainId), urls[Number(chainId)])
return accumulator return accumulator
}, {}) }, {})
} }
@ -178,4 +195,21 @@ export class NetworkConnector extends AbstractConnector {
public deactivate() { public deactivate() {
return return
} }
/**
* Meant to be called only by MiniRpcProvider
* @param chainId the new chain id
*/
public changeChainId(chainId: number) {
if (chainId in this.providers) {
this.currentChainId = chainId
this.emitUpdate({
chainId,
account: null,
provider: this.providers[chainId],
})
} else {
throw new Error(`Unsupported chain ID: ${chainId}`)
}
}
} }

@ -6,33 +6,16 @@ import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { WalletLinkConnector } from '@web3-react/walletlink-connector' import { WalletLinkConnector } from '@web3-react/walletlink-connector'
import UNISWAP_LOGO_URL from '../assets/svg/logo.svg' import UNISWAP_LOGO_URL from '../assets/svg/logo.svg'
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from '../constants/chains' import { ALL_SUPPORTED_CHAIN_IDS, INFURA_NETWORK_URLS, SupportedChainId } from '../constants/chains'
import getLibrary from '../utils/getLibrary' import getLibrary from '../utils/getLibrary'
import { FortmaticConnector } from './Fortmatic' import { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector' import { NetworkConnector } from './NetworkConnector'
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
const PORTIS_ID = process.env.REACT_APP_PORTIS_ID const PORTIS_ID = process.env.REACT_APP_PORTIS_ID
if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
export const NETWORK_URLS: { [key in SupportedChainId]: string } = {
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
}
export const network = new NetworkConnector({ export const network = new NetworkConnector({
urls: NETWORK_URLS, urls: INFURA_NETWORK_URLS,
defaultChainId: 1, defaultChainId: 1,
}) })
@ -49,7 +32,7 @@ export const gnosisSafe = new SafeAppConnector()
export const walletconnect = new WalletConnectConnector({ export const walletconnect = new WalletConnectConnector({
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS, supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
rpc: NETWORK_URLS, rpc: INFURA_NETWORK_URLS,
qrcode: true, qrcode: true,
}) })
@ -67,7 +50,7 @@ export const portis = new PortisConnector({
// mainnet only // mainnet only
export const walletlink = new WalletLinkConnector({ export const walletlink = new WalletLinkConnector({
url: NETWORK_URLS[SupportedChainId.MAINNET], url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap', appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL, appLogoUrl: UNISWAP_LOGO_URL,
supportedChainIds: [SupportedChainId.MAINNET], supportedChainIds: [SupportedChainId.MAINNET],

@ -11,6 +11,8 @@ export const MULTICALL_ADDRESS: AddressMap = {
...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984', [ ...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984', [
SupportedChainId.OPTIMISTIC_KOVAN, SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISM,
SupportedChainId.POLYGON_MUMBAI,
SupportedChainId.POLYGON,
]), ]),
[SupportedChainId.ARBITRUM_ONE]: '0xadF885960B47eA2CD9B55E6DAc6B42b7Cb2806dB', [SupportedChainId.ARBITRUM_ONE]: '0xadF885960B47eA2CD9B55E6DAc6B42b7Cb2806dB',
[SupportedChainId.ARBITRUM_RINKEBY]: '0xa501c031958F579dB7676fF1CE78AD305794d579', [SupportedChainId.ARBITRUM_RINKEBY]: '0xa501c031958F579dB7676fF1CE78AD305794d579',
@ -63,12 +65,16 @@ export const V3_CORE_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V3_
SupportedChainId.OPTIMISTIC_KOVAN, SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY, SupportedChainId.ARBITRUM_RINKEBY,
SupportedChainId.POLYGON_MUMBAI,
SupportedChainId.POLYGON,
]) ])
export const QUOTER_ADDRESSES: AddressMap = constructSameAddressMap('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', [ export const QUOTER_ADDRESSES: AddressMap = constructSameAddressMap('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', [
SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN, SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY, SupportedChainId.ARBITRUM_RINKEBY,
SupportedChainId.POLYGON_MUMBAI,
SupportedChainId.POLYGON,
]) ])
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = constructSameAddressMap( export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = constructSameAddressMap(
'0xC36442b4a4522E871399CD717aBDD847Ab11FE88', '0xC36442b4a4522E871399CD717aBDD847Ab11FE88',
@ -77,6 +83,8 @@ export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = constructSameA
SupportedChainId.OPTIMISTIC_KOVAN, SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY, SupportedChainId.ARBITRUM_RINKEBY,
SupportedChainId.POLYGON_MUMBAI,
SupportedChainId.POLYGON,
] ]
) )
export const ENS_REGISTRAR_ADDRESSES: AddressMap = { export const ENS_REGISTRAR_ADDRESSES: AddressMap = {
@ -92,4 +100,6 @@ export const SOCKS_CONTROLLER_ADDRESSES: AddressMap = {
export const V3_MIGRATOR_ADDRESSES: AddressMap = constructSameAddressMap('0xA5644E29708357803b5A882D272c41cC0dF92B34', [ export const V3_MIGRATOR_ADDRESSES: AddressMap = constructSameAddressMap('0xA5644E29708357803b5A882D272c41cC0dF92B34', [
SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY, SupportedChainId.ARBITRUM_RINKEBY,
SupportedChainId.POLYGON_MUMBAI,
SupportedChainId.POLYGON,
]) ])

@ -1,10 +1,14 @@
import ethereumLogoUrl from 'assets/images/ethereum-logo.png' import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg' import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg' import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg'
import ms from 'ms.macro' import ms from 'ms.macro'
import { ARBITRUM_LIST, OPTIMISM_LIST } from './lists' import { ARBITRUM_LIST, OPTIMISM_LIST } from './lists'
/**
* List of all the networks supported by the Uniswap Interface
*/
export enum SupportedChainId { export enum SupportedChainId {
MAINNET = 1, MAINNET = 1,
ROPSTEN = 3, ROPSTEN = 3,
@ -14,33 +18,45 @@ export enum SupportedChainId {
ARBITRUM_ONE = 42161, ARBITRUM_ONE = 42161,
ARBITRUM_RINKEBY = 421611, ARBITRUM_RINKEBY = 421611,
OPTIMISM = 10, OPTIMISM = 10,
OPTIMISTIC_KOVAN = 69, OPTIMISTIC_KOVAN = 69,
POLYGON = 137,
POLYGON_MUMBAI = 80001,
} }
export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = [ const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
SupportedChainId.MAINNET, if (typeof INFURA_KEY === 'undefined') {
SupportedChainId.ROPSTEN, throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
SupportedChainId.RINKEBY, }
SupportedChainId.GOERLI,
SupportedChainId.KOVAN,
SupportedChainId.ARBITRUM_ONE, /**
SupportedChainId.ARBITRUM_RINKEBY, * Array of all the supported chain IDs
SupportedChainId.OPTIMISM, */
SupportedChainId.OPTIMISTIC_KOVAN, export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = Object.values(SupportedChainId).filter(
] (id) => typeof id === 'number'
) as SupportedChainId[]
/**
* All the chain IDs that are running the Ethereum protocol.
*/
export const L1_CHAIN_IDS = [ export const L1_CHAIN_IDS = [
SupportedChainId.MAINNET, SupportedChainId.MAINNET,
SupportedChainId.ROPSTEN, SupportedChainId.ROPSTEN,
SupportedChainId.RINKEBY, SupportedChainId.RINKEBY,
SupportedChainId.GOERLI, SupportedChainId.GOERLI,
SupportedChainId.KOVAN, SupportedChainId.KOVAN,
SupportedChainId.POLYGON,
SupportedChainId.POLYGON_MUMBAI,
] as const ] as const
export type SupportedL1ChainId = typeof L1_CHAIN_IDS[number] export type SupportedL1ChainId = typeof L1_CHAIN_IDS[number]
/**
* Controls some L2 specific behavior, e.g. slippage tolerance, special UI behavior.
* The expectation is that all of these networks have immediate transaction confirmation.
*/
export const L2_CHAIN_IDS = [ export const L2_CHAIN_IDS = [
SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY, SupportedChainId.ARBITRUM_RINKEBY,
@ -50,106 +66,149 @@ export const L2_CHAIN_IDS = [
export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number] export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number]
export interface L1ChainInfo { /**
readonly blockWaitMsBeforeWarning?: number * These are the network URLs used by the interface when there is not another available source of chain data
readonly docs: string */
readonly explorer: string export const INFURA_NETWORK_URLS: { [key in SupportedChainId]: string } = {
readonly infoLink: string [SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
readonly label: string [SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
readonly logoUrl?: string [SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
readonly rpcUrls?: string[] [SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_ONE]: `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_RINKEBY]: `https://arbitrum-rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.POLYGON]: `https://polygon-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.POLYGON_MUMBAI]: `https://polygon-mumbai.infura.io/v3/${INFURA_KEY}`,
}
/**
* This is used to call the add network RPC
*/
interface AddNetworkInfo {
readonly rpcUrl: string
readonly nativeCurrency: { readonly nativeCurrency: {
name: string // 'Goerli ETH', name: string // e.g. 'Goerli ETH',
symbol: string // 'gorETH', symbol: string // e.g. 'gorETH',
decimals: number //18, decimals: number // e.g. 18,
} }
} }
export interface L2ChainInfo extends L1ChainInfo {
readonly bridge: string export enum NetworkType {
L1,
L2,
}
interface BaseChainInfo {
readonly networkType: NetworkType
readonly blockWaitMsBeforeWarning?: number
readonly docs: string
readonly bridge?: string
readonly explorer: string
readonly infoLink: string
readonly logoUrl: string readonly logoUrl: string
readonly label: string
readonly helpCenterUrl?: string
readonly addNetworkInfo: AddNetworkInfo
}
export interface L1ChainInfo extends BaseChainInfo {
readonly networkType: NetworkType.L1
}
export interface L2ChainInfo extends BaseChainInfo {
readonly networkType: NetworkType.L2
readonly bridge: string
readonly statusPage?: string readonly statusPage?: string
readonly defaultListUrl: string readonly defaultListUrl: string
} }
export type ChainInfo = { readonly [chainId: number]: L1ChainInfo | L2ChainInfo } & { export type ChainInfoMap = { readonly [chainId: number]: L1ChainInfo | L2ChainInfo } & {
readonly [chainId in SupportedL2ChainId]: L2ChainInfo readonly [chainId in SupportedL2ChainId]: L2ChainInfo
} & } &
{ readonly [chainId in SupportedL1ChainId]: L1ChainInfo } { readonly [chainId in SupportedL1ChainId]: L1ChainInfo }
export const CHAIN_INFO: ChainInfo = { export const CHAIN_INFO: ChainInfoMap = {
[SupportedChainId.ARBITRUM_ONE]: {
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://bridge.arbitrum.io/',
defaultListUrl: ARBITRUM_LIST,
docs: 'https://offchainlabs.com/',
explorer: 'https://arbiscan.io/',
infoLink: 'https://info.uniswap.org/#/arbitrum/',
label: 'Arbitrum',
logoUrl: arbitrumLogoUrl,
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
rpcUrls: ['https://arb1.arbitrum.io/rpc'],
},
[SupportedChainId.ARBITRUM_RINKEBY]: {
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://bridge.arbitrum.io/',
defaultListUrl: ARBITRUM_LIST,
docs: 'https://offchainlabs.com/',
explorer: 'https://rinkeby-explorer.arbitrum.io/',
infoLink: 'https://info.uniswap.org/#/arbitrum/',
label: 'Arbitrum Rinkeby',
logoUrl: arbitrumLogoUrl,
nativeCurrency: { name: 'Rinkeby ArbETH', symbol: 'rinkArbETH', decimals: 18 },
rpcUrls: ['https://rinkeby.arbitrum.io/rpc'],
},
[SupportedChainId.MAINNET]: { [SupportedChainId.MAINNET]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/', docs: 'https://docs.uniswap.org/',
explorer: 'https://etherscan.io/', explorer: 'https://etherscan.io/',
infoLink: 'https://info.uniswap.org/#/', infoLink: 'https://info.uniswap.org/#/',
label: 'Ethereum', label: 'Ethereum',
logoUrl: ethereumLogoUrl, logoUrl: ethereumLogoUrl,
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 }, addNetworkInfo: {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
},
}, },
[SupportedChainId.RINKEBY]: { [SupportedChainId.RINKEBY]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/', docs: 'https://docs.uniswap.org/',
explorer: 'https://rinkeby.etherscan.io/', explorer: 'https://rinkeby.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/', infoLink: 'https://info.uniswap.org/#/',
label: 'Rinkeby', label: 'Rinkeby',
nativeCurrency: { name: 'Rinkeby ETH', symbol: 'rinkETH', decimals: 18 }, logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Rinkeby Ether', symbol: 'rETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.RINKEBY],
},
}, },
[SupportedChainId.ROPSTEN]: { [SupportedChainId.ROPSTEN]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/', docs: 'https://docs.uniswap.org/',
explorer: 'https://ropsten.etherscan.io/', explorer: 'https://ropsten.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/', infoLink: 'https://info.uniswap.org/#/',
label: 'Ropsten', label: 'Ropsten',
nativeCurrency: { name: 'Ropsten ETH', symbol: 'ropETH', decimals: 18 }, logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Ropsten Ether', symbol: 'ropETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.ROPSTEN],
},
}, },
[SupportedChainId.KOVAN]: { [SupportedChainId.KOVAN]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/', docs: 'https://docs.uniswap.org/',
explorer: 'https://kovan.etherscan.io/', explorer: 'https://kovan.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/', infoLink: 'https://info.uniswap.org/#/',
label: 'Kovan', label: 'Kovan',
nativeCurrency: { name: 'Kovan ETH', symbol: 'kovETH', decimals: 18 }, logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Kovan Ether', symbol: 'kovETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.KOVAN],
},
}, },
[SupportedChainId.GOERLI]: { [SupportedChainId.GOERLI]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/', docs: 'https://docs.uniswap.org/',
explorer: 'https://goerli.etherscan.io/', explorer: 'https://goerli.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/', infoLink: 'https://info.uniswap.org/#/',
label: 'Görli', label: 'Görli',
nativeCurrency: { name: 'Görli ETH', symbol: 'görETH', decimals: 18 }, logoUrl: ethereumLogoUrl,
addNetworkInfo: {
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
rpcUrl: INFURA_NETWORK_URLS[SupportedChainId.GOERLI],
},
}, },
[SupportedChainId.OPTIMISM]: { [SupportedChainId.OPTIMISM]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`25m`, blockWaitMsBeforeWarning: ms`25m`,
bridge: 'https://gateway.optimism.io/', bridge: 'https://gateway.optimism.io/?chainId=1',
defaultListUrl: OPTIMISM_LIST, defaultListUrl: OPTIMISM_LIST,
docs: 'https://optimism.io/', docs: 'https://optimism.io/',
explorer: 'https://optimistic.etherscan.io/', explorer: 'https://optimistic.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/optimism/', infoLink: 'https://info.uniswap.org/#/optimism/',
label: 'OΞ', label: 'Optimism',
logoUrl: optimismLogoUrl, logoUrl: optimismLogoUrl,
nativeCurrency: { name: 'Optimistic ETH', symbol: 'ETH', decimals: 18 }, statusPage: 'https://optimism.io/status',
rpcUrls: ['https://mainnet.optimism.io'], helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
addNetworkInfo: {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrl: 'https://mainnet.optimism.io',
},
}, },
[SupportedChainId.OPTIMISTIC_KOVAN]: { [SupportedChainId.OPTIMISTIC_KOVAN]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`25m`, blockWaitMsBeforeWarning: ms`25m`,
bridge: 'https://gateway.optimism.io/', bridge: 'https://gateway.optimism.io/',
defaultListUrl: OPTIMISM_LIST, defaultListUrl: OPTIMISM_LIST,
@ -157,12 +216,72 @@ export const CHAIN_INFO: ChainInfo = {
explorer: 'https://optimistic.etherscan.io/', explorer: 'https://optimistic.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/optimism/', infoLink: 'https://info.uniswap.org/#/optimism/',
label: 'Optimistic Kovan', label: 'Optimistic Kovan',
rpcUrls: ['https://kovan.optimism.io'],
logoUrl: optimismLogoUrl, logoUrl: optimismLogoUrl,
nativeCurrency: { name: 'Optimistic kovETH', symbol: 'kovOpETH', decimals: 18 }, statusPage: 'https://optimism.io/status',
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ',
addNetworkInfo: {
nativeCurrency: { name: 'Optimistic Kovan Ether', symbol: 'kovOpETH', decimals: 18 },
rpcUrl: 'https://kovan.optimism.io',
},
},
[SupportedChainId.ARBITRUM_ONE]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://bridge.arbitrum.io/',
docs: 'https://offchainlabs.com/',
explorer: 'https://arbiscan.io/',
infoLink: 'https://info.uniswap.org/#/arbitrum',
label: 'Arbitrum',
logoUrl: arbitrumLogoUrl,
defaultListUrl: ARBITRUM_LIST,
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
addNetworkInfo: {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrl: 'https://arb1.arbitrum.io/rpc',
},
},
[SupportedChainId.ARBITRUM_RINKEBY]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://bridge.arbitrum.io/',
docs: 'https://offchainlabs.com/',
explorer: 'https://rinkeby-explorer.arbitrum.io/',
infoLink: 'https://info.uniswap.org/#/arbitrum/',
label: 'Arbitrum Rinkeby',
logoUrl: arbitrumLogoUrl,
defaultListUrl: ARBITRUM_LIST,
helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum',
addNetworkInfo: {
nativeCurrency: { name: 'Rinkeby Arbitrum Ether', symbol: 'rinkArbETH', decimals: 18 },
rpcUrl: 'https://rinkeby.arbitrum.io/rpc',
},
},
[SupportedChainId.POLYGON]: {
networkType: NetworkType.L1,
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://wallet.polygon.technology/bridge',
docs: 'https://polygon.io/',
explorer: 'https://polygonscan.com/',
infoLink: 'https://info.uniswap.org/#/polygon',
label: 'Polygon',
logoUrl: polygonMaticLogo,
addNetworkInfo: {
rpcUrl: 'https://polygon-rpc.com/',
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 },
},
},
[SupportedChainId.POLYGON_MUMBAI]: {
networkType: NetworkType.L1,
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://wallet.polygon.technology/bridge',
docs: 'https://polygon.io/',
explorer: 'https://mumbai.polygonscan.com/',
infoLink: 'https://info.uniswap.org/#/polygon',
label: 'Polygon Mumbai',
logoUrl: polygonMaticLogo,
addNetworkInfo: {
nativeCurrency: { name: 'Polygon Mumbai Matic', symbol: 'mMATIC', decimals: 18 },
rpcUrl: 'https://rpc-endpoints.superfluid.dev/mumbai',
},
}, },
} }
export const ARBITRUM_HELP_CENTER_LINK = 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum'
export const OPTIMISM_HELP_CENTER_LINK =
'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ'

@ -7,11 +7,12 @@ import {
DAI, DAI,
DAI_ARBITRUM_ONE, DAI_ARBITRUM_ONE,
DAI_OPTIMISM, DAI_OPTIMISM,
DAI_POLYGON,
ETH2X_FLI, ETH2X_FLI,
ExtendedEther,
FEI, FEI,
FRAX, FRAX,
FXS, FXS,
nativeOnChain,
renBTC, renBTC,
rETH2, rETH2,
sETH2, sETH2,
@ -20,13 +21,18 @@ import {
USDC, USDC,
USDC_ARBITRUM, USDC_ARBITRUM,
USDC_OPTIMISM, USDC_OPTIMISM,
USDC_POLYGON,
USDT, USDT,
USDT_ARBITRUM_ONE, USDT_ARBITRUM_ONE,
USDT_OPTIMISM, USDT_OPTIMISM,
USDT_POLYGON,
WBTC, WBTC,
WBTC_ARBITRUM_ONE, WBTC_ARBITRUM_ONE,
WBTC_OPTIMISM, WBTC_OPTIMISM,
WETH9_EXTENDED, WBTC_POLYGON,
WETH_POLYGON,
WETH_POLYGON_MUMBAI,
WRAPPED_NATIVE_CURRENCY,
} from './tokens' } from './tokens'
type ChainTokenList = { type ChainTokenList = {
@ -37,21 +43,33 @@ type ChainCurrencyList = {
readonly [chainId: number]: Currency[] readonly [chainId: number]: Currency[]
} }
const WETH_ONLY: ChainTokenList = Object.fromEntries( const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
Object.entries(WETH9_EXTENDED).map(([key, value]) => [key, [value]]) Object.entries(WRAPPED_NATIVE_CURRENCY).map(([key, value]) => [key, [value]])
) )
// used to construct intermediary pairs for trading // used to construct intermediary pairs for trading
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = { export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
...WETH_ONLY, ...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WETH_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC], [SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.OPTIMISM]: [...WETH_ONLY[SupportedChainId.OPTIMISM], DAI_OPTIMISM, USDT_OPTIMISM, WBTC_OPTIMISM], [SupportedChainId.OPTIMISM]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.OPTIMISM],
DAI_OPTIMISM,
USDT_OPTIMISM,
WBTC_OPTIMISM,
],
[SupportedChainId.ARBITRUM_ONE]: [ [SupportedChainId.ARBITRUM_ONE]: [
...WETH_ONLY[SupportedChainId.ARBITRUM_ONE], ...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.ARBITRUM_ONE],
DAI_ARBITRUM_ONE, DAI_ARBITRUM_ONE,
USDT_ARBITRUM_ONE, USDT_ARBITRUM_ONE,
WBTC_ARBITRUM_ONE, WBTC_ARBITRUM_ONE,
], ],
[SupportedChainId.POLYGON]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.POLYGON],
DAI_POLYGON,
USDC_POLYGON,
USDT_POLYGON,
WETH_POLYGON,
],
} }
export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = { export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
[SupportedChainId.MAINNET]: { [SupportedChainId.MAINNET]: {
@ -72,7 +90,7 @@ export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: To
*/ */
export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = { export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
[SupportedChainId.MAINNET]: { [SupportedChainId.MAINNET]: {
[AMPL.address]: [DAI, WETH9_EXTENDED[SupportedChainId.MAINNET]], [AMPL.address]: [DAI, WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET]],
}, },
} }
@ -81,49 +99,62 @@ export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[
*/ */
export const COMMON_BASES: ChainCurrencyList = { export const COMMON_BASES: ChainCurrencyList = {
[SupportedChainId.MAINNET]: [ [SupportedChainId.MAINNET]: [
ExtendedEther.onChain(SupportedChainId.MAINNET), nativeOnChain(SupportedChainId.MAINNET),
DAI, DAI,
USDC, USDC,
USDT, USDT,
WBTC, WBTC,
WETH9_EXTENDED[SupportedChainId.MAINNET], WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET],
], ],
[SupportedChainId.ROPSTEN]: [ [SupportedChainId.ROPSTEN]: [
ExtendedEther.onChain(SupportedChainId.ROPSTEN), nativeOnChain(SupportedChainId.ROPSTEN),
WETH9_EXTENDED[SupportedChainId.ROPSTEN], WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN],
], ],
[SupportedChainId.RINKEBY]: [ [SupportedChainId.RINKEBY]: [
ExtendedEther.onChain(SupportedChainId.RINKEBY), nativeOnChain(SupportedChainId.RINKEBY),
WETH9_EXTENDED[SupportedChainId.RINKEBY], WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY],
], ],
[SupportedChainId.GOERLI]: [ExtendedEther.onChain(SupportedChainId.GOERLI), WETH9_EXTENDED[SupportedChainId.GOERLI]], [SupportedChainId.GOERLI]: [nativeOnChain(SupportedChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI]],
[SupportedChainId.KOVAN]: [ExtendedEther.onChain(SupportedChainId.KOVAN), WETH9_EXTENDED[SupportedChainId.KOVAN]], [SupportedChainId.KOVAN]: [nativeOnChain(SupportedChainId.KOVAN), WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN]],
[SupportedChainId.ARBITRUM_ONE]: [ [SupportedChainId.ARBITRUM_ONE]: [
ExtendedEther.onChain(SupportedChainId.ARBITRUM_ONE), nativeOnChain(SupportedChainId.ARBITRUM_ONE),
DAI_ARBITRUM_ONE, DAI_ARBITRUM_ONE,
USDC_ARBITRUM, USDC_ARBITRUM,
USDT_ARBITRUM_ONE, USDT_ARBITRUM_ONE,
WBTC_ARBITRUM_ONE, WBTC_ARBITRUM_ONE,
WETH9_EXTENDED[SupportedChainId.ARBITRUM_ONE], WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE],
], ],
[SupportedChainId.ARBITRUM_RINKEBY]: [ [SupportedChainId.ARBITRUM_RINKEBY]: [
ExtendedEther.onChain(SupportedChainId.ARBITRUM_RINKEBY), nativeOnChain(SupportedChainId.ARBITRUM_RINKEBY),
WETH9_EXTENDED[SupportedChainId.ARBITRUM_RINKEBY], WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY],
], ],
[SupportedChainId.OPTIMISM]: [ [SupportedChainId.OPTIMISM]: [
ExtendedEther.onChain(SupportedChainId.OPTIMISM), nativeOnChain(SupportedChainId.OPTIMISM),
DAI_OPTIMISM, DAI_OPTIMISM,
USDC_OPTIMISM, USDC_OPTIMISM,
USDT_OPTIMISM, USDT_OPTIMISM,
WBTC_OPTIMISM, WBTC_OPTIMISM,
], ],
[SupportedChainId.OPTIMISTIC_KOVAN]: [ExtendedEther.onChain(SupportedChainId.OPTIMISTIC_KOVAN)], [SupportedChainId.OPTIMISTIC_KOVAN]: [nativeOnChain(SupportedChainId.OPTIMISTIC_KOVAN)],
[SupportedChainId.POLYGON]: [
nativeOnChain(SupportedChainId.POLYGON),
WETH_POLYGON,
USDC_POLYGON,
DAI_POLYGON,
USDT_POLYGON,
WBTC_POLYGON,
],
[SupportedChainId.POLYGON_MUMBAI]: [
nativeOnChain(SupportedChainId.POLYGON_MUMBAI),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.POLYGON_MUMBAI],
WETH_POLYGON_MUMBAI,
],
} }
// used to construct the list of all pairs we consider by default in the frontend // used to construct the list of all pairs we consider by default in the frontend
export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = { export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
...WETH_ONLY, ...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WETH_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC], [SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
} }
export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = { export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
[SupportedChainId.MAINNET]: [ [SupportedChainId.MAINNET]: [

@ -1,4 +1,4 @@
import { Ether, Token, WETH9 } from '@uniswap/sdk-core' import { Currency, Ether, NativeCurrency, Token, WETH9 } from '@uniswap/sdk-core'
import { UNI_ADDRESS } from './addresses' import { UNI_ADDRESS } from './addresses'
import { SupportedChainId } from './chains' import { SupportedChainId } from './chains'
@ -45,6 +45,34 @@ export const USDC_ARBITRUM = new Token(
'USDC', 'USDC',
'USD//C' 'USD//C'
) )
export const USDC_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
6,
'USDC',
'USD//C'
)
export const DAI_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
18,
'DAI',
'Dai Stablecoin'
)
export const USDT_POLYGON = new Token(
SupportedChainId.POLYGON,
'0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
6,
'USDT',
'Tether USD'
)
export const WBTC_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6',
8,
'WBTC',
'Wrapped BTC'
)
export const USDC_OPTIMISM = new Token( export const USDC_OPTIMISM = new Token(
SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607', '0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
@ -157,6 +185,21 @@ export const SWISE = new Token(
'SWISE', 'SWISE',
'StakeWise' 'StakeWise'
) )
export const WETH_POLYGON_MUMBAI = new Token(
SupportedChainId.POLYGON_MUMBAI,
'0xa6fa4fb5f76172d178d61b04b0ecd319c5d1c0aa',
18,
'WETH',
'Wrapped Ether'
)
export const WETH_POLYGON = new Token(
SupportedChainId.POLYGON,
'0x7ceb23fd6bc0add59e62ac25578270cff1b9f619',
18,
'WETH',
'Wrapped Ether'
)
export const UNI: { [chainId: number]: Token } = { export const UNI: { [chainId: number]: Token } = {
[SupportedChainId.MAINNET]: new Token(SupportedChainId.MAINNET, UNI_ADDRESS[1], 18, 'UNI', 'Uniswap'), [SupportedChainId.MAINNET]: new Token(SupportedChainId.MAINNET, UNI_ADDRESS[1], 18, 'UNI', 'Uniswap'),
[SupportedChainId.RINKEBY]: new Token(SupportedChainId.RINKEBY, UNI_ADDRESS[4], 18, 'UNI', 'Uniswap'), [SupportedChainId.RINKEBY]: new Token(SupportedChainId.RINKEBY, UNI_ADDRESS[4], 18, 'UNI', 'Uniswap'),
@ -165,7 +208,7 @@ export const UNI: { [chainId: number]: Token } = {
[SupportedChainId.KOVAN]: new Token(SupportedChainId.KOVAN, UNI_ADDRESS[42], 18, 'UNI', 'Uniswap'), [SupportedChainId.KOVAN]: new Token(SupportedChainId.KOVAN, UNI_ADDRESS[42], 18, 'UNI', 'Uniswap'),
} }
export const WETH9_EXTENDED: { [chainId: number]: Token } = { export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = {
...WETH9, ...WETH9,
[SupportedChainId.OPTIMISM]: new Token( [SupportedChainId.OPTIMISM]: new Token(
SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISM,
@ -195,17 +238,61 @@ export const WETH9_EXTENDED: { [chainId: number]: Token } = {
'WETH', 'WETH',
'Wrapped Ether' 'Wrapped Ether'
), ),
[SupportedChainId.POLYGON]: new Token(
SupportedChainId.POLYGON,
'0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
18,
'WMATIC',
'Wrapped MATIC'
),
[SupportedChainId.POLYGON_MUMBAI]: new Token(
SupportedChainId.POLYGON_MUMBAI,
'0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889',
18,
'WMATIC',
'Wrapped MATIC'
),
}
function isMatic(chainId: number): chainId is SupportedChainId.POLYGON | SupportedChainId.POLYGON_MUMBAI {
return chainId === SupportedChainId.POLYGON_MUMBAI || chainId === SupportedChainId.POLYGON
}
class MaticNativeCurrency extends NativeCurrency {
equals(other: Currency): boolean {
return other.isNative && other.chainId === this.chainId
}
get wrapped(): Token {
if (!isMatic(this.chainId)) throw new Error('Not matic')
return WRAPPED_NATIVE_CURRENCY[this.chainId]
}
public constructor(chainId: number) {
if (!isMatic(chainId)) throw new Error('Not matic')
super(chainId, 18, 'MATIC', 'Polygon Matic')
}
} }
export class ExtendedEther extends Ether { export class ExtendedEther extends Ether {
public get wrapped(): Token { public get wrapped(): Token {
if (this.chainId in WETH9_EXTENDED) return WETH9_EXTENDED[this.chainId] if (this.chainId in WRAPPED_NATIVE_CURRENCY) return WRAPPED_NATIVE_CURRENCY[this.chainId]
throw new Error('Unsupported chain ID') throw new Error('Unsupported chain ID')
} }
private static _cachedEther: { [chainId: number]: ExtendedEther } = {} private static _cachedExtendedEther: { [chainId: number]: NativeCurrency } = {}
public static onChain(chainId: number): ExtendedEther { public static onChain(chainId: number): ExtendedEther {
return this._cachedEther[chainId] ?? (this._cachedEther[chainId] = new ExtendedEther(chainId)) return this._cachedExtendedEther[chainId] ?? (this._cachedExtendedEther[chainId] = new ExtendedEther(chainId))
} }
} }
const cachedNativeCurrency: { [chainId: number]: NativeCurrency } = {}
export function nativeOnChain(chainId: number): NativeCurrency {
return (
cachedNativeCurrency[chainId] ??
(cachedNativeCurrency[chainId] = isMatic(chainId)
? new MaticNativeCurrency(chainId)
: ExtendedEther.onChain(chainId))
)
}

@ -5,7 +5,7 @@ import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '
import { useMemo } from 'react' import { useMemo } from 'react'
import { createTokenFilterFunction } from '../components/SearchModal/filtering' import { createTokenFilterFunction } from '../components/SearchModal/filtering'
import { ExtendedEther, WETH9_EXTENDED } from '../constants/tokens' import { nativeOnChain } from '../constants/tokens'
import { useAllLists, useCombinedActiveList, useInactiveListUrls } from '../state/lists/hooks' import { useAllLists, useCombinedActiveList, useInactiveListUrls } from '../state/lists/hooks'
import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo'
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks' import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
@ -225,20 +225,28 @@ export function useToken(tokenAddress?: string | null): Token | undefined | null
]) ])
} }
export function useCurrency(currencyId: string | null | undefined): Currency | null | undefined { export function useNativeCurrency(): Currency {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const isETH = currencyId?.toUpperCase() === 'ETH' return useMemo(
const token = useToken(isETH ? undefined : currencyId)
const extendedEther = useMemo(
() => () =>
chainId chainId
? ExtendedEther.onChain(chainId) ? nativeOnChain(chainId)
: // display mainnet when not connected : // display mainnet when not connected
ExtendedEther.onChain(SupportedChainId.MAINNET), nativeOnChain(SupportedChainId.MAINNET),
[chainId] [chainId]
) )
const weth = chainId ? WETH9_EXTENDED[chainId] : undefined }
if (currencyId === null || currencyId === undefined) return currencyId
if (weth?.address?.toUpperCase() === currencyId?.toUpperCase()) return weth export function useCurrency(currencyId: string | null | undefined): Currency | null | undefined {
return isETH ? extendedEther : token const nativeCurrency = useNativeCurrency()
const isNative = Boolean(nativeCurrency && currencyId?.toUpperCase() === 'ETH')
const token = useToken(isNative ? undefined : currencyId)
if (currencyId === null || currencyId === undefined) return currencyId
// this case so we use our builtin wrapped token instead of wrapped tokens on token lists
const wrappedNative = nativeCurrency?.wrapped
if (wrappedNative?.address?.toUpperCase() === currencyId?.toUpperCase()) return wrappedNative
return isNative ? nativeCurrency : token
} }

@ -38,7 +38,7 @@ import { V3Migrator } from 'types/v3/V3Migrator'
import { getContract } from 'utils' import { getContract } from 'utils'
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Erc721, Erc1155, Weth } from '../abis/types' import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Erc721, Erc1155, Weth } from '../abis/types'
import { UNI, WETH9_EXTENDED } from '../constants/tokens' import { UNI, WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { useActiveWeb3React } from './web3' import { useActiveWeb3React } from './web3'
// returns null on errors // returns null on errors
@ -74,7 +74,11 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b
export function useWETHContract(withSignerIfPossible?: boolean) { export function useWETHContract(withSignerIfPossible?: boolean) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
return useContract<Weth>(chainId ? WETH9_EXTENDED[chainId]?.address : undefined, WETH_ABI, withSignerIfPossible) return useContract<Weth>(
chainId ? WRAPPED_NATIVE_CURRENCY[chainId]?.address : undefined,
WETH_ABI,
withSignerIfPossible
)
} }
export function useERC721Contract(nftAddress?: string) { export function useERC721Contract(nftAddress?: string) {
@ -113,7 +117,7 @@ export function useV2RouterContract(): Contract | null {
return useContract(V2_ROUTER_ADDRESS, IUniswapV2Router02ABI, true) return useContract(V2_ROUTER_ADDRESS, IUniswapV2Router02ABI, true)
} }
export function useMulticall2Contract() { export function useInterfaceMulticall() {
return useContract<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallABI, false) as UniswapInterfaceMulticall return useContract<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallABI, false) as UniswapInterfaceMulticall
} }

@ -1,10 +1,10 @@
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { useSingleCallResult } from '../state/multicall/hooks' import { useSingleCallResult } from '../state/multicall/hooks'
import { useMulticall2Contract } from './useContract' import { useInterfaceMulticall } from './useContract'
// gets the current timestamp from the blockchain // gets the current timestamp from the blockchain
export default function useCurrentBlockTimestamp(): BigNumber | undefined { export default function useCurrentBlockTimestamp(): BigNumber | undefined {
const multicall = useMulticall2Contract() const multicall = useInterfaceMulticall()
return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0] return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0]
} }

@ -7,7 +7,7 @@ import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
import { useUserSlippageToleranceWithDefault } from '../state/user/hooks' import { useUserSlippageToleranceWithDefault } from '../state/user/hooks'
import { useCurrency } from './Tokens' import { useNativeCurrency } from './Tokens'
import useGasPrice from './useGasPrice' import useGasPrice from './useGasPrice'
import useUSDCPrice, { useUSDCValue } from './useUSDCPrice' import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'
import { useActiveWeb3React } from './web3' import { useActiveWeb3React } from './web3'
@ -35,19 +35,23 @@ export default function useSwapSlippageTolerance(
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const onL2 = chainId && L2_CHAIN_IDS.includes(chainId) const onL2 = chainId && L2_CHAIN_IDS.includes(chainId)
const outputDollarValue = useUSDCValue(trade?.outputAmount) const outputDollarValue = useUSDCValue(trade?.outputAmount)
const ethGasPrice = useGasPrice() const nativeGasPrice = useGasPrice()
const gasEstimate = guesstimateGas(trade) const gasEstimate = guesstimateGas(trade)
const ether = useCurrency('ETH') const nativeCurrency = useNativeCurrency()
const etherPrice = useUSDCPrice(ether ?? undefined) const nativeCurrencyPrice = useUSDCPrice(nativeCurrency ?? undefined)
const defaultSlippageTolerance = useMemo(() => { const defaultSlippageTolerance = useMemo(() => {
if (!trade || onL2) return ONE_TENTHS_PERCENT if (!trade || onL2) return ONE_TENTHS_PERCENT
const ethGasCost = const nativeGasCost =
ethGasPrice && typeof gasEstimate === 'number' ? JSBI.multiply(ethGasPrice, JSBI.BigInt(gasEstimate)) : undefined nativeGasPrice && typeof gasEstimate === 'number'
? JSBI.multiply(nativeGasPrice, JSBI.BigInt(gasEstimate))
: undefined
const dollarGasCost = const dollarGasCost =
ether && ethGasCost && etherPrice ? etherPrice.quote(CurrencyAmount.fromRawAmount(ether, ethGasCost)) : undefined nativeCurrency && nativeGasCost && nativeCurrencyPrice
? nativeCurrencyPrice.quote(CurrencyAmount.fromRawAmount(nativeCurrency, nativeGasCost))
: undefined
// if valid estimate from api and using api trade, use gas estimate from api // if valid estimate from api and using api trade, use gas estimate from api
// NOTE - dont use gas estimate for L2s yet - need to verify accuracy // NOTE - dont use gas estimate for L2s yet - need to verify accuracy
@ -68,7 +72,7 @@ export default function useSwapSlippageTolerance(
} }
return V3_SWAP_DEFAULT_SLIPPAGE return V3_SWAP_DEFAULT_SLIPPAGE
}, [trade, onL2, ethGasPrice, gasEstimate, ether, etherPrice, chainId, outputDollarValue]) }, [trade, onL2, nativeGasPrice, gasEstimate, nativeCurrency, nativeCurrencyPrice, chainId, outputDollarValue])
return useUserSlippageToleranceWithDefault(defaultSlippageTolerance) return useUserSlippageToleranceWithDefault(defaultSlippageTolerance)
} }

@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { tryParseAmount } from 'state/swap/hooks' import { tryParseAmount } from 'state/swap/hooks'
import { SupportedChainId } from '../constants/chains' import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM } from '../constants/tokens' import { DAI_OPTIMISM, USDC, USDC_ARBITRUM, USDC_POLYGON } from '../constants/tokens'
import { useBestV2Trade } from './useBestV2Trade' import { useBestV2Trade } from './useBestV2Trade'
import { useClientSideV3Trade } from './useClientSideV3Trade' import { useClientSideV3Trade } from './useClientSideV3Trade'
import { useActiveWeb3React } from './web3' import { useActiveWeb3React } from './web3'
@ -14,6 +14,7 @@ export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount<Token> }
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC, 100_000e6), [SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC, 100_000e6),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6), [SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18), [SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18),
[SupportedChainId.POLYGON]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6),
} }
/** /**

@ -1,11 +1,13 @@
import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { useMemo } from 'react' import { useMemo } from 'react'
import { WETH9_EXTENDED } from '../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { tryParseAmount } from '../state/swap/hooks' import { tryParseAmount } from '../state/swap/hooks'
import { TransactionType } from '../state/transactions/actions' import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
import { useCurrencyBalance } from '../state/wallet/hooks' import { useCurrencyBalance } from '../state/wallet/hooks'
import { useNativeCurrency } from './Tokens'
import { useWETHContract } from './useContract' import { useWETHContract } from './useContract'
import { useActiveWeb3React } from './web3' import { useActiveWeb3React } from './web3'
@ -16,6 +18,34 @@ export enum WrapType {
} }
const NOT_APPLICABLE = { wrapType: WrapType.NOT_APPLICABLE } const NOT_APPLICABLE = { wrapType: WrapType.NOT_APPLICABLE }
enum WrapInputError {
NO_ERROR, // must be equal to 0 so all other errors are truthy
ENTER_NATIVE_AMOUNT,
ENTER_WRAPPED_AMOUNT,
INSUFFICIENT_NATIVE_BALANCE,
INSUFFICIENT_WRAPPED_BALANCE,
}
export function WrapErrorText({ wrapInputError }: { wrapInputError: WrapInputError }) {
const native = useNativeCurrency()
const wrapped = native?.wrapped
switch (wrapInputError) {
case WrapInputError.NO_ERROR:
return null
case WrapInputError.ENTER_NATIVE_AMOUNT:
return <Trans>Enter {native?.symbol} amount</Trans>
case WrapInputError.ENTER_WRAPPED_AMOUNT:
return <Trans>Enter {wrapped?.symbol} amount</Trans>
case WrapInputError.INSUFFICIENT_NATIVE_BALANCE:
return <Trans>Insufficient {native?.symbol} balance</Trans>
case WrapInputError.INSUFFICIENT_WRAPPED_BALANCE:
return <Trans>Insufficient {wrapped?.symbol} balance</Trans>
}
}
/** /**
* Given the selected input and output currency, return a wrap callback * Given the selected input and output currency, return a wrap callback
* @param inputCurrency the selected input currency * @param inputCurrency the selected input currency
@ -26,7 +56,7 @@ export default function useWrapCallback(
inputCurrency: Currency | undefined | null, inputCurrency: Currency | undefined | null,
outputCurrency: Currency | undefined | null, outputCurrency: Currency | undefined | null,
typedValue: string | undefined typedValue: string | undefined
): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); inputError?: string } { ): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); inputError?: WrapInputError } {
const { chainId, account } = useActiveWeb3React() const { chainId, account } = useActiveWeb3React()
const wethContract = useWETHContract() const wethContract = useWETHContract()
const balance = useCurrencyBalance(account ?? undefined, inputCurrency ?? undefined) const balance = useCurrencyBalance(account ?? undefined, inputCurrency ?? undefined)
@ -36,7 +66,7 @@ export default function useWrapCallback(
return useMemo(() => { return useMemo(() => {
if (!wethContract || !chainId || !inputCurrency || !outputCurrency) return NOT_APPLICABLE if (!wethContract || !chainId || !inputCurrency || !outputCurrency) return NOT_APPLICABLE
const weth = WETH9_EXTENDED[chainId] const weth = WRAPPED_NATIVE_CURRENCY[chainId]
if (!weth) return NOT_APPLICABLE if (!weth) return NOT_APPLICABLE
const hasInputAmount = Boolean(inputAmount?.greaterThan('0')) const hasInputAmount = Boolean(inputAmount?.greaterThan('0'))
@ -54,13 +84,18 @@ export default function useWrapCallback(
type: TransactionType.WRAP, type: TransactionType.WRAP,
unwrapped: false, unwrapped: false,
currencyAmountRaw: inputAmount?.quotient.toString(), currencyAmountRaw: inputAmount?.quotient.toString(),
chainId,
}) })
} catch (error) { } catch (error) {
console.error('Could not deposit', error) console.error('Could not deposit', error)
} }
} }
: undefined, : undefined,
inputError: sufficientBalance ? undefined : hasInputAmount ? 'Insufficient ETH balance' : 'Enter ETH amount', inputError: sufficientBalance
? undefined
: hasInputAmount
? WrapInputError.INSUFFICIENT_NATIVE_BALANCE
: WrapInputError.ENTER_NATIVE_AMOUNT,
} }
} else if (weth.equals(inputCurrency) && outputCurrency.isNative) { } else if (weth.equals(inputCurrency) && outputCurrency.isNative) {
return { return {
@ -74,13 +109,18 @@ export default function useWrapCallback(
type: TransactionType.WRAP, type: TransactionType.WRAP,
unwrapped: true, unwrapped: true,
currencyAmountRaw: inputAmount?.quotient.toString(), currencyAmountRaw: inputAmount?.quotient.toString(),
chainId,
}) })
} catch (error) { } catch (error) {
console.error('Could not withdraw', error) console.error('Could not withdraw', error)
} }
} }
: undefined, : undefined,
inputError: sufficientBalance ? undefined : hasInputAmount ? 'Insufficient WETH balance' : 'Enter WETH amount', inputError: sufficientBalance
? undefined
: hasInputAmount
? WrapInputError.INSUFFICIENT_WRAPPED_BALANCE
: WrapInputError.ENTER_WRAPPED_AMOUNT,
} }
} else { } else {
return NOT_APPLICABLE return NOT_APPLICABLE

@ -71,3 +71,4 @@ ReactDOM.render(
if (process.env.REACT_APP_SERVICE_WORKER !== 'false') { if (process.env.REACT_APP_SERVICE_WORKER !== 'false') {
serviceWorkerRegistration.register() serviceWorkerRegistration.register()
} }
export { INFURA_NETWORK_URLS } from './constants/chains'

@ -35,7 +35,7 @@ import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '../../constants/addresses' import { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '../../constants/addresses'
import { ZERO_PERCENT } from '../../constants/misc' import { ZERO_PERCENT } from '../../constants/misc'
import { WETH9_EXTENDED } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useArgentWalletContract } from '../../hooks/useArgentWalletContract' import { useArgentWalletContract } from '../../hooks/useArgentWalletContract'
@ -315,10 +315,12 @@ export default function AddLiquidity({
} else { } else {
// prevent weth + eth // prevent weth + eth
const isETHOrWETHNew = const isETHOrWETHNew =
currencyIdNew === 'ETH' || (chainId !== undefined && currencyIdNew === WETH9_EXTENDED[chainId]?.address) currencyIdNew === 'ETH' ||
(chainId !== undefined && currencyIdNew === WRAPPED_NATIVE_CURRENCY[chainId]?.address)
const isETHOrWETHOther = const isETHOrWETHOther =
currencyIdOther !== undefined && currencyIdOther !== undefined &&
(currencyIdOther === 'ETH' || (chainId !== undefined && currencyIdOther === WETH9_EXTENDED[chainId]?.address)) (currencyIdOther === 'ETH' ||
(chainId !== undefined && currencyIdOther === WRAPPED_NATIVE_CURRENCY[chainId]?.address))
if (isETHOrWETHNew && isETHOrWETHOther) { if (isETHOrWETHNew && isETHOrWETHOther) {
return [currencyIdNew, undefined] return [currencyIdNew, undefined]

@ -1,7 +1,7 @@
import { useActiveWeb3React } from 'hooks/web3' import { useActiveWeb3React } from 'hooks/web3'
import { Redirect, RouteComponentProps } from 'react-router-dom' import { Redirect, RouteComponentProps } from 'react-router-dom'
import { WETH9_EXTENDED } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import AddLiquidity from './index' import AddLiquidity from './index'
export function RedirectDuplicateTokenIds( export function RedirectDuplicateTokenIds(
@ -17,9 +17,9 @@ export function RedirectDuplicateTokenIds(
// prevent weth + eth // prevent weth + eth
const isETHOrWETHA = const isETHOrWETHA =
currencyIdA === 'ETH' || (chainId !== undefined && currencyIdA === WETH9_EXTENDED[chainId]?.address) currencyIdA === 'ETH' || (chainId !== undefined && currencyIdA === WRAPPED_NATIVE_CURRENCY[chainId]?.address)
const isETHOrWETHB = const isETHOrWETHB =
currencyIdB === 'ETH' || (chainId !== undefined && currencyIdB === WETH9_EXTENDED[chainId]?.address) currencyIdB === 'ETH' || (chainId !== undefined && currencyIdB === WRAPPED_NATIVE_CURRENCY[chainId]?.address)
if ( if (
currencyIdA && currencyIdA &&

@ -21,7 +21,7 @@ import { MinimalPositionCard } from '../../components/PositionCard'
import Row, { RowBetween, RowFlat } from '../../components/Row' import Row, { RowBetween, RowFlat } from '../../components/Row'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { ZERO_PERCENT } from '../../constants/misc' import { ZERO_PERCENT } from '../../constants/misc'
import { WETH9_EXTENDED } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useV2RouterContract } from '../../hooks/useContract' import { useV2RouterContract } from '../../hooks/useContract'
@ -61,8 +61,8 @@ export default function AddLiquidity({
const oneCurrencyIsWETH = Boolean( const oneCurrencyIsWETH = Boolean(
chainId && chainId &&
((currencyA && currencyA.equals(WETH9_EXTENDED[chainId])) || ((currencyA && currencyA.equals(WRAPPED_NATIVE_CURRENCY[chainId])) ||
(currencyB && currencyB.equals(WETH9_EXTENDED[chainId]))) (currencyB && currencyB.equals(WRAPPED_NATIVE_CURRENCY[chainId])))
) )
const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected const toggleWalletModal = useWalletModalToggle() // toggle wallet when disconnected

@ -36,7 +36,7 @@ import CurrencyLogo from '../../components/CurrencyLogo'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount' import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row' import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses' import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import { WETH9_EXTENDED } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { useToken } from '../../hooks/Tokens' import { useToken } from '../../hooks/Tokens'
import { usePairContract, useV2MigratorContract } from '../../hooks/useContract' import { usePairContract, useV2MigratorContract } from '../../hooks/useContract'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit' import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
@ -592,10 +592,10 @@ function V2PairMigration({
<ThemedText.Black fontSize={12}> <ThemedText.Black fontSize={12}>
<Trans> <Trans>
At least {formatCurrencyAmount(refund0, 4)}{' '} At least {formatCurrencyAmount(refund0, 4)}{' '}
{token0.equals(WETH9_EXTENDED[chainId]) ? 'ETH' : token0.symbol} and{' '} {token0.equals(WRAPPED_NATIVE_CURRENCY[chainId]) ? 'ETH' : token0.symbol} and{' '}
{formatCurrencyAmount(refund1, 4)}{' '} {formatCurrencyAmount(refund1, 4)}{' '}
{token1.equals(WETH9_EXTENDED[chainId]) ? 'ETH' : token1.symbol} will be refunded to your wallet {token1.equals(WRAPPED_NATIVE_CURRENCY[chainId]) ? 'ETH' : token1.symbol} will be refunded to your
due to selected price range. wallet due to selected price range.
</Trans> </Trans>
</ThemedText.Black> </ThemedText.Black>
) : null} ) : null}

@ -4,11 +4,10 @@ import { AutoColumn } from 'components/Column'
import DowntimeWarning from 'components/DowntimeWarning' import DowntimeWarning from 'components/DowntimeWarning'
import { FlyoutAlignment, NewMenu } from 'components/Menu' import { FlyoutAlignment, NewMenu } from 'components/Menu'
import { SwapPoolTabs } from 'components/NavigationTabs' import { SwapPoolTabs } from 'components/NavigationTabs'
import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert' import { SingleRowNetworkAlert } from 'components/NetworkAlert/NetworkAlert'
import PositionList from 'components/PositionList' import PositionList from 'components/PositionList'
import { RowBetween, RowFixed } from 'components/Row' import { RowBetween, RowFixed } from 'components/Row'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink' import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import { L2_CHAIN_IDS } from 'constants/chains'
import { useV3Positions } from 'hooks/useV3Positions' import { useV3Positions } from 'hooks/useV3Positions'
import { useActiveWeb3React } from 'hooks/web3' import { useActiveWeb3React } from 'hooks/web3'
import { useContext } from 'react' import { useContext } from 'react'
@ -20,6 +19,7 @@ import styled, { ThemeContext } from 'styled-components/macro'
import { HideSmall, ThemedText } from 'theme' import { HideSmall, ThemedText } from 'theme'
import { PositionDetails } from 'types/position' import { PositionDetails } from 'types/position'
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import CTACards from './CTACards' import CTACards from './CTACards'
import { LoadingRows } from './styleds' import { LoadingRows } from './styleds'
@ -128,6 +128,25 @@ const ResponsiveRow = styled(RowFixed)`
`}; `};
` `
function PositionsLoadingPlaceholder() {
return (
<LoadingRows>
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
</LoadingRows>
)
}
export default function Pool() { export default function Pool() {
const { account, chainId } = useActiveWeb3React() const { account, chainId } = useActiveWeb3React()
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
@ -147,7 +166,7 @@ export default function Pool() {
const filteredPositions = [...openPositions, ...(userHideClosedPositions ? [] : closedPositions)] const filteredPositions = [...openPositions, ...(userHideClosedPositions ? [] : closedPositions)]
const showConnectAWallet = Boolean(!account) const showConnectAWallet = Boolean(!account)
const showV2Features = !!chainId && !L2_CHAIN_IDS.includes(chainId) const showV2Features = Boolean(chainId && V2_FACTORY_ADDRESSES[chainId])
const menuItems = [ const menuItems = [
{ {
@ -224,27 +243,14 @@ export default function Pool() {
</TitleRow> </TitleRow>
<HideSmall> <HideSmall>
<NetworkAlert thin /> <SingleRowNetworkAlert />
<DowntimeWarning /> <DowntimeWarning />
<CTACards /> <CTACards />
</HideSmall> </HideSmall>
<MainContentWrapper> <MainContentWrapper>
{positionsLoading ? ( {positionsLoading ? (
<LoadingRows> <PositionsLoadingPlaceholder />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
<div />
</LoadingRows>
) : filteredPositions && filteredPositions.length > 0 ? ( ) : filteredPositions && filteredPositions.length > 0 ? (
<PositionList positions={filteredPositions} /> <PositionList positions={filteredPositions} />
) : ( ) : (

@ -16,7 +16,7 @@ import { MinimalPositionCard } from '../../components/PositionCard'
import Row from '../../components/Row' import Row from '../../components/Row'
import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModal' import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModal'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink' import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { ExtendedEther } from '../../constants/tokens' import { nativeOnChain } from '../../constants/tokens'
import { PairState, useV2Pair } from '../../hooks/useV2Pairs' import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { usePairAdder } from '../../state/user/hooks' import { usePairAdder } from '../../state/user/hooks'
@ -44,7 +44,7 @@ export default function PoolFinder() {
const [showSearch, setShowSearch] = useState<boolean>(false) const [showSearch, setShowSearch] = useState<boolean>(false)
const [activeField, setActiveField] = useState<number>(Fields.TOKEN1) const [activeField, setActiveField] = useState<number>(Fields.TOKEN1)
const [currency0, setCurrency0] = useState<Currency | null>(() => (chainId ? ExtendedEther.onChain(chainId) : null)) const [currency0, setCurrency0] = useState<Currency | null>(() => (chainId ? nativeOnChain(chainId) : null))
const [currency1, setCurrency1] = useState<Currency | null>(null) const [currency1, setCurrency1] = useState<Currency | null>(null)
const [pairState, pair] = useV2Pair(currency0 ?? undefined, currency1 ?? undefined) const [pairState, pair] = useV2Pair(currency0 ?? undefined, currency1 ?? undefined)

@ -32,7 +32,7 @@ import { useUserSlippageToleranceWithDefault } from 'state/user/hooks'
import { ThemedText } from 'theme' import { ThemedText } from 'theme'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { WETH9_EXTENDED } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { TransactionType } from '../../state/transactions/actions' import { TransactionType } from '../../state/transactions/actions'
import { calculateGasMargin } from '../../utils/calculateGasMargin' import { calculateGasMargin } from '../../utils/calculateGasMargin'
import { currencyId } from '../../utils/currencyId' import { currencyId } from '../../utils/currencyId'
@ -266,8 +266,8 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
liquidityValue1?.currency && liquidityValue1?.currency &&
(liquidityValue0.currency.isNative || (liquidityValue0.currency.isNative ||
liquidityValue1.currency.isNative || liquidityValue1.currency.isNative ||
liquidityValue0.currency.wrapped.equals(WETH9_EXTENDED[liquidityValue0.currency.chainId]) || liquidityValue0.currency.wrapped.equals(WRAPPED_NATIVE_CURRENCY[liquidityValue0.currency.chainId]) ||
liquidityValue1.currency.wrapped.equals(WETH9_EXTENDED[liquidityValue1.currency.chainId])) liquidityValue1.currency.wrapped.equals(WRAPPED_NATIVE_CURRENCY[liquidityValue1.currency.chainId]))
) )
return ( return (
<AutoColumn> <AutoColumn>

@ -22,7 +22,7 @@ import Row, { RowBetween, RowFixed } from '../../components/Row'
import Slider from '../../components/Slider' import Slider from '../../components/Slider'
import { Dots } from '../../components/swap/styleds' import { Dots } from '../../components/swap/styleds'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import { WETH9_EXTENDED } from '../../constants/tokens' import { WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { usePairContract, useV2RouterContract } from '../../hooks/useContract' import { usePairContract, useV2RouterContract } from '../../hooks/useContract'
@ -388,8 +388,8 @@ export default function RemoveLiquidity({
const oneCurrencyIsETH = currencyA?.isNative || currencyB?.isNative const oneCurrencyIsETH = currencyA?.isNative || currencyB?.isNative
const oneCurrencyIsWETH = Boolean( const oneCurrencyIsWETH = Boolean(
chainId && chainId &&
WETH9_EXTENDED[chainId] && WRAPPED_NATIVE_CURRENCY[chainId] &&
(currencyA?.equals(WETH9_EXTENDED[chainId]) || currencyB?.equals(WETH9_EXTENDED[chainId])) (currencyA?.equals(WRAPPED_NATIVE_CURRENCY[chainId]) || currencyB?.equals(WRAPPED_NATIVE_CURRENCY[chainId]))
) )
const handleSelectCurrencyA = useCallback( const handleSelectCurrencyA = useCallback(
@ -532,17 +532,17 @@ export default function RemoveLiquidity({
<RowBetween style={{ justifyContent: 'flex-end' }}> <RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? ( {oneCurrencyIsETH ? (
<StyledInternalLink <StyledInternalLink
to={`/remove/v2/${currencyA?.isNative ? WETH9_EXTENDED[chainId].address : currencyIdA}/${ to={`/remove/v2/${
currencyB?.isNative ? WETH9_EXTENDED[chainId].address : currencyIdB currencyA?.isNative ? WRAPPED_NATIVE_CURRENCY[chainId].address : currencyIdA
}`} }/${currencyB?.isNative ? WRAPPED_NATIVE_CURRENCY[chainId].address : currencyIdB}`}
> >
Receive WETH Receive WETH
</StyledInternalLink> </StyledInternalLink>
) : oneCurrencyIsWETH ? ( ) : oneCurrencyIsWETH ? (
<StyledInternalLink <StyledInternalLink
to={`/remove/v2/${currencyA?.equals(WETH9_EXTENDED[chainId]) ? 'ETH' : currencyIdA}/${ to={`/remove/v2/${
currencyB?.equals(WETH9_EXTENDED[chainId]) ? 'ETH' : currencyIdB currencyA?.equals(WRAPPED_NATIVE_CURRENCY[chainId]) ? 'ETH' : currencyIdA
}`} }/${currencyB?.equals(WRAPPED_NATIVE_CURRENCY[chainId]) ? 'ETH' : currencyIdB}`}
> >
Receive ETH Receive ETH
</StyledInternalLink> </StyledInternalLink>

@ -14,7 +14,7 @@ import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom' import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { TradeState } from 'state/routing/types' import { TradeState } from 'state/routing/types'
import { ThemeContext } from 'styled-components/macro' import styled, { ThemeContext } from 'styled-components/macro'
import AddressInputPanel from '../../components/AddressInputPanel' import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
@ -38,7 +38,7 @@ import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported' import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useSwapCallback } from '../../hooks/useSwapCallback' import { useSwapCallback } from '../../hooks/useSwapCallback'
import { useUSDCValue } from '../../hooks/useUSDCPrice' import { useUSDCValue } from '../../hooks/useUSDCPrice'
import useWrapCallback, { WrapType } from '../../hooks/useWrapCallback' import useWrapCallback, { WrapErrorText, WrapType } from '../../hooks/useWrapCallback'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
@ -55,6 +55,10 @@ import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { warningSeverity } from '../../utils/prices' import { warningSeverity } from '../../utils/prices'
import AppBody from '../AppBody' import AppBody from '../AppBody'
const AlertWrapper = styled.div`
max-width: 480px;
`
export default function Swap({ history }: RouteComponentProps) { export default function Swap({ history }: RouteComponentProps) {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch() const loadedUrlParams = useDefaultsFromURLSearch()
@ -374,7 +378,9 @@ export default function Swap({ history }: RouteComponentProps) {
onConfirm={handleConfirmTokenWarning} onConfirm={handleConfirmTokenWarning}
onDismiss={handleDismissTokenWarning} onDismiss={handleDismissTokenWarning}
/> />
<NetworkAlert /> <AlertWrapper>
<NetworkAlert />
</AlertWrapper>
<AppBody> <AppBody>
<SwapHeader allowedSlippage={allowedSlippage} /> <SwapHeader allowedSlippage={allowedSlippage} />
<Wrapper id="swap-page"> <Wrapper id="swap-page">
@ -473,12 +479,13 @@ export default function Swap({ history }: RouteComponentProps) {
</ButtonLight> </ButtonLight>
) : showWrap ? ( ) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}> <ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ?? {wrapInputError ? (
(wrapType === WrapType.WRAP ? ( <WrapErrorText wrapInputError={wrapInputError} />
<Trans>Wrap</Trans> ) : wrapType === WrapType.WRAP ? (
) : wrapType === WrapType.UNWRAP ? ( <Trans>Wrap</Trans>
<Trans>Unwrap</Trans> ) : wrapType === WrapType.UNWRAP ? (
) : null)} <Trans>Unwrap</Trans>
) : null}
</ButtonPrimary> </ButtonPrimary>
) : routeNotFound && userHasSpecifiedInputOutput && !routeIsLoading && !routeIsSyncing ? ( ) : routeNotFound && userHasSpecifiedInputOutput && !routeIsLoading && !routeIsSyncing ? (
<GreyCard style={{ textAlign: 'center' }}> <GreyCard style={{ textAlign: 'center' }}>

@ -1,10 +1,5 @@
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
export const StandardPageWrapper = styled.div`
padding-top: 160px;
width: 100%;
`
export const IframeBodyWrapper = styled.div` export const IframeBodyWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;

@ -19,7 +19,6 @@ describe('application reducer', () => {
1: 3, 1: 3,
}, },
chainId: null, chainId: null,
implements3085: false,
openModal: null, openModal: null,
popupList: [], popupList: [],
}) })

@ -1,11 +1,17 @@
import { createSlice, nanoid } from '@reduxjs/toolkit' import { createSlice, nanoid } from '@reduxjs/toolkit'
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc' import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
export type PopupContent = { import { SupportedChainId } from '../../constants/chains'
txn: {
hash: string export type PopupContent =
} | {
} txn: {
hash: string
}
}
| {
failedSwitchNetwork: SupportedChainId
}
export enum ApplicationModal { export enum ApplicationModal {
WALLET, WALLET,
@ -26,7 +32,6 @@ type PopupList = Array<{ key: string; show: boolean; content: PopupContent; remo
export interface ApplicationState { export interface ApplicationState {
readonly blockNumber: { readonly [chainId: number]: number } readonly blockNumber: { readonly [chainId: number]: number }
readonly chainId: number | null readonly chainId: number | null
readonly implements3085: boolean
readonly openModal: ApplicationModal | null readonly openModal: ApplicationModal | null
readonly popupList: PopupList readonly popupList: PopupList
} }
@ -34,7 +39,6 @@ export interface ApplicationState {
const initialState: ApplicationState = { const initialState: ApplicationState = {
blockNumber: {}, blockNumber: {},
chainId: null, chainId: null,
implements3085: false,
openModal: null, openModal: null,
popupList: [], popupList: [],
} }
@ -75,12 +79,8 @@ const applicationSlice = createSlice({
} }
}) })
}, },
setImplements3085(state, { payload: { implements3085 } }) {
state.implements3085 = implements3085
},
}, },
}) })
export const { updateChainId, updateBlockNumber, setOpenModal, addPopup, removePopup, setImplements3085 } = export const { updateChainId, updateBlockNumber, setOpenModal, addPopup, removePopup } = applicationSlice.actions
applicationSlice.actions
export default applicationSlice.reducer export default applicationSlice.reducer

@ -5,9 +5,8 @@ import { useCallback, useEffect, useState } from 'react'
import { api, CHAIN_TAG } from 'state/data/enhanced' import { api, CHAIN_TAG } from 'state/data/enhanced'
import { useAppDispatch, useAppSelector } from 'state/hooks' import { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId' import { supportedChainId } from 'utils/supportedChainId'
import { switchToNetwork } from 'utils/switchToNetwork'
import { setImplements3085, updateBlockNumber, updateChainId } from './reducer' import { updateBlockNumber, updateChainId } from './reducer'
function useQueryCacheInvalidator() { function useQueryCacheInvalidator() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@ -23,7 +22,7 @@ function useQueryCacheInvalidator() {
} }
export default function Updater(): null { export default function Updater(): null {
const { account, chainId, library } = useActiveWeb3React() const { chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible() const windowVisible = useIsWindowVisible()
@ -77,19 +76,5 @@ export default function Updater(): null {
) )
}, [dispatch, debouncedState.chainId]) }, [dispatch, debouncedState.chainId])
const implements3085 = useAppSelector((state) => state.application.implements3085)
useEffect(() => {
if (!library?.provider?.request) {
dispatch(setImplements3085({ implements3085: false }))
} else if (account && !implements3085) {
switchToNetwork({ library })
.then((x) => x ?? dispatch(setImplements3085({ implements3085: true })))
.catch(() => dispatch(setImplements3085({ implements3085: false })))
} else if (!account && implements3085) {
dispatch(setImplements3085({ implements3085: false }))
}
}, [account, dispatch, implements3085, library])
return null return null
} }

@ -13,6 +13,8 @@ const CHAIN_SUBGRAPH_URL: Record<number, string> = {
[SupportedChainId.ARBITRUM_ONE]: 'https://api.thegraph.com/subgraphs/name/ianlapham/arbitrum-minimal', [SupportedChainId.ARBITRUM_ONE]: 'https://api.thegraph.com/subgraphs/name/ianlapham/arbitrum-minimal',
[SupportedChainId.OPTIMISM]: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-optimism-dev', [SupportedChainId.OPTIMISM]: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-optimism-dev',
[SupportedChainId.POLYGON]: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-polygon',
} }
export const api = createApi({ export const api = createApi({

@ -194,14 +194,14 @@ export function useV3DerivedMintInfo(
// check for invalid price input (converts to invalid ratio) // check for invalid price input (converts to invalid ratio)
const invalidPrice = useMemo(() => { const invalidPrice = useMemo(() => {
const sqrtRatioX96 = price ? encodeSqrtRatioX96(price.numerator, price.denominator) : undefined const sqrtRatioX96 = price ? encodeSqrtRatioX96(price.numerator, price.denominator) : undefined
const invalid = return (
price && price &&
sqrtRatioX96 && sqrtRatioX96 &&
!( !(
JSBI.greaterThanOrEqual(sqrtRatioX96, TickMath.MIN_SQRT_RATIO) && JSBI.greaterThanOrEqual(sqrtRatioX96, TickMath.MIN_SQRT_RATIO) &&
JSBI.lessThan(sqrtRatioX96, TickMath.MAX_SQRT_RATIO) JSBI.lessThan(sqrtRatioX96, TickMath.MAX_SQRT_RATIO)
) )
return invalid )
}, [price]) }, [price])
// used for ratio calculation when pool not initialized // used for ratio calculation when pool not initialized

@ -1,4 +1,4 @@
import { useMulticall2Contract } from '../../hooks/useContract' import { useInterfaceMulticall } from '../../hooks/useContract'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { useBlockNumber } from '../application/hooks' import { useBlockNumber } from '../application/hooks'
import { multicall } from './instance' import { multicall } from './instance'
@ -7,6 +7,6 @@ import { multicall } from './instance'
export default function Updater() { export default function Updater() {
const latestBlockNumber = useBlockNumber() const latestBlockNumber = useBlockNumber()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const multicall2Contract = useMulticall2Contract() const multicall2Contract = useInterfaceMulticall()
return <multicall.Updater chainId={chainId} latestBlockNumber={latestBlockNumber} contract={multicall2Contract} /> return <multicall.Updater chainId={chainId} latestBlockNumber={latestBlockNumber} contract={multicall2Contract} />
} }

@ -1,6 +1,5 @@
import { AlphaRouterParams, IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router' import { AlphaRouterParams, IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
import { NETWORK_URLS } from 'connectors' import { INFURA_NETWORK_URLS, SupportedChainId } from 'constants/chains'
import { SupportedChainId } from 'constants/chains'
import { providers } from 'ethers/lib/ethers' import { providers } from 'ethers/lib/ethers'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
@ -14,7 +13,7 @@ export type Dependencies = {
export function buildDependencies(): Dependencies { export function buildDependencies(): Dependencies {
const dependenciesByChain: Dependencies = {} const dependenciesByChain: Dependencies = {}
for (const chainId of AUTO_ROUTER_SUPPORTED_CHAINS) { for (const chainId of AUTO_ROUTER_SUPPORTED_CHAINS) {
const provider = new providers.JsonRpcProvider(NETWORK_URLS[chainId]) const provider = new providers.JsonRpcProvider(INFURA_NETWORK_URLS[chainId])
dependenciesByChain[chainId] = { dependenciesByChain[chainId] = {
chainId, chainId,

@ -1,12 +1,13 @@
import { Ether, Token, TradeType } from '@uniswap/sdk-core' import { Token, TradeType } from '@uniswap/sdk-core'
import { nativeOnChain } from '../../constants/tokens'
import { computeRoutes } from './utils' import { computeRoutes } from './utils'
const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC') const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI') const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI')
const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR') const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR')
const ETH = Ether.onChain(1) const ETH = nativeOnChain(1)
// helper function to make amounts more readable // helper function to make amounts more readable
const amount = (raw: TemplateStringsArray) => (parseInt(raw[0]) * 1e6).toString() const amount = (raw: TemplateStringsArray) => (parseInt(raw[0]) * 1e6).toString()

@ -1,7 +1,8 @@
import { Currency, CurrencyAmount, Ether, Token, TradeType } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk' import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
import { nativeOnChain } from '../../constants/tokens'
import { GetQuoteResult, InterfaceTrade, V2PoolInRoute, V3PoolInRoute } from './types' import { GetQuoteResult, InterfaceTrade, V2PoolInRoute, V3PoolInRoute } from './types'
/** /**
@ -24,9 +25,9 @@ export function computeRoutes(
if (parsedTokenIn.address !== currencyIn.wrapped.address) return undefined if (parsedTokenIn.address !== currencyIn.wrapped.address) return undefined
if (parsedTokenOut.address !== currencyOut.wrapped.address) return undefined if (parsedTokenOut.address !== currencyOut.wrapped.address) return undefined
const parsedCurrencyIn = currencyIn.isNative ? Ether.onChain(currencyIn.chainId) : parsedTokenIn const parsedCurrencyIn = currencyIn.isNative ? nativeOnChain(currencyIn.chainId) : parsedTokenIn
const parsedCurrencyOut = currencyOut.isNative ? Ether.onChain(currencyOut.chainId) : parsedTokenOut const parsedCurrencyOut = currencyOut.isNative ? nativeOnChain(currencyOut.chainId) : parsedTokenOut
try { try {
return quoteResult.route.map((route) => { return quoteResult.route.map((route) => {

@ -7,7 +7,7 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { ReactNode, useMemo } from 'react' import { ReactNode, useMemo } from 'react'
import { DAI, UNI, USDC, USDT, WBTC, WETH9_EXTENDED } from '../../constants/tokens' import { DAI, UNI, USDC, USDT, WBTC, WRAPPED_NATIVE_CURRENCY } from '../../constants/tokens'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { NEVER_RELOAD, useMultipleContractSingleData } from '../multicall/hooks' import { NEVER_RELOAD, useMultipleContractSingleData } from '../multicall/hooks'
import { tryParseAmount } from '../swap/hooks' import { tryParseAmount } from '../swap/hooks'
@ -26,19 +26,19 @@ export const STAKING_REWARDS_INFO: {
} = { } = {
1: [ 1: [
{ {
tokens: [WETH9_EXTENDED[1], DAI], tokens: [WRAPPED_NATIVE_CURRENCY[1], DAI],
stakingRewardAddress: '0xa1484C3aa22a66C62b77E0AE78E15258bd0cB711', stakingRewardAddress: '0xa1484C3aa22a66C62b77E0AE78E15258bd0cB711',
}, },
{ {
tokens: [WETH9_EXTENDED[1], USDC], tokens: [WRAPPED_NATIVE_CURRENCY[1], USDC],
stakingRewardAddress: '0x7FBa4B8Dc5E7616e59622806932DBea72537A56b', stakingRewardAddress: '0x7FBa4B8Dc5E7616e59622806932DBea72537A56b',
}, },
{ {
tokens: [WETH9_EXTENDED[1], USDT], tokens: [WRAPPED_NATIVE_CURRENCY[1], USDT],
stakingRewardAddress: '0x6C3e4cb2E96B01F4b866965A91ed4437839A121a', stakingRewardAddress: '0x6C3e4cb2E96B01F4b866965A91ed4437839A121a',
}, },
{ {
tokens: [WETH9_EXTENDED[1], WBTC], tokens: [WRAPPED_NATIVE_CURRENCY[1], WBTC],
stakingRewardAddress: '0xCA35e32e7926b96A9988f61d510E038108d8068e', stakingRewardAddress: '0xCA35e32e7926b96A9988f61d510E038108d8068e',
}, },
], ],

@ -95,6 +95,7 @@ export interface WrapTransactionInfo {
type: TransactionType.WRAP type: TransactionType.WRAP
unwrapped: boolean unwrapped: boolean
currencyAmountRaw: string currencyAmountRaw: string
chainId?: number
} }
export interface ClaimTransactionInfo { export interface ClaimTransactionInfo {

@ -15,13 +15,8 @@ export interface SerializedPair {
} }
export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode') export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>('user/updateMatchesDarkMode')
export const updateArbitrumAlphaAcknowledged = createAction<{ arbitrumAlphaAcknowledged: boolean }>(
'user/updateArbitrumAlphaAcknowledged'
)
export const updateOptimismAlphaAcknowledged = createAction<{ optimismAlphaAcknowledged: boolean }>(
'user/updateOptimismAlphaAcknowledged'
)
export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode') export const updateUserDarkMode = createAction<{ userDarkMode: boolean }>('user/updateUserDarkMode')
export const acknowledgeNetworkAlert = createAction<{ chainId: number }>('user/acknowledgeNetworkAlert')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode') export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale') export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale')
export const updateUserClientSideRouter = createAction<{ userClientSideRouter: boolean }>( export const updateUserClientSideRouter = createAction<{ userClientSideRouter: boolean }>(

@ -14,14 +14,13 @@ import { useAllTokens } from '../../hooks/Tokens'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { AppState } from '../index' import { AppState } from '../index'
import { import {
acknowledgeNetworkAlert,
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
removeSerializedToken, removeSerializedToken,
SerializedPair, SerializedPair,
SerializedToken, SerializedToken,
updateArbitrumAlphaAcknowledged,
updateHideClosedPositions, updateHideClosedPositions,
updateOptimismAlphaAcknowledged,
updateUserClientSideRouter, updateUserClientSideRouter,
updateUserDarkMode, updateUserDarkMode,
updateUserDeadline, updateUserDeadline,
@ -339,22 +338,13 @@ export function useTrackedTokenPairs(): [Token, Token][] {
}, [combinedList]) }, [combinedList])
} }
export function useArbitrumAlphaAlert(): [boolean, (arbitrumAlphaAcknowledged: boolean) => void] { export function useNetworkAlertStatus(chainId: number | undefined): [boolean, () => void] {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const arbitrumAlphaAcknowledged = useAppSelector(({ user }) => user.arbitrumAlphaAcknowledged) const alertAcknowledged = useAppSelector(({ user }) => user.networkAlertsAcknowledged)
const setArbitrumAlphaAcknowledged = (arbitrumAlphaAcknowledged: boolean) => { const acknowledgeAlert = useCallback(() => {
dispatch(updateArbitrumAlphaAcknowledged({ arbitrumAlphaAcknowledged })) if (typeof chainId !== 'number') return
} dispatch(acknowledgeNetworkAlert({ chainId }))
}, [chainId, dispatch])
return [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] return [typeof chainId === 'number' ? alertAcknowledged[chainId] ?? false : false, acknowledgeAlert]
}
export function useOptimismAlphaAlert(): [boolean, (optimismAlphaAcknowledged: boolean) => void] {
const dispatch = useAppDispatch()
const optimismAlphaAcknowledged = useAppSelector(({ user }) => user.optimismAlphaAcknowledged)
const setOptimismAlphaAcknowledged = (optimismAlphaAcknowledged: boolean) => {
dispatch(updateOptimismAlphaAcknowledged({ optimismAlphaAcknowledged }))
}
return [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged]
} }

@ -1,19 +1,19 @@
import { createReducer } from '@reduxjs/toolkit' import { createReducer } from '@reduxjs/toolkit'
import { SupportedLocale } from 'constants/locales' import { SupportedLocale } from 'constants/locales'
import { SupportedChainId } from '../../constants/chains'
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc' import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
import { updateVersion } from '../global/actions' import { updateVersion } from '../global/actions'
import { import {
acknowledgeNetworkAlert,
addSerializedPair, addSerializedPair,
addSerializedToken, addSerializedToken,
removeSerializedPair, removeSerializedPair,
removeSerializedToken, removeSerializedToken,
SerializedPair, SerializedPair,
SerializedToken, SerializedToken,
updateArbitrumAlphaAcknowledged,
updateHideClosedPositions, updateHideClosedPositions,
updateMatchesDarkMode, updateMatchesDarkMode,
updateOptimismAlphaAcknowledged,
updateUserClientSideRouter, updateUserClientSideRouter,
updateUserDarkMode, updateUserDarkMode,
updateUserDeadline, updateUserDeadline,
@ -25,13 +25,15 @@ import {
const currentTimestamp = () => new Date().getTime() const currentTimestamp = () => new Date().getTime()
export interface UserState { export interface UserState {
arbitrumAlphaAcknowledged: boolean // replaces the above two fields
networkAlertsAcknowledged: {
[chainId: number]: true
}
// the timestamp of the last updateVersion action // the timestamp of the last updateVersion action
lastUpdateVersionTimestamp?: number lastUpdateVersionTimestamp?: number
matchesDarkMode: boolean // whether the dark mode media query matches matchesDarkMode: boolean // whether the dark mode media query matches
optimismAlphaAcknowledged: boolean
userDarkMode: boolean | null // the user's choice for dark mode or light mode userDarkMode: boolean | null // the user's choice for dark mode or light mode
userLocale: SupportedLocale | null userLocale: SupportedLocale | null
@ -72,9 +74,8 @@ function pairKey(token0Address: string, token1Address: string) {
} }
export const initialState: UserState = { export const initialState: UserState = {
arbitrumAlphaAcknowledged: false,
matchesDarkMode: false, matchesDarkMode: false,
optimismAlphaAcknowledged: false, networkAlertsAcknowledged: {},
userDarkMode: null, userDarkMode: null,
userExpertMode: false, userExpertMode: false,
userLocale: null, userLocale: null,
@ -122,8 +123,20 @@ export default createReducer(initialState, (builder) =>
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
} }
state.networkAlertsAcknowledged = state.networkAlertsAcknowledged ?? {}
if ((state as unknown as { arbitrumAlphaAcknowledged: boolean }).arbitrumAlphaAcknowledged) {
state.networkAlertsAcknowledged[SupportedChainId.ARBITRUM_ONE] = true
}
if ((state as unknown as { optimismAlphaAcknowledged: boolean }).optimismAlphaAcknowledged) {
state.networkAlertsAcknowledged[SupportedChainId.OPTIMISM] = true
}
state.lastUpdateVersionTimestamp = currentTimestamp() state.lastUpdateVersionTimestamp = currentTimestamp()
}) })
.addCase(acknowledgeNetworkAlert, (state, action) => {
state.networkAlertsAcknowledged = state.networkAlertsAcknowledged ?? {}
state.networkAlertsAcknowledged[action.payload.chainId] = true
})
.addCase(updateUserDarkMode, (state, action) => { .addCase(updateUserDarkMode, (state, action) => {
state.userDarkMode = action.payload.userDarkMode state.userDarkMode = action.payload.userDarkMode
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()
@ -132,12 +145,6 @@ export default createReducer(initialState, (builder) =>
state.matchesDarkMode = action.payload.matchesDarkMode state.matchesDarkMode = action.payload.matchesDarkMode
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()
}) })
.addCase(updateArbitrumAlphaAcknowledged, (state, action) => {
state.arbitrumAlphaAcknowledged = action.payload.arbitrumAlphaAcknowledged
})
.addCase(updateOptimismAlphaAcknowledged, (state, action) => {
state.optimismAlphaAcknowledged = action.payload.optimismAlphaAcknowledged
})
.addCase(updateUserExpertMode, (state, action) => { .addCase(updateUserExpertMode, (state, action) => {
state.userExpertMode = action.payload.userExpertMode state.userExpertMode = action.payload.userExpertMode
state.timestamp = currentTimestamp() state.timestamp = currentTimestamp()

@ -1,13 +1,13 @@
import { Interface } from '@ethersproject/abi' import { Interface } from '@ethersproject/abi'
import { Currency, CurrencyAmount, Ether, Token } from '@uniswap/sdk-core' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'
import ERC20ABI from 'abis/erc20.json' import ERC20ABI from 'abis/erc20.json'
import { Erc20Interface } from 'abis/types/Erc20' import { Erc20Interface } from 'abis/types/Erc20'
import JSBI from 'jsbi' import JSBI from 'jsbi'
import { useMemo } from 'react' import { useMemo } from 'react'
import { UNI } from '../../constants/tokens' import { nativeOnChain, UNI } from '../../constants/tokens'
import { useAllTokens } from '../../hooks/Tokens' import { useAllTokens } from '../../hooks/Tokens'
import { useMulticall2Contract } from '../../hooks/useContract' import { useInterfaceMulticall } from '../../hooks/useContract'
import { useActiveWeb3React } from '../../hooks/web3' import { useActiveWeb3React } from '../../hooks/web3'
import { isAddress } from '../../utils' import { isAddress } from '../../utils'
import { useUserUnclaimedAmount } from '../claim/hooks' import { useUserUnclaimedAmount } from '../claim/hooks'
@ -16,38 +16,35 @@ import { useTotalUniEarned } from '../stake/hooks'
/** /**
* Returns a map of the given addresses to their eventually consistent ETH balances. * Returns a map of the given addresses to their eventually consistent ETH balances.
*/ */
export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): { export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefined)[]): {
[address: string]: CurrencyAmount<Currency> | undefined [address: string]: CurrencyAmount<Currency> | undefined
} { } {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const multicallContract = useMulticall2Contract() const multicallContract = useInterfaceMulticall()
const addresses: string[] = useMemo( const validAddressInputs: [string][] = useMemo(
() => () =>
uncheckedAddresses uncheckedAddresses
? uncheckedAddresses ? uncheckedAddresses
.map(isAddress) .map(isAddress)
.filter((a): a is string => a !== false) .filter((a): a is string => a !== false)
.sort() .sort()
.map((addr) => [addr])
: [], : [],
[uncheckedAddresses] [uncheckedAddresses]
) )
const results = useSingleContractMultipleData( const results = useSingleContractMultipleData(multicallContract, 'getEthBalance', validAddressInputs)
multicallContract,
'getEthBalance',
addresses.map((address) => [address])
)
return useMemo( return useMemo(
() => () =>
addresses.reduce<{ [address: string]: CurrencyAmount<Currency> }>((memo, address, i) => { validAddressInputs.reduce<{ [address: string]: CurrencyAmount<Currency> }>((memo, [address], i) => {
const value = results?.[i]?.result?.[0] const value = results?.[i]?.result?.[0]
if (value && chainId) if (value && chainId)
memo[address] = CurrencyAmount.fromRawAmount(Ether.onChain(chainId), JSBI.BigInt(value.toString())) memo[address] = CurrencyAmount.fromRawAmount(nativeOnChain(chainId), JSBI.BigInt(value.toString()))
return memo return memo
}, {}), }, {}),
[addresses, chainId, results] [validAddressInputs, chainId, results]
) )
} }
@ -120,7 +117,7 @@ export function useCurrencyBalances(
const tokenBalances = useTokenBalances(account, tokens) const tokenBalances = useTokenBalances(account, tokens)
const containsETH: boolean = useMemo(() => currencies?.some((currency) => currency?.isNative) ?? false, [currencies]) const containsETH: boolean = useMemo(() => currencies?.some((currency) => currency?.isNative) ?? false, [currencies])
const ethBalance = useETHBalances(containsETH ? [account] : []) const ethBalance = useNativeCurrencyBalances(containsETH ? [account] : [])
return useMemo( return useMemo(
() => () =>

@ -8,11 +8,13 @@ const initialStyles = {
width: '200vw', width: '200vw',
height: '200vh', height: '200vh',
transform: 'translate(-50vw, -100vh)', transform: 'translate(-50vw, -100vh)',
backgroundBlendMode: '',
} }
const backgroundResetStyles = { const backgroundResetStyles = {
width: '100vw', width: '100vw',
height: '100vh', height: '100vh',
transform: 'unset', transform: 'unset',
backgroundBlendMode: '',
} }
type TargetBackgroundStyles = typeof initialStyles | typeof backgroundResetStyles type TargetBackgroundStyles = typeof initialStyles | typeof backgroundResetStyles
@ -48,6 +50,16 @@ export default function RadialGradientByChainUpdater(): null {
const optimismDarkGradient = 'radial-gradient(150% 100% at 50% 0%, #3E2E38 2%, #2C1F2D 53%, #1F2128 100%)' const optimismDarkGradient = 'radial-gradient(150% 100% at 50% 0%, #3E2E38 2%, #2C1F2D 53%, #1F2128 100%)'
backgroundRadialGradientElement.style.background = darkMode ? optimismDarkGradient : optimismLightGradient backgroundRadialGradientElement.style.background = darkMode ? optimismDarkGradient : optimismLightGradient
break break
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
setBackground(backgroundResetStyles)
const polygonLightGradient =
'radial-gradient(153.32% 100% at 47.26% 0%, rgba(130, 71, 229, 0.0864) 0%, rgba(0, 41, 255, 0.06) 48.19%, rgba(0, 41, 255, 0.012) 100%), #FFFFFF'
const polygonDarkGradient =
'radial-gradient(150.6% 98.22% at 48.06% 0%, rgba(130, 71, 229, 0.6) 0%, rgba(200, 168, 255, 0) 100%), #1F2128'
backgroundRadialGradientElement.style.background = darkMode ? polygonDarkGradient : polygonLightGradient
backgroundRadialGradientElement.style.backgroundBlendMode = darkMode ? 'overlay,normal' : 'multiply,normal'
break
default: default:
setBackground(initialStyles) setBackground(initialStyles)
backgroundRadialGradientElement.style.background = '' backgroundRadialGradientElement.style.background = ''

@ -1,35 +0,0 @@
import { BigNumber } from '@ethersproject/bignumber'
import { hexStripZeros } from '@ethersproject/bytes'
import { Web3Provider } from '@ethersproject/providers'
import { L1ChainInfo, L2ChainInfo, SupportedChainId } from 'constants/chains'
interface AddNetworkArguments {
library: Web3Provider
chainId: SupportedChainId
info: L1ChainInfo | L2ChainInfo
}
// provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw
// see https://github.com/rekmarks/EIPs/blob/3326-create/EIPS/eip-3326.md for more info on wallet_switchEthereumChain
export async function addNetwork({ library, chainId, info }: AddNetworkArguments): Promise<null | void> {
if (!library?.provider?.request) {
return
}
const formattedChainId = hexStripZeros(BigNumber.from(chainId).toHexString())
try {
await library?.provider.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: formattedChainId,
chainName: info.label,
rpcUrls: info.rpcUrls,
nativeCurrency: info.nativeCurrency,
blockExplorerUrls: [info.explorer],
},
],
})
} catch (error) {
console.error('error adding eth network: ', chainId, info, error)
}
}

9
src/utils/chains.tsx Normal file

@ -0,0 +1,9 @@
import { CHAIN_INFO, NetworkType, SupportedL1ChainId, SupportedL2ChainId } from '../constants/chains'
export function isL1ChainId(chainId: number | undefined): chainId is SupportedL1ChainId {
return typeof chainId === 'number' && CHAIN_INFO[chainId].networkType === NetworkType.L1
}
export function isL2ChainId(chainId: number | undefined): chainId is SupportedL2ChainId {
return typeof chainId === 'number' && CHAIN_INFO[chainId].networkType === NetworkType.L2
}

@ -1,13 +1,19 @@
import { L1_CHAIN_IDS, SupportedChainId } from '../constants/chains' import { SupportedChainId } from '../constants/chains'
const DEFAULT_NETWORKS = [
SupportedChainId.MAINNET,
SupportedChainId.ROPSTEN,
SupportedChainId.RINKEBY,
SupportedChainId.GOERLI,
SupportedChainId.KOVAN,
]
export function constructSameAddressMap<T extends string>( export function constructSameAddressMap<T extends string>(
address: T, address: T,
additionalNetworks: SupportedChainId[] = [] additionalNetworks: SupportedChainId[] = []
): { [chainId: number]: T } { ): { [chainId: number]: T } {
return (L1_CHAIN_IDS as readonly SupportedChainId[]) return DEFAULT_NETWORKS.concat(additionalNetworks).reduce<{ [chainId: number]: T }>((memo, chainId) => {
.concat(additionalNetworks) memo[chainId] = address
.reduce<{ [chainId: number]: T }>((memo, chainId) => { return memo
memo[chainId] = address }, {})
return memo
}, {})
} }

@ -13,6 +13,12 @@ describe('#getExplorerLink', () => {
it('unrecognized chain id defaults to mainnet', () => { it('unrecognized chain id defaults to mainnet', () => {
expect(getExplorerLink(2, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://etherscan.io/address/abc') expect(getExplorerLink(2, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://etherscan.io/address/abc')
}) })
it('arbitrum', () => {
expect(getExplorerLink(42161, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://arbiscan.io/address/abc')
})
it('polygon', () => {
expect(getExplorerLink(137, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://polygonscan.com/address/abc')
})
it('ropsten', () => { it('ropsten', () => {
expect(getExplorerLink(3, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://ropsten.etherscan.io/address/abc') expect(getExplorerLink(3, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://ropsten.etherscan.io/address/abc')
}) })

@ -1,13 +1,15 @@
import { SupportedChainId } from '../constants/chains' import { SupportedChainId } from '../constants/chains'
const ETHERSCAN_PREFIXES: { [chainId: number]: string } = { const ETHERSCAN_PREFIXES: { [chainId: number]: string } = {
[SupportedChainId.MAINNET]: '', [SupportedChainId.MAINNET]: 'https://etherscan.io',
[SupportedChainId.ROPSTEN]: 'ropsten.', [SupportedChainId.ROPSTEN]: 'https://ropsten.etherscan.io',
[SupportedChainId.RINKEBY]: 'rinkeby.', [SupportedChainId.RINKEBY]: 'https://rinkeby.etherscan.io',
[SupportedChainId.GOERLI]: 'goerli.', [SupportedChainId.GOERLI]: 'https://goerli.etherscan.io',
[SupportedChainId.KOVAN]: 'kovan.', [SupportedChainId.KOVAN]: 'https://kovan.etherscan.io',
[SupportedChainId.OPTIMISM]: 'optimistic.', [SupportedChainId.OPTIMISM]: 'https://optimistic.etherscan.io',
[SupportedChainId.OPTIMISTIC_KOVAN]: 'kovan-optimistic.', [SupportedChainId.OPTIMISTIC_KOVAN]: 'https://kovan-optimistic.etherscan.io',
[SupportedChainId.POLYGON_MUMBAI]: 'https://mumbai.polygonscan.com',
[SupportedChainId.POLYGON]: 'https://polygonscan.com',
} }
export enum ExplorerDataType { export enum ExplorerDataType {
@ -52,7 +54,7 @@ export function getExplorerLink(chainId: number, data: string, type: ExplorerDat
} }
} }
const prefix = `https://${ETHERSCAN_PREFIXES[chainId] ?? ''}etherscan.io` const prefix = ETHERSCAN_PREFIXES[chainId] ?? 'https://etherscan.io'
switch (type) { switch (type) {
case ExplorerDataType.TRANSACTION: case ExplorerDataType.TRANSACTION:

@ -3,11 +3,9 @@ import { hexStripZeros } from '@ethersproject/bytes'
import { Web3Provider } from '@ethersproject/providers' import { Web3Provider } from '@ethersproject/providers'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains' import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { addNetwork } from './addNetwork'
interface SwitchNetworkArguments { interface SwitchNetworkArguments {
library: Web3Provider library: Web3Provider
chainId?: SupportedChainId chainId: SupportedChainId
} }
// provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw // provider.request returns Promise<any>, but wallet_switchEthereumChain must return null or throw
@ -16,25 +14,39 @@ export async function switchToNetwork({ library, chainId }: SwitchNetworkArgumen
if (!library?.provider?.request) { if (!library?.provider?.request) {
return return
} }
if (!chainId && library?.getNetwork) {
;({ chainId } = await library.getNetwork())
}
const formattedChainId = hexStripZeros(BigNumber.from(chainId).toHexString()) const formattedChainId = hexStripZeros(BigNumber.from(chainId).toHexString())
try { try {
await library?.provider.request({ await library.provider.request({
method: 'wallet_switchEthereumChain', method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }], params: [{ chainId: formattedChainId }],
}) })
} catch (error) { } catch (error) {
// 4902 is the error code for attempting to switch to an unrecognized chainId // 4902 is the error code for attempting to switch to an unrecognized chainId
if (error.code === 4902 && chainId !== undefined) { if (error.code === 4902) {
const info = CHAIN_INFO[chainId] const info = CHAIN_INFO[chainId]
// metamask (only known implementer) automatically switches after a network is added await library.provider.request({
// the second call is done here because that behavior is not a part of the spec and cannot be relied upon in the future method: 'wallet_addEthereumChain',
// metamask's behavior when switching to the current network is just to return null (a no-op) params: [
await addNetwork({ library, chainId, info }) {
await switchToNetwork({ library, chainId }) chainId: formattedChainId,
chainName: info.label,
rpcUrls: [info.addNetworkInfo.rpcUrl],
nativeCurrency: info.addNetworkInfo.nativeCurrency,
blockExplorerUrls: [info.explorer],
},
],
})
const { chainId: chainIdAfterSwitch } = await library.getNetwork()
if (chainIdAfterSwitch !== chainId) {
// metamask (only known implementer) automatically switches after a network is added
// the second call is done here because that behavior is not a part of the spec and cannot be relied upon in the future
// metamask's behavior when switching to the current network is just to return null (a no-op)
await library.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
}
} else { } else {
throw error throw error
} }

@ -1,12 +1,12 @@
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import { ExtendedEther, WETH9_EXTENDED } from '../constants/tokens' import { nativeOnChain, WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { supportedChainId } from './supportedChainId' import { supportedChainId } from './supportedChainId'
export function unwrappedToken(currency: Currency): Currency { export function unwrappedToken(currency: Currency): Currency {
if (currency.isNative) return currency if (currency.isNative) return currency
const formattedChainId = supportedChainId(currency.chainId) const formattedChainId = supportedChainId(currency.chainId)
if (formattedChainId && currency.equals(WETH9_EXTENDED[formattedChainId])) if (formattedChainId && currency.equals(WRAPPED_NATIVE_CURRENCY[formattedChainId]))
return ExtendedEther.onChain(currency.chainId) return nativeOnChain(currency.chainId)
return currency return currency
} }