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 JSBI from 'jsbi'
import { nativeOnChain } from '../../constants/tokens'
import { useCurrency, useToken } from '../../hooks/Tokens'
import useENSName from '../../hooks/useENSName'
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>
}
function WrapSummary({ info: { currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) {
function WrapSummary({ info: { chainId, currencyAmountRaw, unwrapped } }: { info: WrapTransactionInfo }) {
const native = chainId ? nativeOnChain(chainId) : undefined
if (unwrapped) {
return (
<Trans>
Unwrap <FormattedCurrencyAmount rawAmount={currencyAmountRaw} symbol={'WETH'} decimals={18} sigFigs={6} /> to
ETH
Unwrap{' '}
<FormattedCurrencyAmount
rawAmount={currencyAmountRaw}
symbol={native?.wrapped?.symbol ?? 'WETH'}
decimals={18}
sigFigs={6}
/>{' '}
to {native?.symbol ?? 'ETH'}
</Trans>
)
} else {
return (
<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>
)
}

@ -1,11 +1,12 @@
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 useHttpLocations from 'hooks/useHttpLocations'
import React, { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
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'
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};
height: ${({ size }) => size};
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
@ -87,7 +88,17 @@ export default function CurrencyLogo({
}, [currency, uriLocations])
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} />

@ -1,10 +1,12 @@
import { Trans } from '@lingui/macro'
import { L2_CHAIN_IDS, SupportedChainId } from 'constants/chains'
import { SupportedChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { AlertOctagon } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink } from 'theme'
import { isL2ChainId } from '../../utils/chains'
const Root = styled.div`
background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')};
border-radius: 18px;
@ -18,7 +20,6 @@ const Root = styled.div`
max-width: 880px;
`
const WarningIcon = styled(AlertOctagon)`
display: block;
margin: auto 16px auto 0;
min-height: 22px;
min-width: 22px;
@ -28,50 +29,54 @@ const ReadMoreLink = styled(ExternalLink)`
text-decoration: underline;
`
export default function DowntimeWarning() {
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
}
}
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<Root>
<WarningIcon />
<Content />
<div>{children}</div>
</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 {
ARBITRUM_HELP_CENTER_LINK,
CHAIN_INFO,
L2_CHAIN_IDS,
OPTIMISM_HELP_CENTER_LINK,
SupportedChainId,
SupportedL2ChainId,
} from 'constants/chains'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useRef } from 'react'
import { ArrowDownCircle, ChevronDown } from 'react-feather'
import { useModalOpen, useToggleModal } from 'state/application/hooks'
import { ApplicationModal } from 'state/application/reducer'
import { useAppSelector } from 'state/hooks'
import { addPopup, ApplicationModal } from 'state/application/reducer'
import styled from 'styled-components/macro'
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`
display: flex;
@ -136,7 +130,7 @@ const SelectorWrapper = styled.div`
const StyledChevronDown = styled(ChevronDown)`
width: 12px;
`
const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
const BridgeLabel = ({ chainId }: { chainId: SupportedChainId }) => {
switch (chainId) {
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
@ -144,11 +138,14 @@ const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
return <Trans>Optimism Gateway</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygon Bridge</Trans>
default:
return <Trans>Bridge</Trans>
}
}
const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
const ExplorerLabel = ({ chainId }: { chainId: SupportedChainId }) => {
switch (chainId) {
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
@ -156,91 +153,108 @@ const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
return <Trans>Optimistic Etherscan</Trans>
case SupportedChainId.POLYGON:
case SupportedChainId.POLYGON_MUMBAI:
return <Trans>Polygonscan</Trans>
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() {
const { chainId, library } = useActiveWeb3React()
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.NETWORK_SELECTOR)
const toggle = useToggleModal(ApplicationModal.NETWORK_SELECTOR)
useOnClickOutside(node, open ? toggle : undefined)
const implements3085 = useAppSelector((state) => state.application.implements3085)
const info = chainId ? CHAIN_INFO[chainId] : undefined
const isOnL2 = chainId ? L2_CHAIN_IDS.includes(chainId) : false
const showSelector = Boolean(implements3085 || isOnL2)
const mainnetInfo = CHAIN_INFO[SupportedChainId.MAINNET]
const dispatch = useAppDispatch()
const conditionalToggle = useCallback(() => {
if (showSelector) {
toggle()
}
}, [showSelector, toggle])
const handleRowClick = useCallback(
(targetChain: number) => {
if (!library) return
switchToNetwork({ library, chainId: targetChain })
.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) {
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 (
<SelectorWrapper ref={node as any}>
<SelectorControls onClick={conditionalToggle} interactive={showSelector}>
<SelectorLogo interactive={showSelector} src={info.logoUrl || mainnetInfo.logoUrl} />
<SelectorControls onClick={toggle} interactive>
<SelectorLogo interactive src={info.logoUrl} />
<SelectorLabel>{info.label}</SelectorLabel>
{showSelector && <StyledChevronDown />}
<StyledChevronDown />
</SelectorControls>
{open && (
<FlyoutMenu>
<FlyoutHeader>
<Trans>Select a network</Trans>
</FlyoutHeader>
<Row targetChain={SupportedChainId.MAINNET} />
<Row targetChain={SupportedChainId.OPTIMISM} />
<Row targetChain={SupportedChainId.ARBITRUM_ONE} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.MAINNET} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.POLYGON} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.OPTIMISM} />
<Row onSelectChain={handleRowClick} targetChain={SupportedChainId.ARBITRUM_ONE} />
</FlyoutMenu>
)}
</SelectorWrapper>

@ -147,8 +147,8 @@ export default function Polling() {
<MouseoverTooltip
text={
<Trans>
{`The current fast gas amount for sending a transaction on L1.
Gas fees are paid in Ethereum's native currency Ether (ETH) and denominated in gwei. `}
The current fast gas amount for sending a transaction on L1. Gas fees are paid in
Ethereum&apos;s native currency Ether (ETH) and denominated in GWEI.
</Trans>
}
>
@ -166,7 +166,7 @@ export default function Polling() {
}
>
<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;
</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 useTheme from 'hooks/useTheme'
import { darken } from 'polished'
import { useState } from 'react'
import { NavLink } from 'react-router-dom'
import { Text } from 'rebass'
import { useShowClaimPopup, useToggleSelfClaimModal } from 'state/application/hooks'
import { useUserHasAvailableClaim } from 'state/claim/hooks'
import { useUserHasSubmittedClaim } from 'state/transactions/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 { ReactComponent as Logo } from '../../assets/svg/logo.svg'
@ -19,12 +18,10 @@ import { ExternalLink, ThemedText } from '../../theme'
import ClaimModal from '../claim/ClaimModal'
import { CardNoise } from '../earn/styled'
import Menu from '../Menu'
import Modal from '../Modal'
import Row from '../Row'
import { Dots } from '../swap/styleds'
import Web3Status from '../Web3Status'
import NetworkSelector from './NetworkSelector'
import UniBalanceContent from './UniBalanceContent'
const HeaderFrame = styled.div<{ showBackground: boolean }>`
display: grid;
@ -246,7 +243,7 @@ const StyledExternalLink = styled(ExternalLink).attrs({
export default function Header() {
const { account, chainId } = useActiveWeb3React()
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
const userEthBalance = useNativeCurrencyBalances(account ? [account] : [])?.[account ?? '']
const [darkMode] = useDarkModeManager()
const { white, black } = useTheme()
@ -256,18 +253,20 @@ export default function Header() {
const { claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const [showUniBalanceModal, setShowUniBalanceModal] = useState(false)
const showClaimPopup = useShowClaimPopup()
const scrollY = useScrollPosition()
const { infoLink } = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
const {
infoLink,
addNetworkInfo: {
nativeCurrency: { symbol: nativeCurrencySymbol },
},
} = CHAIN_INFO[chainId ? chainId : SupportedChainId.MAINNET]
return (
<HeaderFrame showBackground={scrollY > 45}>
<ClaimModal />
<Modal isOpen={showUniBalanceModal} onDismiss={() => setShowUniBalanceModal(false)}>
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
</Modal>
<Title href=".">
<UniIcon>
<Logo fill={darkMode ? white : black} width="24px" height="100%" title="logo" />
@ -325,7 +324,9 @@ export default function Header() {
<AccountElement active={!!account}>
{account && userEthBalance ? (
<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>
) : null}
<Web3Status />

@ -1,32 +1,22 @@
import { Trans } from '@lingui/macro'
import {
ARBITRUM_HELP_CENTER_LINK,
L2_CHAIN_IDS,
OPTIMISM_HELP_CENTER_LINK,
SupportedChainId,
SupportedL2ChainId,
} from 'constants/chains'
import { SupportedChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { ArrowDownCircle, X } from 'react-feather'
import { useArbitrumAlphaAlert, useDarkModeManager, useOptimismAlphaAlert } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks'
import styled, { css } from 'styled-components/macro'
import { useDarkModeManager, useNetworkAlertStatus } from 'state/user/hooks'
import { useNativeCurrencyBalances } from 'state/wallet/hooks'
import styled from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { CHAIN_INFO } from '../../constants/chains'
export const DesktopTextBreak = styled.div`
display: none;
@media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
display: block;
}
`
import { ThemedText } from '../../theme'
import { AutoRow } from '../Row'
const L2Icon = styled.img`
width: 36px;
height: 36px;
justify-self: center;
margin-right: 14px;
`
const BetaTag = styled.span<{ color: string }>`
align-items: center;
@ -44,24 +34,11 @@ const BetaTag = styled.span<{ color: string }>`
width: 60px;
z-index: 1;
`
const Body = styled.p`
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 }>`
export const Controls = styled.div`
align-items: center;
display: flex;
justify-content: flex-start;
${({ thin }) =>
thin &&
css`
margin: auto 32px auto 0;
`}
padding: 0 20px 20px 20px;
`
const CloseIcon = styled(X)`
cursor: pointer;
@ -71,17 +48,13 @@ const CloseIcon = styled(X)`
`
const BodyText = styled.div`
align-items: center;
display: grid;
grid-gap: 4px;
grid-template-columns: 40px 4fr;
grid-template-rows: auto auto;
margin: 20px 16px;
@media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
grid-template-columns: 42px 4fr;
grid-gap: 8px;
}
`
const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
const LearnMoreLink = styled(ExternalLink)`
align-items: center;
background-color: transparent;
border: 1px solid rgba(255, 255, 255, 0.4);
@ -91,7 +64,6 @@ const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
font-size: 16px;
height: 44px;
justify-content: space-between;
margin: 0 0 20px 0;
padding: 12px 16px;
text-decoration: none;
width: auto;
@ -101,57 +73,51 @@ const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
background-color: rgba(255, 255, 255, 0.05);
}
transition: background-color 150ms ease-in-out;
${({ thin }) =>
thin &&
css`
font-size: 14px;
margin: auto;
width: 112px;
`}
`
const RootWrapper = styled.div`
position: relative;
`
export const ArbitrumWrapperBackgroundDarkMode = css`
background: 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);
`
export const ArbitrumWrapperBackgroundLightMode = css`
background: 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);
`
export const OptimismWrapperBackgroundDarkMode = css`
background: 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`
background: 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);
`
const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string; thin?: boolean }>`
${({ chainId, darkMode }) =>
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? darkMode
? OptimismWrapperBackgroundDarkMode
: OptimismWrapperBackgroundLightMode
: darkMode
? ArbitrumWrapperBackgroundDarkMode
: ArbitrumWrapperBackgroundLightMode};
const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
[darkMode in 'dark' | 'light']: { [chainId in SupportedChainId]?: string }
} = {
dark: {
[SupportedChainId.POLYGON]:
'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.3) 0%, rgba(82, 32, 166, 0.3) 100%)',
[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%)',
[SupportedChainId.OPTIMISM]:
'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%)',
[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%)',
[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(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)',
[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)',
},
light: {
[SupportedChainId.POLYGON]:
'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%)',
[SupportedChainId.POLYGON_MUMBAI]:
'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%)',
[SupportedChainId.OPTIMISM]:
'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.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;
display: flex;
flex-direction: column;
max-width: 480px;
min-height: 174px;
overflow: hidden;
position: relative;
width: 100%;
${({ thin }) =>
thin &&
css`
flex-direction: row;
max-width: max-content;
min-height: min-content;
`}
:before {
background-image: url(${({ logoUrl }) => logoUrl});
background-repeat: no-repeat;
@ -165,12 +131,11 @@ const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean
z-index: -1;
}
`
const Header = styled.h2<{ thin?: boolean }>`
const Header = styled.h2`
font-weight: 600;
font-size: 20px;
margin: 0;
padding-right: 30px;
display: ${({ thin }) => (thin ? 'none' : 'block')};
`
const LinkOutCircle = styled(ArrowDownCircle)`
margin-left: 12px;
@ -178,7 +143,7 @@ const LinkOutCircle = styled(ArrowDownCircle)`
width: 20px;
height: 20px;
`
const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
const LinkOutToBridge = styled(ExternalLink)`
align-items: center;
background-color: black;
border-radius: 8px;
@ -187,8 +152,8 @@ const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
font-size: 16px;
height: 44px;
justify-content: space-between;
margin: 0 12px 20px 18px;
padding: 12px 16px;
margin-right: 20px;
text-decoration: none;
width: auto;
:hover,
@ -196,85 +161,154 @@ const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
:active {
background-color: black;
}
${({ thin }) =>
thin &&
css`
font-size: 14px;
margin: auto 10px;
width: 168px;
`}
`
interface NetworkAlertProps {
thin?: boolean
const DisclaimerText = styled(ThemedText.Body)`
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 [darkMode] = useDarkModeManager()
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
const [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged] = useOptimismAlphaAlert()
const [alertAcknowledged, acknowledgeAlert] = useNetworkAlertStatus(chainId)
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(() => {
if (userEthBalance?.greaterThan(0)) {
switch (chainId) {
case SupportedChainId.OPTIMISM:
setOptimismAlphaAcknowledged(true)
break
case SupportedChainId.ARBITRUM_ONE:
setArbitrumAlphaAcknowledged(true)
break
}
} else {
setLocallyDimissed(true)
}
}, [chainId, setArbitrumAlphaAcknowledged, setOptimismAlphaAcknowledged, userEthBalance])
setLocallyDimissed(true)
if (!alertAcknowledged) acknowledgeAlert()
}, [acknowledgeAlert, alertAcknowledged])
const onOptimismAndOptimismAcknowledged = SupportedChainId.OPTIMISM === chainId && optimismAlphaAcknowledged
const onArbitrumAndArbitrumAcknowledged = SupportedChainId.ARBITRUM_ONE === chainId && arbitrumAlphaAcknowledged
if (
!chainId ||
!L2_CHAIN_IDS.includes(chainId) ||
onArbitrumAndArbitrumAcknowledged ||
onOptimismAndOptimismAcknowledged ||
locallyDismissed
) {
if (!shouldShowAlert(chainId) || alertAcknowledged || locallyDismissed) {
return null
}
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
const showCloseIcon = Boolean(userEthBalance?.greaterThan(0) && !props.thin)
const { label, logoUrl, bridge, helpCenterUrl } = CHAIN_INFO[chainId]
const showCloseIcon = Boolean(userNativeCurrencyBalance?.greaterThan(0))
const betaColor = BETA_TAG_COLORS[chainId]
return (
<RootWrapper>
<BetaTag color={isOptimism ? '#ff0420' : '#0490ed'}>Beta</BetaTag>
<ContentWrapper chainId={chainId} darkMode={darkMode} logoUrl={info.logoUrl} thin={props.thin}>
{betaColor ? <BetaTag color={betaColor}>Beta</BetaTag> : null}
<ContentWrapper chainId={chainId} darkMode={darkMode} logoUrl={logoUrl}>
{showCloseIcon && <CloseIcon onClick={dismiss} />}
<BodyText>
<L2Icon src={info.logoUrl} />
<Header thin={props.thin}>
<Trans>Uniswap on {info.label}</Trans>
</Header>
<Body>
<Trans>
To start trading on {info.label}, first bridge your assets from L1 to L2. Please treat this as a beta
release and learn about the risks before using {info.label}.
</Trans>
</Body>
<AutoRow style={{ marginBottom: '1em' }}>
<L2Icon src={logoUrl} />
<Header>
<Trans>Uniswap on {label}</Trans>
</Header>
</AutoRow>
<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>
</BodyText>
<Controls thin={props.thin}>
<LinkOutToBridge href={depositUrl} thin={props.thin}>
<Trans>Deposit Assets</Trans>
<LinkOutCircle />
</LinkOutToBridge>
<LearnMoreLink href={helpCenterLink} thin={props.thin}>
<Trans>Learn More</Trans>
</LearnMoreLink>
<Controls>
{bridge ? (
<LinkOutToBridge href={bridge}>
<Trans>Deposit Assets</Trans>
<LinkOutCircle />
</LinkOutToBridge>
) : null}
{helpCenterUrl ? (
<LearnMoreLink href={helpCenterUrl}>
<Trans>Learn More</Trans>
</LearnMoreLink>
) : null}
</Controls>
</ContentWrapper>
</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 { PopupContent } from '../../state/application/reducer'
import FailedNetworkSwitchPopup from './FailedNetworkSwitchPopup'
import TransactionPopup from './TransactionPopup'
const StyledClose = styled(X)`
@ -77,6 +78,8 @@ export default function PopupItem({
txn: { hash },
} = content
popupContent = <TransactionPopup hash={hash} />
} else if ('failedSwitchNetwork' in content) {
popupContent = <FailedNetworkSwitchPopup chainId={content.failedSwitchNetwork} />
}
const faderStyle = useSpring({

@ -19,7 +19,7 @@ import { PositionDetails } from 'types/position'
import { formatTickPrice } from 'utils/formatTickPrice'
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)`
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
const bases = [...Object.values(WETH9_EXTENDED), WBTC]
const bases = [...Object.values(WRAPPED_NATIVE_CURRENCY), WBTC]
if (bases.some((base) => base.equals(token1))) {
return {
priceLower: position.token0PriceUpper.invert(),

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

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

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

@ -36,11 +36,14 @@ class MiniRpcProvider implements AsyncSendable {
public readonly path: string
public readonly batchWaitTimeMs: number
private readonly connector: NetworkConnector
private nextId = 1
private batchTimeoutId: ReturnType<typeof setTimeout> | null = null
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.url = url
const parsed = new URL(url)
@ -52,7 +55,21 @@ class MiniRpcProvider implements AsyncSendable {
public readonly clearBatch = async () => {
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.batchTimeoutId = null
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')
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) => {
accumulator[Number(chainId)] = new MiniRpcProvider(Number(chainId), urls[Number(chainId)])
accumulator[Number(chainId)] = new MiniRpcProvider(this, Number(chainId), urls[Number(chainId)])
return accumulator
}, {})
}
@ -178,4 +195,21 @@ export class NetworkConnector extends AbstractConnector {
public deactivate() {
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 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 { FortmaticConnector } from './Fortmatic'
import { NetworkConnector } from './NetworkConnector'
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
const FORMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY
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({
urls: NETWORK_URLS,
urls: INFURA_NETWORK_URLS,
defaultChainId: 1,
})
@ -49,7 +32,7 @@ export const gnosisSafe = new SafeAppConnector()
export const walletconnect = new WalletConnectConnector({
supportedChainIds: ALL_SUPPORTED_CHAIN_IDS,
rpc: NETWORK_URLS,
rpc: INFURA_NETWORK_URLS,
qrcode: true,
})
@ -67,7 +50,7 @@ export const portis = new PortisConnector({
// mainnet only
export const walletlink = new WalletLinkConnector({
url: NETWORK_URLS[SupportedChainId.MAINNET],
url: INFURA_NETWORK_URLS[SupportedChainId.MAINNET],
appName: 'Uniswap',
appLogoUrl: UNISWAP_LOGO_URL,
supportedChainIds: [SupportedChainId.MAINNET],

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

@ -1,10 +1,14 @@
import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg'
import ms from 'ms.macro'
import { ARBITRUM_LIST, OPTIMISM_LIST } from './lists'
/**
* List of all the networks supported by the Uniswap Interface
*/
export enum SupportedChainId {
MAINNET = 1,
ROPSTEN = 3,
@ -14,33 +18,45 @@ export enum SupportedChainId {
ARBITRUM_ONE = 42161,
ARBITRUM_RINKEBY = 421611,
OPTIMISM = 10,
OPTIMISTIC_KOVAN = 69,
POLYGON = 137,
POLYGON_MUMBAI = 80001,
}
export const ALL_SUPPORTED_CHAIN_IDS: SupportedChainId[] = [
SupportedChainId.MAINNET,
SupportedChainId.ROPSTEN,
SupportedChainId.RINKEBY,
SupportedChainId.GOERLI,
SupportedChainId.KOVAN,
const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
]
/**
* Array of all the supported chain IDs
*/
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 = [
SupportedChainId.MAINNET,
SupportedChainId.ROPSTEN,
SupportedChainId.RINKEBY,
SupportedChainId.GOERLI,
SupportedChainId.KOVAN,
SupportedChainId.POLYGON,
SupportedChainId.POLYGON_MUMBAI,
] as const
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 = [
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
@ -50,106 +66,149 @@ export const L2_CHAIN_IDS = [
export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number]
export interface L1ChainInfo {
readonly blockWaitMsBeforeWarning?: number
readonly docs: string
readonly explorer: string
readonly infoLink: string
readonly label: string
readonly logoUrl?: string
readonly rpcUrls?: string[]
/**
* These are the network URLs used by the interface when there is not another available source of chain data
*/
export const INFURA_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}`,
[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: {
name: string // 'Goerli ETH',
symbol: string // 'gorETH',
decimals: number //18,
name: string // e.g. 'Goerli ETH',
symbol: string // e.g. 'gorETH',
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 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 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 SupportedL1ChainId]: L1ChainInfo }
export const CHAIN_INFO: ChainInfo = {
[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'],
},
export const CHAIN_INFO: ChainInfoMap = {
[SupportedChainId.MAINNET]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/',
explorer: 'https://etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
label: 'Ethereum',
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]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/',
explorer: 'https://rinkeby.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
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]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/',
explorer: 'https://ropsten.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
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]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/',
explorer: 'https://kovan.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
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]: {
networkType: NetworkType.L1,
docs: 'https://docs.uniswap.org/',
explorer: 'https://goerli.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
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]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`25m`,
bridge: 'https://gateway.optimism.io/',
bridge: 'https://gateway.optimism.io/?chainId=1',
defaultListUrl: OPTIMISM_LIST,
docs: 'https://optimism.io/',
explorer: 'https://optimistic.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/optimism/',
label: 'OΞ',
label: 'Optimism',
logoUrl: optimismLogoUrl,
nativeCurrency: { name: 'Optimistic ETH', symbol: 'ETH', decimals: 18 },
rpcUrls: ['https://mainnet.optimism.io'],
statusPage: 'https://optimism.io/status',
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]: {
networkType: NetworkType.L2,
blockWaitMsBeforeWarning: ms`25m`,
bridge: 'https://gateway.optimism.io/',
defaultListUrl: OPTIMISM_LIST,
@ -157,12 +216,72 @@ export const CHAIN_INFO: ChainInfo = {
explorer: 'https://optimistic.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/optimism/',
label: 'Optimistic Kovan',
rpcUrls: ['https://kovan.optimism.io'],
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_ARBITRUM_ONE,
DAI_OPTIMISM,
DAI_POLYGON,
ETH2X_FLI,
ExtendedEther,
FEI,
FRAX,
FXS,
nativeOnChain,
renBTC,
rETH2,
sETH2,
@ -20,13 +21,18 @@ import {
USDC,
USDC_ARBITRUM,
USDC_OPTIMISM,
USDC_POLYGON,
USDT,
USDT_ARBITRUM_ONE,
USDT_OPTIMISM,
USDT_POLYGON,
WBTC,
WBTC_ARBITRUM_ONE,
WBTC_OPTIMISM,
WETH9_EXTENDED,
WBTC_POLYGON,
WETH_POLYGON,
WETH_POLYGON_MUMBAI,
WRAPPED_NATIVE_CURRENCY,
} from './tokens'
type ChainTokenList = {
@ -37,21 +43,33 @@ type ChainCurrencyList = {
readonly [chainId: number]: Currency[]
}
const WETH_ONLY: ChainTokenList = Object.fromEntries(
Object.entries(WETH9_EXTENDED).map(([key, value]) => [key, [value]])
const WRAPPED_NATIVE_CURRENCIES_ONLY: ChainTokenList = Object.fromEntries(
Object.entries(WRAPPED_NATIVE_CURRENCY).map(([key, value]) => [key, [value]])
)
// used to construct intermediary pairs for trading
export const BASES_TO_CHECK_TRADES_AGAINST: ChainTokenList = {
...WETH_ONLY,
[SupportedChainId.MAINNET]: [...WETH_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.OPTIMISM]: [...WETH_ONLY[SupportedChainId.OPTIMISM], DAI_OPTIMISM, USDT_OPTIMISM, WBTC_OPTIMISM],
...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
[SupportedChainId.OPTIMISM]: [
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.OPTIMISM],
DAI_OPTIMISM,
USDT_OPTIMISM,
WBTC_OPTIMISM,
],
[SupportedChainId.ARBITRUM_ONE]: [
...WETH_ONLY[SupportedChainId.ARBITRUM_ONE],
...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.ARBITRUM_ONE],
DAI_ARBITRUM_ONE,
USDT_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[] } } = {
[SupportedChainId.MAINNET]: {
@ -72,7 +90,7 @@ export const ADDITIONAL_BASES: { [chainId: number]: { [tokenAddress: string]: To
*/
export const CUSTOM_BASES: { [chainId: number]: { [tokenAddress: string]: Token[] } } = {
[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 = {
[SupportedChainId.MAINNET]: [
ExtendedEther.onChain(SupportedChainId.MAINNET),
nativeOnChain(SupportedChainId.MAINNET),
DAI,
USDC,
USDT,
WBTC,
WETH9_EXTENDED[SupportedChainId.MAINNET],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.MAINNET],
],
[SupportedChainId.ROPSTEN]: [
ExtendedEther.onChain(SupportedChainId.ROPSTEN),
WETH9_EXTENDED[SupportedChainId.ROPSTEN],
nativeOnChain(SupportedChainId.ROPSTEN),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ROPSTEN],
],
[SupportedChainId.RINKEBY]: [
ExtendedEther.onChain(SupportedChainId.RINKEBY),
WETH9_EXTENDED[SupportedChainId.RINKEBY],
nativeOnChain(SupportedChainId.RINKEBY),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.RINKEBY],
],
[SupportedChainId.GOERLI]: [ExtendedEther.onChain(SupportedChainId.GOERLI), WETH9_EXTENDED[SupportedChainId.GOERLI]],
[SupportedChainId.KOVAN]: [ExtendedEther.onChain(SupportedChainId.KOVAN), WETH9_EXTENDED[SupportedChainId.KOVAN]],
[SupportedChainId.GOERLI]: [nativeOnChain(SupportedChainId.GOERLI), WRAPPED_NATIVE_CURRENCY[SupportedChainId.GOERLI]],
[SupportedChainId.KOVAN]: [nativeOnChain(SupportedChainId.KOVAN), WRAPPED_NATIVE_CURRENCY[SupportedChainId.KOVAN]],
[SupportedChainId.ARBITRUM_ONE]: [
ExtendedEther.onChain(SupportedChainId.ARBITRUM_ONE),
nativeOnChain(SupportedChainId.ARBITRUM_ONE),
DAI_ARBITRUM_ONE,
USDC_ARBITRUM,
USDT_ARBITRUM_ONE,
WBTC_ARBITRUM_ONE,
WETH9_EXTENDED[SupportedChainId.ARBITRUM_ONE],
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_ONE],
],
[SupportedChainId.ARBITRUM_RINKEBY]: [
ExtendedEther.onChain(SupportedChainId.ARBITRUM_RINKEBY),
WETH9_EXTENDED[SupportedChainId.ARBITRUM_RINKEBY],
nativeOnChain(SupportedChainId.ARBITRUM_RINKEBY),
WRAPPED_NATIVE_CURRENCY[SupportedChainId.ARBITRUM_RINKEBY],
],
[SupportedChainId.OPTIMISM]: [
ExtendedEther.onChain(SupportedChainId.OPTIMISM),
nativeOnChain(SupportedChainId.OPTIMISM),
DAI_OPTIMISM,
USDC_OPTIMISM,
USDT_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
export const BASES_TO_TRACK_LIQUIDITY_FOR: ChainTokenList = {
...WETH_ONLY,
[SupportedChainId.MAINNET]: [...WETH_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
...WRAPPED_NATIVE_CURRENCIES_ONLY,
[SupportedChainId.MAINNET]: [...WRAPPED_NATIVE_CURRENCIES_ONLY[SupportedChainId.MAINNET], DAI, USDC, USDT, WBTC],
}
export const PINNED_PAIRS: { readonly [chainId: number]: [Token, Token][] } = {
[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 { SupportedChainId } from './chains'
@ -45,6 +45,34 @@ export const USDC_ARBITRUM = new Token(
'USDC',
'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(
SupportedChainId.OPTIMISM,
'0x7F5c764cBc14f9669B88837ca1490cCa17c31607',
@ -157,6 +185,21 @@ export const SWISE = new Token(
'SWISE',
'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 } = {
[SupportedChainId.MAINNET]: new Token(SupportedChainId.MAINNET, UNI_ADDRESS[1], 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'),
}
export const WETH9_EXTENDED: { [chainId: number]: Token } = {
export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token } = {
...WETH9,
[SupportedChainId.OPTIMISM]: new Token(
SupportedChainId.OPTIMISM,
@ -195,17 +238,61 @@ export const WETH9_EXTENDED: { [chainId: number]: Token } = {
'WETH',
'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 {
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')
}
private static _cachedEther: { [chainId: number]: ExtendedEther } = {}
private static _cachedExtendedEther: { [chainId: number]: NativeCurrency } = {}
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 { 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 { WrappedTokenInfo } from '../state/lists/wrappedTokenInfo'
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 isETH = currencyId?.toUpperCase() === 'ETH'
const token = useToken(isETH ? undefined : currencyId)
const extendedEther = useMemo(
return useMemo(
() =>
chainId
? ExtendedEther.onChain(chainId)
? nativeOnChain(chainId)
: // display mainnet when not connected
ExtendedEther.onChain(SupportedChainId.MAINNET),
nativeOnChain(SupportedChainId.MAINNET),
[chainId]
)
const weth = chainId ? WETH9_EXTENDED[chainId] : undefined
if (currencyId === null || currencyId === undefined) return currencyId
if (weth?.address?.toUpperCase() === currencyId?.toUpperCase()) return weth
return isETH ? extendedEther : token
}
export function useCurrency(currencyId: string | null | undefined): Currency | null | undefined {
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 { 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'
// returns null on errors
@ -74,7 +74,11 @@ export function useTokenContract(tokenAddress?: string, withSignerIfPossible?: b
export function useWETHContract(withSignerIfPossible?: boolean) {
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) {
@ -113,7 +117,7 @@ export function useV2RouterContract(): Contract | null {
return useContract(V2_ROUTER_ADDRESS, IUniswapV2Router02ABI, true)
}
export function useMulticall2Contract() {
export function useInterfaceMulticall() {
return useContract<UniswapInterfaceMulticall>(MULTICALL_ADDRESS, MulticallABI, false) as UniswapInterfaceMulticall
}

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

@ -7,7 +7,7 @@ import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { useUserSlippageToleranceWithDefault } from '../state/user/hooks'
import { useCurrency } from './Tokens'
import { useNativeCurrency } from './Tokens'
import useGasPrice from './useGasPrice'
import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'
import { useActiveWeb3React } from './web3'
@ -35,19 +35,23 @@ export default function useSwapSlippageTolerance(
const { chainId } = useActiveWeb3React()
const onL2 = chainId && L2_CHAIN_IDS.includes(chainId)
const outputDollarValue = useUSDCValue(trade?.outputAmount)
const ethGasPrice = useGasPrice()
const nativeGasPrice = useGasPrice()
const gasEstimate = guesstimateGas(trade)
const ether = useCurrency('ETH')
const etherPrice = useUSDCPrice(ether ?? undefined)
const nativeCurrency = useNativeCurrency()
const nativeCurrencyPrice = useUSDCPrice(nativeCurrency ?? undefined)
const defaultSlippageTolerance = useMemo(() => {
if (!trade || onL2) return ONE_TENTHS_PERCENT
const ethGasCost =
ethGasPrice && typeof gasEstimate === 'number' ? JSBI.multiply(ethGasPrice, JSBI.BigInt(gasEstimate)) : undefined
const nativeGasCost =
nativeGasPrice && typeof gasEstimate === 'number'
? JSBI.multiply(nativeGasPrice, JSBI.BigInt(gasEstimate))
: undefined
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
// 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
}, [trade, onL2, ethGasPrice, gasEstimate, ether, etherPrice, chainId, outputDollarValue])
}, [trade, onL2, nativeGasPrice, gasEstimate, nativeCurrency, nativeCurrencyPrice, chainId, outputDollarValue])
return useUserSlippageToleranceWithDefault(defaultSlippageTolerance)
}

@ -3,7 +3,7 @@ import { useMemo } from 'react'
import { tryParseAmount } from 'state/swap/hooks'
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 { useClientSideV3Trade } from './useClientSideV3Trade'
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.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
[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 { useMemo } from 'react'
import { WETH9_EXTENDED } from '../constants/tokens'
import { WRAPPED_NATIVE_CURRENCY } from '../constants/tokens'
import { tryParseAmount } from '../state/swap/hooks'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks'
import { useCurrencyBalance } from '../state/wallet/hooks'
import { useNativeCurrency } from './Tokens'
import { useWETHContract } from './useContract'
import { useActiveWeb3React } from './web3'
@ -16,6 +18,34 @@ export enum WrapType {
}
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
* @param inputCurrency the selected input currency
@ -26,7 +56,7 @@ export default function useWrapCallback(
inputCurrency: Currency | undefined | null,
outputCurrency: Currency | undefined | null,
typedValue: string | undefined
): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); inputError?: string } {
): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); inputError?: WrapInputError } {
const { chainId, account } = useActiveWeb3React()
const wethContract = useWETHContract()
const balance = useCurrencyBalance(account ?? undefined, inputCurrency ?? undefined)
@ -36,7 +66,7 @@ export default function useWrapCallback(
return useMemo(() => {
if (!wethContract || !chainId || !inputCurrency || !outputCurrency) return NOT_APPLICABLE
const weth = WETH9_EXTENDED[chainId]
const weth = WRAPPED_NATIVE_CURRENCY[chainId]
if (!weth) return NOT_APPLICABLE
const hasInputAmount = Boolean(inputAmount?.greaterThan('0'))
@ -54,13 +84,18 @@ export default function useWrapCallback(
type: TransactionType.WRAP,
unwrapped: false,
currencyAmountRaw: inputAmount?.quotient.toString(),
chainId,
})
} catch (error) {
console.error('Could not deposit', error)
}
}
: 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) {
return {
@ -74,13 +109,18 @@ export default function useWrapCallback(
type: TransactionType.WRAP,
unwrapped: true,
currencyAmountRaw: inputAmount?.quotient.toString(),
chainId,
})
} catch (error) {
console.error('Could not withdraw', error)
}
}
: undefined,
inputError: sufficientBalance ? undefined : hasInputAmount ? 'Insufficient WETH balance' : 'Enter WETH amount',
inputError: sufficientBalance
? undefined
: hasInputAmount
? WrapInputError.INSUFFICIENT_WRAPPED_BALANCE
: WrapInputError.ENTER_WRAPPED_AMOUNT,
}
} else {
return NOT_APPLICABLE

@ -71,3 +71,4 @@ ReactDOM.render(
if (process.env.REACT_APP_SERVICE_WORKER !== 'false') {
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 { NONFUNGIBLE_POSITION_MANAGER_ADDRESSES } from '../../constants/addresses'
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 { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import { useArgentWalletContract } from '../../hooks/useArgentWalletContract'
@ -315,10 +315,12 @@ export default function AddLiquidity({
} else {
// prevent weth + eth
const isETHOrWETHNew =
currencyIdNew === 'ETH' || (chainId !== undefined && currencyIdNew === WETH9_EXTENDED[chainId]?.address)
currencyIdNew === 'ETH' ||
(chainId !== undefined && currencyIdNew === WRAPPED_NATIVE_CURRENCY[chainId]?.address)
const isETHOrWETHOther =
currencyIdOther !== undefined &&
(currencyIdOther === 'ETH' || (chainId !== undefined && currencyIdOther === WETH9_EXTENDED[chainId]?.address))
(currencyIdOther === 'ETH' ||
(chainId !== undefined && currencyIdOther === WRAPPED_NATIVE_CURRENCY[chainId]?.address))
if (isETHOrWETHNew && isETHOrWETHOther) {
return [currencyIdNew, undefined]

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

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

@ -36,7 +36,7 @@ import CurrencyLogo from '../../components/CurrencyLogo'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
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 { usePairContract, useV2MigratorContract } from '../../hooks/useContract'
import { useV2LiquidityTokenPermit } from '../../hooks/useERC20Permit'
@ -592,10 +592,10 @@ function V2PairMigration({
<ThemedText.Black fontSize={12}>
<Trans>
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)}{' '}
{token1.equals(WETH9_EXTENDED[chainId]) ? 'ETH' : token1.symbol} will be refunded to your wallet
due to selected price range.
{token1.equals(WRAPPED_NATIVE_CURRENCY[chainId]) ? 'ETH' : token1.symbol} will be refunded to your
wallet due to selected price range.
</Trans>
</ThemedText.Black>
) : null}

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

@ -16,7 +16,7 @@ import { MinimalPositionCard } from '../../components/PositionCard'
import Row from '../../components/Row'
import CurrencySearchModal from '../../components/SearchModal/CurrencySearchModal'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import { ExtendedEther } from '../../constants/tokens'
import { nativeOnChain } from '../../constants/tokens'
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
import { useActiveWeb3React } from '../../hooks/web3'
import { usePairAdder } from '../../state/user/hooks'
@ -44,7 +44,7 @@ export default function PoolFinder() {
const [showSearch, setShowSearch] = useState<boolean>(false)
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 [pairState, pair] = useV2Pair(currency0 ?? undefined, currency1 ?? undefined)

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

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

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

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

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

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

@ -5,9 +5,8 @@ import { useCallback, useEffect, useState } from 'react'
import { api, CHAIN_TAG } from 'state/data/enhanced'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId'
import { switchToNetwork } from 'utils/switchToNetwork'
import { setImplements3085, updateBlockNumber, updateChainId } from './reducer'
import { updateBlockNumber, updateChainId } from './reducer'
function useQueryCacheInvalidator() {
const dispatch = useAppDispatch()
@ -23,7 +22,7 @@ function useQueryCacheInvalidator() {
}
export default function Updater(): null {
const { account, chainId, library } = useActiveWeb3React()
const { chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible()
@ -77,19 +76,5 @@ export default function Updater(): null {
)
}, [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
}

@ -13,6 +13,8 @@ const CHAIN_SUBGRAPH_URL: Record<number, string> = {
[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.POLYGON]: 'https://api.thegraph.com/subgraphs/name/ianlapham/uniswap-v3-polygon',
}
export const api = createApi({

@ -194,14 +194,14 @@ export function useV3DerivedMintInfo(
// check for invalid price input (converts to invalid ratio)
const invalidPrice = useMemo(() => {
const sqrtRatioX96 = price ? encodeSqrtRatioX96(price.numerator, price.denominator) : undefined
const invalid =
return (
price &&
sqrtRatioX96 &&
!(
JSBI.greaterThanOrEqual(sqrtRatioX96, TickMath.MIN_SQRT_RATIO) &&
JSBI.lessThan(sqrtRatioX96, TickMath.MAX_SQRT_RATIO)
)
return invalid
)
}, [price])
// 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 { useBlockNumber } from '../application/hooks'
import { multicall } from './instance'
@ -7,6 +7,6 @@ import { multicall } from './instance'
export default function Updater() {
const latestBlockNumber = useBlockNumber()
const { chainId } = useActiveWeb3React()
const multicall2Contract = useMulticall2Contract()
const multicall2Contract = useInterfaceMulticall()
return <multicall.Updater chainId={chainId} latestBlockNumber={latestBlockNumber} contract={multicall2Contract} />
}

@ -1,6 +1,5 @@
import { AlphaRouterParams, IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
import { NETWORK_URLS } from 'connectors'
import { SupportedChainId } from 'constants/chains'
import { INFURA_NETWORK_URLS, SupportedChainId } from 'constants/chains'
import { providers } from 'ethers/lib/ethers'
import ReactGA from 'react-ga'
@ -14,7 +13,7 @@ export type Dependencies = {
export function buildDependencies(): Dependencies {
const dependenciesByChain: Dependencies = {}
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] = {
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'
const USDC = new Token(1, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC')
const DAI = new Token(1, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 6, 'DAI')
const MKR = new Token(1, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 6, 'MKR')
const ETH = Ether.onChain(1)
const ETH = nativeOnChain(1)
// helper function to make amounts more readable
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 { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
import { nativeOnChain } from '../../constants/tokens'
import { GetQuoteResult, InterfaceTrade, V2PoolInRoute, V3PoolInRoute } from './types'
/**
@ -24,9 +25,9 @@ export function computeRoutes(
if (parsedTokenIn.address !== currencyIn.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 {
return quoteResult.route.map((route) => {

@ -7,7 +7,7 @@ import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import JSBI from 'jsbi'
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 { NEVER_RELOAD, useMultipleContractSingleData } from '../multicall/hooks'
import { tryParseAmount } from '../swap/hooks'
@ -26,19 +26,19 @@ export const STAKING_REWARDS_INFO: {
} = {
1: [
{
tokens: [WETH9_EXTENDED[1], DAI],
tokens: [WRAPPED_NATIVE_CURRENCY[1], DAI],
stakingRewardAddress: '0xa1484C3aa22a66C62b77E0AE78E15258bd0cB711',
},
{
tokens: [WETH9_EXTENDED[1], USDC],
tokens: [WRAPPED_NATIVE_CURRENCY[1], USDC],
stakingRewardAddress: '0x7FBa4B8Dc5E7616e59622806932DBea72537A56b',
},
{
tokens: [WETH9_EXTENDED[1], USDT],
tokens: [WRAPPED_NATIVE_CURRENCY[1], USDT],
stakingRewardAddress: '0x6C3e4cb2E96B01F4b866965A91ed4437839A121a',
},
{
tokens: [WETH9_EXTENDED[1], WBTC],
tokens: [WRAPPED_NATIVE_CURRENCY[1], WBTC],
stakingRewardAddress: '0xCA35e32e7926b96A9988f61d510E038108d8068e',
},
],

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

@ -15,13 +15,8 @@ export interface SerializedPair {
}
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 acknowledgeNetworkAlert = createAction<{ chainId: number }>('user/acknowledgeNetworkAlert')
export const updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale')
export const updateUserClientSideRouter = createAction<{ userClientSideRouter: boolean }>(

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

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

@ -1,13 +1,13 @@
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 { Erc20Interface } from 'abis/types/Erc20'
import JSBI from 'jsbi'
import { useMemo } from 'react'
import { UNI } from '../../constants/tokens'
import { nativeOnChain, UNI } from '../../constants/tokens'
import { useAllTokens } from '../../hooks/Tokens'
import { useMulticall2Contract } from '../../hooks/useContract'
import { useInterfaceMulticall } from '../../hooks/useContract'
import { useActiveWeb3React } from '../../hooks/web3'
import { isAddress } from '../../utils'
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.
*/
export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): {
export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefined)[]): {
[address: string]: CurrencyAmount<Currency> | undefined
} {
const { chainId } = useActiveWeb3React()
const multicallContract = useMulticall2Contract()
const multicallContract = useInterfaceMulticall()
const addresses: string[] = useMemo(
const validAddressInputs: [string][] = useMemo(
() =>
uncheckedAddresses
? uncheckedAddresses
.map(isAddress)
.filter((a): a is string => a !== false)
.sort()
.map((addr) => [addr])
: [],
[uncheckedAddresses]
)
const results = useSingleContractMultipleData(
multicallContract,
'getEthBalance',
addresses.map((address) => [address])
)
const results = useSingleContractMultipleData(multicallContract, 'getEthBalance', validAddressInputs)
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]
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
}, {}),
[addresses, chainId, results]
[validAddressInputs, chainId, results]
)
}
@ -120,7 +117,7 @@ export function useCurrencyBalances(
const tokenBalances = useTokenBalances(account, tokens)
const containsETH: boolean = useMemo(() => currencies?.some((currency) => currency?.isNative) ?? false, [currencies])
const ethBalance = useETHBalances(containsETH ? [account] : [])
const ethBalance = useNativeCurrencyBalances(containsETH ? [account] : [])
return useMemo(
() =>

@ -8,11 +8,13 @@ const initialStyles = {
width: '200vw',
height: '200vh',
transform: 'translate(-50vw, -100vh)',
backgroundBlendMode: '',
}
const backgroundResetStyles = {
width: '100vw',
height: '100vh',
transform: 'unset',
backgroundBlendMode: '',
}
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%)'
backgroundRadialGradientElement.style.background = darkMode ? optimismDarkGradient : optimismLightGradient
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:
setBackground(initialStyles)
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>(
address: T,
additionalNetworks: SupportedChainId[] = []
): { [chainId: number]: T } {
return (L1_CHAIN_IDS as readonly SupportedChainId[])
.concat(additionalNetworks)
.reduce<{ [chainId: number]: T }>((memo, chainId) => {
memo[chainId] = address
return memo
}, {})
return DEFAULT_NETWORKS.concat(additionalNetworks).reduce<{ [chainId: number]: T }>((memo, chainId) => {
memo[chainId] = address
return memo
}, {})
}

@ -13,6 +13,12 @@ describe('#getExplorerLink', () => {
it('unrecognized chain id defaults to mainnet', () => {
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', () => {
expect(getExplorerLink(3, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://ropsten.etherscan.io/address/abc')
})

@ -1,13 +1,15 @@
import { SupportedChainId } from '../constants/chains'
const ETHERSCAN_PREFIXES: { [chainId: number]: string } = {
[SupportedChainId.MAINNET]: '',
[SupportedChainId.ROPSTEN]: 'ropsten.',
[SupportedChainId.RINKEBY]: 'rinkeby.',
[SupportedChainId.GOERLI]: 'goerli.',
[SupportedChainId.KOVAN]: 'kovan.',
[SupportedChainId.OPTIMISM]: 'optimistic.',
[SupportedChainId.OPTIMISTIC_KOVAN]: 'kovan-optimistic.',
[SupportedChainId.MAINNET]: 'https://etherscan.io',
[SupportedChainId.ROPSTEN]: 'https://ropsten.etherscan.io',
[SupportedChainId.RINKEBY]: 'https://rinkeby.etherscan.io',
[SupportedChainId.GOERLI]: 'https://goerli.etherscan.io',
[SupportedChainId.KOVAN]: 'https://kovan.etherscan.io',
[SupportedChainId.OPTIMISM]: 'https://optimistic.etherscan.io',
[SupportedChainId.OPTIMISTIC_KOVAN]: 'https://kovan-optimistic.etherscan.io',
[SupportedChainId.POLYGON_MUMBAI]: 'https://mumbai.polygonscan.com',
[SupportedChainId.POLYGON]: 'https://polygonscan.com',
}
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) {
case ExplorerDataType.TRANSACTION:

@ -3,11 +3,9 @@ import { hexStripZeros } from '@ethersproject/bytes'
import { Web3Provider } from '@ethersproject/providers'
import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
import { addNetwork } from './addNetwork'
interface SwitchNetworkArguments {
library: Web3Provider
chainId?: SupportedChainId
chainId: SupportedChainId
}
// 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) {
return
}
if (!chainId && library?.getNetwork) {
;({ chainId } = await library.getNetwork())
}
const formattedChainId = hexStripZeros(BigNumber.from(chainId).toHexString())
try {
await library?.provider.request({
await library.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
} catch (error) {
// 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]
// 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 addNetwork({ library, chainId, info })
await switchToNetwork({ library, chainId })
await library.provider.request({
method: 'wallet_addEthereumChain',
params: [
{
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 {
throw error
}

@ -1,12 +1,12 @@
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'
export function unwrappedToken(currency: Currency): Currency {
if (currency.isNative) return currency
const formattedChainId = supportedChainId(currency.chainId)
if (formattedChainId && currency.equals(WETH9_EXTENDED[formattedChainId]))
return ExtendedEther.onChain(currency.chainId)
if (formattedChainId && currency.equals(WRAPPED_NATIVE_CURRENCY[formattedChainId]))
return nativeOnChain(currency.chainId)
return currency
}