Commit f047f0d1 authored by Moody Salem's avatar Moody Salem Committed by GitHub

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: default avatarSara Reynolds <30504811+snreynolds@users.noreply.github.com>
parent 7ac1ed3f
<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>
<?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>
......@@ -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>
)
}
......
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} />
......
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,32 +29,43 @@ const ReadMoreLink = styled(ExternalLink)`
text-decoration: underline;
`
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<Root>
<WarningIcon />
<div>{children}</div>
</Root>
)
}
/**
* Shows a downtime warning for the network if it's relevant
*/
export default function DowntimeWarning() {
const { chainId } = useActiveWeb3React()
if (!chainId || !L2_CHAIN_IDS.includes(chainId)) {
if (!isL2ChainId(chainId)) {
return null
}
const Content = () => {
switch (chainId) {
case SupportedChainId.OPTIMISM:
case SupportedChainId.OPTIMISTIC_KOVAN:
return (
<div>
<Wrapper>
<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.{' '}
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>
</div>
</Wrapper>
)
case SupportedChainId.ARBITRUM_ONE:
case SupportedChainId.ARBITRUM_RINKEBY:
return (
<div>
<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.{' '}
......@@ -61,17 +73,10 @@ export default function DowntimeWarning() {
Read more.
</ReadMoreLink>
</Trans>
</div>
</Wrapper>
)
default:
return null
}
}
return (
<Root>
<WarningIcon />
<Content />
</Root>
)
}
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>
}
}
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 conditionalToggle = useCallback(() => {
if (showSelector) {
toggle()
}
}, [showSelector, toggle])
if (!chainId || !info || !library) {
return null
}
function Row({ targetChain }: { targetChain: number }) {
if (!library || !chainId || (!implements3085 && targetChain !== chainId)) {
function Row({
targetChain,
onSelectChain,
}: {
targetChain: SupportedChainId
onSelectChain: (targetChain: number) => void
}) {
const { library, chainId } = useActiveWeb3React()
if (!library || !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>
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>
)
const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
if (active && hasExtendedInfo) {
if (active) {
return (
<ActiveRowWrapper>
<RowContent />
{rowContent}
<ActiveRowLinkList>
<ExternalLink href={CHAIN_INFO[targetChain as SupportedL2ChainId].bridge}>
<BridgeText chainId={chainId} /> <LinkOutCircle />
{bridge ? (
<ExternalLink href={bridge}>
<BridgeLabel chainId={chainId} /> <LinkOutCircle />
</ExternalLink>
<ExternalLink href={CHAIN_INFO[targetChain].explorer}>
<ExplorerText chainId={chainId} /> <LinkOutCircle />
) : null}
{explorer ? (
<ExternalLink href={explorer}>
<ExplorerLabel chainId={chainId} /> <LinkOutCircle />
</ExternalLink>
<ExternalLink href={helpCenterLink}>
) : null}
{helpCenterUrl ? (
<ExternalLink href={helpCenterUrl}>
<Trans>Help Center</Trans> <LinkOutCircle />
</ExternalLink>
) : null}
</ActiveRowLinkList>
</ActiveRowWrapper>
)
}
return <RowContent />
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 info = chainId ? CHAIN_INFO[chainId] : undefined
const dispatch = useAppDispatch()
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
}
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>
......
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 />
......
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,9 +83,11 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
routes={routes}
/>
)}
{autoRouterSupported && (
<>
<Separator />
{autoRouterSupported &&
(syncing ? (
{syncing ? (
<LoadingRows>
<div style={{ width: '250px', height: '15px' }} />
</LoadingRows>
......@@ -95,11 +97,13 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
<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.
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,
])
This diff is collapsed.
......@@ -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]: [
......
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
}
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
if (weth?.address?.toUpperCase() === currencyId?.toUpperCase()) return weth
return isETH ? extendedEther : token
// 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
}
......
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),
}
/**
......
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]
......
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}
/>
<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 ? (
{wrapInputError ? (
<WrapErrorText wrapInputError={wrapInputError} />
) : wrapType === WrapType.WRAP ? (
<Trans>Wrap</Trans>
) : wrapType === WrapType.UNWRAP ? (
<Trans>Unwrap</Trans>
) : null)}
) : null}
</ButtonPrimary>
) : routeNotFound && userHasSpecifiedInputOutput && !routeIsLoading && !routeIsSyncing ? (
<GreyCard style={{ textAlign: 'center' }}>
......
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: [],
})
......
import { createSlice, nanoid } from '@reduxjs/toolkit'
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
export type PopupContent = {
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
......
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} />
}
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,
......
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()
......
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 }))
}
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 }))
}
const alertAcknowledged = useAppSelector(({ user }) => user.networkAlertsAcknowledged)
const acknowledgeAlert = useCallback(() => {
if (typeof chainId !== 'number') return
dispatch(acknowledgeNetworkAlert({ chainId }))
}, [chainId, dispatch])
return [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged]
return [typeof chainId === 'number' ? alertAcknowledged[chainId] ?? false : false, acknowledgeAlert]
}
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()
......
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 = ''
......
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)
}
}
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
}
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) => {
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')
})
......
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]
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 addNetwork({ library, chainId, info })
await switchToNetwork({ library, chainId })
await library.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: formattedChainId }],
})
}
} else {
throw error
}
......
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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment