-const Base = styled(RebassButton)<
+export const BaseButton = styled(RebassButton)<
{
padding?: string
width?: string
@@ -50,7 +50,7 @@ const Base = styled(RebassButton)<
}
`
-export const ButtonPrimary = styled(Base)`
+export const ButtonPrimary = styled(BaseButton)`
background-color: ${({ theme }) => theme.primary1};
color: white;
&:focus {
@@ -67,7 +67,8 @@ export const ButtonPrimary = styled(Base)`
&:disabled {
background-color: ${({ theme, altDisabledStyle, disabled }) =>
altDisabledStyle ? (disabled ? theme.primary1 : theme.bg2) : theme.bg2};
- color: ${({ theme }) => theme.text2};
+ color: ${({ altDisabledStyle, disabled, theme }) =>
+ altDisabledStyle ? (disabled ? theme.white : theme.text2) : theme.text2};
cursor: auto;
box-shadow: none;
border: 1px solid transparent;
@@ -75,7 +76,7 @@ export const ButtonPrimary = styled(Base)`
}
`
-export const ButtonLight = styled(Base)`
+export const ButtonLight = styled(BaseButton)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primaryText1};
font-size: 16px;
@@ -103,7 +104,7 @@ export const ButtonLight = styled(Base)`
}
`
-export const ButtonGray = styled(Base)`
+export const ButtonGray = styled(BaseButton)`
background-color: ${({ theme }) => theme.bg1};
color: ${({ theme }) => theme.text2};
font-size: 16px;
@@ -117,7 +118,7 @@ export const ButtonGray = styled(Base)`
}
`
-export const ButtonSecondary = styled(Base)`
+export const ButtonSecondary = styled(BaseButton)`
border: 1px solid ${({ theme }) => theme.primary4};
color: ${({ theme }) => theme.primary1};
background-color: transparent;
@@ -145,7 +146,7 @@ export const ButtonSecondary = styled(Base)`
}
`
-export const ButtonOutlined = styled(Base)`
+export const ButtonOutlined = styled(BaseButton)`
border: 1px solid ${({ theme }) => theme.bg2};
background-color: transparent;
color: ${({ theme }) => theme.text1};
@@ -164,7 +165,7 @@ export const ButtonOutlined = styled(Base)`
}
`
-export const ButtonYellow = styled(Base)`
+export const ButtonYellow = styled(BaseButton)`
background-color: ${({ theme }) => theme.yellow3};
color: white;
&:focus {
@@ -185,7 +186,7 @@ export const ButtonYellow = styled(Base)`
}
`
-export const ButtonEmpty = styled(Base)`
+export const ButtonEmpty = styled(BaseButton)`
background-color: transparent;
color: ${({ theme }) => theme.primary1};
display: flex;
@@ -207,7 +208,7 @@ export const ButtonEmpty = styled(Base)`
}
`
-export const ButtonText = styled(Base)`
+export const ButtonText = styled(BaseButton)`
padding: 0;
width: fit-content;
background: none;
@@ -229,7 +230,7 @@ export const ButtonText = styled(Base)`
}
`
-const ButtonConfirmedStyle = styled(Base)`
+const ButtonConfirmedStyle = styled(BaseButton)`
background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.text1};
/* border: 1px solid ${({ theme }) => theme.green1}; */
@@ -242,7 +243,7 @@ const ButtonConfirmedStyle = styled(Base)`
}
`
-const ButtonErrorStyle = styled(Base)`
+const ButtonErrorStyle = styled(BaseButton)`
background-color: ${({ theme }) => theme.red1};
border: 1px solid ${({ theme }) => theme.red1};
diff --git a/src/components/DowntimeWarning/index.tsx b/src/components/DowntimeWarning/index.tsx
new file mode 100644
index 0000000000..35401bcfb0
--- /dev/null
+++ b/src/components/DowntimeWarning/index.tsx
@@ -0,0 +1,77 @@
+import { Trans } from '@lingui/macro'
+import { L2_CHAIN_IDS, SupportedChainId } from 'constants/chains'
+import { useActiveWeb3React } from 'hooks/web3'
+import { AlertOctagon } from 'react-feather'
+import styled from 'styled-components/macro'
+import { ExternalLink } from 'theme'
+
+const Root = styled.div`
+ background-color: ${({ theme }) => (theme.darkMode ? '#888D9B' : '#CED0D9')};
+ border-radius: 18px;
+ color: black;
+ display: flex;
+ flex-direction: row;
+ font-size: 14px;
+ margin: 12px auto;
+ padding: 16px;
+ width: 100%;
+ max-width: 880px;
+`
+const WarningIcon = styled(AlertOctagon)`
+ display: block;
+ margin: auto 16px auto 0;
+ min-height: 22px;
+ min-width: 22px;
+`
+const ReadMoreLink = styled(ExternalLink)`
+ color: black;
+ text-decoration: underline;
+`
+
+export default function DowntimeWarning() {
+ const { chainId } = useActiveWeb3React()
+ if (!chainId || !L2_CHAIN_IDS.includes(chainId)) {
+ return null
+ }
+
+ const Content = () => {
+ switch (chainId) {
+ case SupportedChainId.OPTIMISM:
+ case SupportedChainId.OPTIMISTIC_KOVAN:
+ return (
+
+
+ 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.{' '}
+
+ Read more.
+
+
+
+ )
+ case SupportedChainId.ARBITRUM_ONE:
+ case SupportedChainId.ARBITRUM_RINKEBY:
+ return (
+
+
+ Arbitrum is in Beta and may experience downtime. During downtime, your position will not earn fees and you
+ will be unable to remove liquidity.{' '}
+
+ Read more.
+
+
+
+ )
+ default:
+ return null
+ }
+ }
+
+ return (
+
+
+
+
+ )
+}
diff --git a/src/components/Header/ChainConnectivityWarning.tsx b/src/components/Header/ChainConnectivityWarning.tsx
index e33b6862f7..87c992c88b 100644
--- a/src/components/Header/ChainConnectivityWarning.tsx
+++ b/src/components/Header/ChainConnectivityWarning.tsx
@@ -60,7 +60,7 @@ export function ChainConnectivityWarning() {
{chainId === SupportedChainId.MAINNET ? (
You may have lost your network connection.
) : (
- {label} may be down right now, or you may have lost your network connection.
+ You may have lost your network connection, or {label} might be down right now.
)}{' '}
{(info as L2ChainInfo).statusPage !== undefined && (
diff --git a/src/components/Header/NetworkCard.tsx b/src/components/Header/NetworkCard.tsx
deleted file mode 100644
index a34986ad95..0000000000
--- a/src/components/Header/NetworkCard.tsx
+++ /dev/null
@@ -1,235 +0,0 @@
-import { Trans } from '@lingui/macro'
-import { YellowCard } from 'components/Card'
-import { useOnClickOutside } from 'hooks/useOnClickOutside'
-import { useActiveWeb3React } from 'hooks/web3'
-import { useEffect, useRef, useState } from 'react'
-import { ArrowDownCircle, ChevronDown, ToggleLeft } from 'react-feather'
-import { ApplicationModal } from 'state/application/actions'
-import { useModalOpen, useToggleModal } from 'state/application/hooks'
-import styled, { css } from 'styled-components/macro'
-import { ExternalLink } from 'theme'
-import { switchToNetwork } from 'utils/switchToNetwork'
-import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from '../../constants/chains'
-
-const BaseWrapper = css`
- position: relative;
- margin-right: 8px;
- ${({ theme }) => theme.mediaWidth.upToMedium`
- justify-self: end;
- `};
-
- ${({ theme }) => theme.mediaWidth.upToSmall`
- margin: 0 0.5rem 0 0;
- width: initial;
- text-overflow: ellipsis;
- flex-shrink: 1;
- `};
-`
-const L2Wrapper = styled.div`
- ${BaseWrapper}
-`
-const BaseMenuItem = css`
- align-items: center;
- background-color: transparent;
- border-radius: 12px;
- color: ${({ theme }) => theme.text2};
- cursor: pointer;
- display: flex;
- flex: 1;
- flex-direction: row;
- font-size: 16px;
- font-weight: 400;
- justify-content: space-between;
- :hover {
- color: ${({ theme }) => theme.text1};
- text-decoration: none;
- }
-`
-const DisabledMenuItem = styled.div`
- ${BaseMenuItem}
- align-items: center;
- background-color: ${({ theme }) => theme.bg2};
- cursor: auto;
- display: flex;
- font-size: 10px;
- font-style: italic;
- justify-content: center;
- :hover,
- :active,
- :focus {
- color: ${({ theme }) => theme.text2};
- }
-`
-const FallbackWrapper = styled(YellowCard)`
- ${BaseWrapper}
- width: auto;
- border-radius: 12px;
- padding: 8px 12px;
- width: 100%;
- user-select: none;
-`
-const Icon = styled.img`
- width: 16px;
- margin-right: 2px;
-
- ${({ theme }) => theme.mediaWidth.upToSmall`
- margin-right: 4px;
-
- `};
-`
-
-const MenuFlyout = styled.span`
- background-color: ${({ theme }) => theme.bg1};
- border: 1px solid ${({ theme }) => theme.bg0};
-
- box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
- 0px 24px 32px rgba(0, 0, 0, 0.01);
- border-radius: 12px;
- padding: 1rem;
- display: flex;
- flex-direction: column;
- font-size: 1rem;
- position: absolute;
- left: 0rem;
- top: 3rem;
- z-index: 100;
- width: 237px;
- ${({ theme }) => theme.mediaWidth.upToMedium`
-
- bottom: unset;
- top: 4.5em
- right: 0;
-
- `};
- > {
- padding: 12px;
- }
- > :not(:first-child) {
- margin-top: 8px;
- }
- > :not(:last-child) {
- margin-bottom: 8px;
- }
-`
-const LinkOutCircle = styled(ArrowDownCircle)`
- transform: rotate(230deg);
- width: 16px;
- height: 16px;
- opacity: 0.6;
-`
-const MenuItem = styled(ExternalLink)`
- ${BaseMenuItem}
-`
-const ButtonMenuItem = styled.button`
- ${BaseMenuItem}
- border: none;
- box-shadow: none;
- color: ${({ theme }) => theme.text2};
- outline: none;
- padding: 0;
-`
-const NetworkInfo = styled.button<{ chainId: SupportedChainId }>`
- align-items: center;
- background-color: ${({ theme }) => theme.bg0};
- border-radius: 12px;
- border: 1px solid ${({ theme }) => theme.bg0};
- color: ${({ theme }) => theme.text1};
- display: flex;
- flex-direction: row;
- font-weight: 500;
- font-size: 12px;
- height: 100%;
- margin: 0;
- height: 38px;
- padding: 0.7rem;
-
- :hover,
- :focus {
- cursor: pointer;
- outline: none;
- border: 1px solid ${({ theme }) => theme.bg3};
- }
-`
-const NetworkName = styled.div<{ chainId: SupportedChainId }>`
- border-radius: 6px;
- font-size: 16px;
- font-weight: 500;
- padding: 0 2px 0.5px 4px;
- margin: 0 2px;
- white-space: pre;
- ${({ theme }) => theme.mediaWidth.upToSmall`
- display: none;
- `};
-`
-
-export default function NetworkCard() {
- const { chainId, library } = useActiveWeb3React()
- const node = useRef(null)
- const open = useModalOpen(ApplicationModal.ARBITRUM_OPTIONS)
- const toggle = useToggleModal(ApplicationModal.ARBITRUM_OPTIONS)
- useOnClickOutside(node, open ? toggle : undefined)
-
- const [implements3085, setImplements3085] = useState(false)
- useEffect(() => {
- // metamask is currently the only known implementer of this EIP
- // here we proceed w/ a noop feature check to ensure the user's version of metamask supports network switching
- // if not, we hide the UI
- if (!library?.provider?.request || !chainId || !library?.provider?.isMetaMask) {
- return
- }
- switchToNetwork({ library, chainId })
- .then((x) => x ?? setImplements3085(true))
- .catch(() => setImplements3085(false))
- }, [library, chainId])
-
- const info = chainId ? CHAIN_INFO[chainId] : undefined
- if (!chainId || chainId === SupportedChainId.MAINNET || !info || !library) {
- return null
- }
-
- if (L2_CHAIN_IDS.includes(chainId)) {
- const info = CHAIN_INFO[chainId as SupportedL2ChainId]
- const isArbitrum = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY].includes(chainId)
- return (
-
-
-
- {info.label}
-
-
- {open && (
-
-
-
-
- {implements3085 ? (
- switchToNetwork({ library, chainId: SupportedChainId.MAINNET })}>
-
- Switch to L1 (Mainnet)
-
-
-
- ) : (
-
- Change your network to go back to L1
-
- )}
-
- )}
-
- )
- }
-
- return {info.label}
-}
diff --git a/src/components/Header/NetworkSelector.tsx b/src/components/Header/NetworkSelector.tsx
new file mode 100644
index 0000000000..5b12509b90
--- /dev/null
+++ b/src/components/Header/NetworkSelector.tsx
@@ -0,0 +1,249 @@
+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 { useOnClickOutside } from 'hooks/useOnClickOutside'
+import { useActiveWeb3React } from 'hooks/web3'
+import { useCallback, useRef } from 'react'
+import { ArrowDownCircle, ChevronDown } from 'react-feather'
+import { ApplicationModal } from 'state/application/actions'
+import { useModalOpen, useToggleModal } from 'state/application/hooks'
+import { useAppSelector } from 'state/hooks'
+import styled from 'styled-components/macro'
+import { ExternalLink, MEDIA_WIDTHS } from 'theme'
+import { switchToNetwork } from 'utils/switchToNetwork'
+
+const ActiveRowLinkList = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 0 8px;
+ & > a {
+ align-items: center;
+ color: ${({ theme }) => theme.text2};
+ display: flex;
+ flex-direction: row;
+ font-size: 14px;
+ font-weight: 500;
+ justify-content: space-between;
+ padding: 8px 0 4px;
+ text-decoration: none;
+ }
+ & > a:first-child {
+ border-top: 1px solid ${({ theme }) => theme.text2};
+ margin: 0;
+ margin-top: 6px;
+ padding-top: 10px;
+ }
+`
+const ActiveRowWrapper = styled.div`
+ background-color: ${({ theme }) => theme.bg2};
+ border-radius: 8px;
+ cursor: pointer;
+ padding: 8px 0 8px 0;
+ width: 100%;
+`
+const FlyoutHeader = styled.div`
+ color: ${({ theme }) => theme.text2};
+ font-weight: 400;
+`
+const FlyoutMenu = styled.div`
+ align-items: flex-start;
+ background-color: ${({ theme }) => theme.bg1};
+ box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
+ 0px 24px 32px rgba(0, 0, 0, 0.01);
+ border-radius: 20px;
+ display: flex;
+ flex-direction: column;
+ font-size: 16px;
+ overflow: auto;
+ padding: 16px;
+ position: absolute;
+ top: 64px;
+ width: 272px;
+ z-index: 99;
+ & > *:not(:last-child) {
+ margin-bottom: 12px;
+ }
+ @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
+ top: 50px;
+ }
+`
+const FlyoutRow = styled.div<{ active: boolean }>`
+ align-items: center;
+ background-color: ${({ active, theme }) => (active ? theme.bg2 : 'transparent')};
+ border-radius: 8px;
+ cursor: pointer;
+ display: flex;
+ font-weight: 500;
+ justify-content: space-between;
+ padding: 6px 8px;
+ text-align: left;
+ width: 100%;
+`
+const FlyoutRowActiveIndicator = styled.div`
+ background-color: ${({ theme }) => theme.green1};
+ border-radius: 50%;
+ height: 9px;
+ width: 9px;
+`
+const LinkOutCircle = styled(ArrowDownCircle)`
+ transform: rotate(230deg);
+ width: 16px;
+ height: 16px;
+`
+const Logo = styled.img`
+ height: 20px;
+ width: 20px;
+ margin-right: 8px;
+`
+const NetworkLabel = styled.div`
+ flex: 1 1 auto;
+`
+const SelectorLabel = styled(NetworkLabel)`
+ display: none;
+ @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
+ display: block;
+ margin-right: 8px;
+ }
+`
+const SelectorControls = styled.div<{ interactive: boolean }>`
+ align-items: center;
+ background-color: ${({ theme }) => theme.bg1};
+ border: 2px solid ${({ theme }) => theme.bg1};
+ border-radius: 12px;
+ color: ${({ theme }) => theme.text1};
+ cursor: ${({ interactive }) => (interactive ? 'pointer' : 'auto')};
+ display: flex;
+ font-weight: 500;
+ justify-content: space-between;
+ padding: 6px 8px;
+`
+const SelectorLogo = styled(Logo)<{ interactive?: boolean }>`
+ margin-right: ${({ interactive }) => (interactive ? 8 : 0)}px;
+ @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
+ margin-right: 8px;
+ }
+`
+const SelectorWrapper = styled.div`
+ @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
+ position: relative;
+ }
+`
+const StyledChevronDown = styled(ChevronDown)`
+ width: 12px;
+`
+const BridgeText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
+ switch (chainId) {
+ case SupportedChainId.ARBITRUM_ONE:
+ case SupportedChainId.ARBITRUM_RINKEBY:
+ return Arbitrum Bridge
+ case SupportedChainId.OPTIMISM:
+ case SupportedChainId.OPTIMISTIC_KOVAN:
+ return Optimism Gateway
+ default:
+ return Bridge
+ }
+}
+const ExplorerText = ({ chainId }: { chainId: SupportedL2ChainId }) => {
+ switch (chainId) {
+ case SupportedChainId.ARBITRUM_ONE:
+ case SupportedChainId.ARBITRUM_RINKEBY:
+ return Arbiscan
+ case SupportedChainId.OPTIMISM:
+ case SupportedChainId.OPTIMISTIC_KOVAN:
+ return Optimistic Etherscan
+ default:
+ return Explorer
+ }
+}
+
+export default function NetworkSelector() {
+ const { chainId, library } = useActiveWeb3React()
+ const node = useRef()
+ 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)) {
+ 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 = () => (
+
+
+ {rowText}
+ {chainId === targetChain && }
+
+ )
+ const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
+ if (active && hasExtendedInfo) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ Help Center
+
+
+
+ )
+ }
+ return
+ }
+
+ return (
+
+
+
+ {info.label}
+ {showSelector && }
+
+ {open && (
+
+
+ Select a network
+
+
+
+
+
+ )}
+
+ )
+}
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
index dbece88e22..a04efa3b1c 100644
--- a/src/components/Header/index.tsx
+++ b/src/components/Header/index.tsx
@@ -22,7 +22,7 @@ import Modal from '../Modal'
import Row from '../Row'
import { Dots } from '../swap/styleds'
import Web3Status from '../Web3Status'
-import NetworkCard from './NetworkCard'
+import NetworkSelector from './NetworkSelector'
import UniBalanceContent from './UniBalanceContent'
const HeaderFrame = styled.div<{ showBackground: boolean }>`
@@ -72,6 +72,10 @@ const HeaderElement = styled.div`
display: flex;
align-items: center;
+ &:not(:first-child) {
+ margin-left: 0.5em;
+ }
+
/* addresses safari's lack of support for "gap" */
& > *:not(:first-child) {
margin-left: 8px;
@@ -300,7 +304,9 @@ export default function Header() {
-
+
+
+
{availableClaim && !showClaimPopup && (
@@ -326,6 +332,8 @@ export default function Header() {
) : null}
+
+
diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx
index 500f1fbe5a..cb63623635 100644
--- a/src/components/Menu/index.tsx
+++ b/src/components/Menu/index.tsx
@@ -62,7 +62,6 @@ const UNIbutton = styled(ButtonPrimary)`
`
const StyledMenu = styled.div`
- margin-left: 0.5rem;
display: flex;
justify-content: center;
align-items: center;
diff --git a/src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx b/src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx
deleted file mode 100644
index 67d49e28cb..0000000000
--- a/src/components/NetworkAlert/AddLiquidityNetworkAlert.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { Trans } from '@lingui/macro'
-import {
- ArbitrumWrapperBackgroundDarkMode,
- ArbitrumWrapperBackgroundLightMode,
- OptimismWrapperBackgroundDarkMode,
- OptimismWrapperBackgroundLightMode,
-} from 'components/NetworkAlert/NetworkAlert'
-import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
-import { useActiveWeb3React } from 'hooks/web3'
-import { ArrowDownCircle } from 'react-feather'
-import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
-import styled from 'styled-components/macro'
-import { ExternalLink, MEDIA_WIDTHS } from 'theme'
-import { ReadMoreLink } from './styles'
-
-const L2Icon = styled.img`
- display: none;
- height: 40px;
- margin: auto 20px auto 4px;
- width: 40px;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
- display: block;
- }
-`
-const DesktopTextBreak = styled.div`
- display: none;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
- display: block;
- }
-`
-const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
- ${({ chainId, darkMode }) =>
- [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
- ? darkMode
- ? OptimismWrapperBackgroundDarkMode
- : OptimismWrapperBackgroundLightMode
- : darkMode
- ? ArbitrumWrapperBackgroundDarkMode
- : ArbitrumWrapperBackgroundLightMode};
- border-radius: 20px;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- padding: 12px;
- position: relative;
- width: 100%;
-
- :before {
- background-image: url(${({ logoUrl }) => logoUrl});
- background-repeat: no-repeat;
- background-size: 300px;
- content: '';
- height: 300px;
- opacity: 0.1;
- position: absolute;
- transform: rotate(25deg) translate(-90px, -40px);
- width: 300px;
- z-index: -1;
- }
- @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
- flex-direction: row;
- padding: 16px 20px;
- }
-`
-const Body = styled.div`
- font-size: 12px;
- line-height: 143%;
- margin: 12px;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
- flex: 1 1 auto;
- margin: auto 0;
- }
-`
-const LinkOutCircle = styled(ArrowDownCircle)`
- transform: rotate(230deg);
- width: 20px;
- height: 20px;
- margin-left: 12px;
-`
-const LinkOutToBridge = styled(ExternalLink)`
- align-items: center;
- background-color: black;
- border-radius: 16px;
- color: white;
- display: flex;
- font-size: 14px;
- justify-content: space-between;
- margin: 0;
- max-height: 47px;
- padding: 16px 12px;
- text-decoration: none;
- width: auto;
- :hover,
- :focus,
- :active {
- background-color: black;
- }
- @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
- margin: auto 0 auto auto;
- padding: 14px 16px;
- min-width: 226px;
- }
-`
-export function AddLiquidityNetworkAlert() {
- const { chainId } = useActiveWeb3React()
- const [darkMode] = useDarkModeManager()
- const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
-
- if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
- return null
- }
- const info = CHAIN_INFO[chainId as SupportedL2ChainId]
- const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
- const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
- const readMoreLink = isOptimism
- ? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism'
- : 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum'
- return (
-
-
-
- This is an alpha release of Uniswap on the {info.label} network.
- You must bridge L1 assets to the network to use them.{' '}
-
- Read more
-
-
-
- Deposit to {info.label}
-
-
-
- )
-}
diff --git a/src/components/NetworkAlert/MinimalNetworkAlert.tsx b/src/components/NetworkAlert/MinimalNetworkAlert.tsx
deleted file mode 100644
index 4a0e7eeb1b..0000000000
--- a/src/components/NetworkAlert/MinimalNetworkAlert.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import { Trans } from '@lingui/macro'
-import {
- ArbitrumWrapperBackgroundDarkMode,
- ArbitrumWrapperBackgroundLightMode,
- OptimismWrapperBackgroundDarkMode,
- OptimismWrapperBackgroundLightMode,
-} from 'components/NetworkAlert/NetworkAlert'
-import { CHAIN_INFO, L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
-import { useActiveWeb3React } from 'hooks/web3'
-import { ArrowDownCircle } from 'react-feather'
-import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
-import styled from 'styled-components/macro'
-import { ExternalLink, MEDIA_WIDTHS } from 'theme'
-import { ReadMoreLink } from './styles'
-
-const L2Icon = styled.img`
- display: none;
- height: 40px;
- margin: auto 20px auto 4px;
- width: 40px;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
- display: block;
- }
-`
-const DesktopTextBreak = styled.div`
- display: none;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
- display: block;
- }
-`
-const Wrapper = styled.div<{ chainId: SupportedL2ChainId; darkMode: boolean; logoUrl: string }>`
- ${({ chainId, darkMode }) =>
- [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
- ? darkMode
- ? OptimismWrapperBackgroundDarkMode
- : OptimismWrapperBackgroundLightMode
- : darkMode
- ? ArbitrumWrapperBackgroundDarkMode
- : ArbitrumWrapperBackgroundLightMode};
- border-radius: 20px;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- padding: 12px;
- position: relative;
- width: 100%;
-
- :before {
- background-image: url(${({ logoUrl }) => logoUrl});
- background-repeat: no-repeat;
- background-size: 300px;
- content: '';
- height: 300px;
- opacity: 0.1;
- position: absolute;
- transform: rotate(25deg) translate(-90px, -40px);
- width: 300px;
- z-index: -1;
- }
- @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
- flex-direction: row;
- padding: 16px 20px;
- }
-`
-const Body = styled.div`
- font-size: 12px;
- line-height: 143%;
- margin: 12px;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
- flex: 1 1 auto;
- margin: auto 0;
- }
-`
-const LinkOutCircle = styled(ArrowDownCircle)`
- transform: rotate(230deg);
- width: 20px;
- height: 20px;
- margin-left: 12px;
-`
-const LinkOutToBridge = styled(ExternalLink)`
- align-items: center;
- background-color: black;
- border-radius: 16px;
- color: white;
- display: flex;
- font-size: 14px;
- justify-content: space-between;
- margin: 0;
- max-height: 47px;
- padding: 16px 8px;
- text-decoration: none;
- width: auto;
- :hover,
- :focus,
- :active {
- background-color: black;
- }
- @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
- margin: auto 0 auto auto;
- padding: 14px 17px;
- min-width: 226px;
- }
-`
-export function MinimalNetworkAlert() {
- const { chainId } = useActiveWeb3React()
- const [darkMode] = useDarkModeManager()
- const [arbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
-
- if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged) {
- return null
- }
- const info = CHAIN_INFO[chainId as SupportedL2ChainId]
- const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
- const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
- const readMoreLink = isOptimism
- ? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism'
- : 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum'
- return (
-
-
-
- This is an alpha release of Uniswap on the {info.label} network.
- You must bridge L1 assets to the network to use them.{' '}
-
- Read more
-
-
-
- Deposit to {info.label}
-
-
-
- )
-}
diff --git a/src/components/NetworkAlert/NetworkAlert.tsx b/src/components/NetworkAlert/NetworkAlert.tsx
index f88e1ec729..6b4621d986 100644
--- a/src/components/NetworkAlert/NetworkAlert.tsx
+++ b/src/components/NetworkAlert/NetworkAlert.tsx
@@ -1,27 +1,74 @@
import { Trans } from '@lingui/macro'
-import { L2_CHAIN_IDS, SupportedChainId, SupportedL2ChainId } from 'constants/chains'
+import {
+ ARBITRUM_HELP_CENTER_LINK,
+ L2_CHAIN_IDS,
+ OPTIMISM_HELP_CENTER_LINK,
+ SupportedChainId,
+ SupportedL2ChainId,
+} from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { useCallback, useState } from 'react'
import { ArrowDownCircle, X } from 'react-feather'
-import { useArbitrumAlphaAlert, useDarkModeManager } from 'state/user/hooks'
+import { useArbitrumAlphaAlert, useDarkModeManager, useOptimismAlphaAlert } from 'state/user/hooks'
import { useETHBalances } from 'state/wallet/hooks'
import styled, { css } from 'styled-components/macro'
import { ExternalLink, MEDIA_WIDTHS } from 'theme'
import { CHAIN_INFO } from '../../constants/chains'
-import { ReadMoreLink } from './styles'
+
+export const DesktopTextBreak = styled.div`
+ display: none;
+ @media screen and (min-width: ${MEDIA_WIDTHS.upToMedium}px) {
+ display: block;
+ }
+`
const L2Icon = styled.img`
- width: 40px;
- height: 40px;
+ width: 36px;
+ height: 36px;
justify-self: center;
`
+const BetaTag = styled.span<{ color: string }>`
+ align-items: center;
+ background-color: ${({ color }) => color};
+ border-radius: 6px;
+ color: ${({ theme }) => theme.white};
+ display: flex;
+ font-size: 14px;
+ height: 28px;
+ justify-content: center;
+ left: -16px;
+ position: absolute;
+ transform: rotate(-15deg);
+ top: -16px;
+ width: 60px;
+ z-index: 1;
+`
+const Body = styled.p`
+ font-size: 12px;
+ grid-column: 1 / 3;
+ line-height: 143%;
+ margin: 0;
+ @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
+ grid-column: 2 / 3;
+ }
+`
+export const Controls = styled.div<{ thin?: boolean }>`
+ align-items: center;
+ display: flex;
+ justify-content: flex-start;
+ ${({ thin }) =>
+ thin &&
+ css`
+ margin: auto 32px auto 0;
+ `}
+`
const CloseIcon = styled(X)`
cursor: pointer;
position: absolute;
top: 16px;
right: 16px;
`
-const ContentWrapper = styled.div`
+const BodyText = styled.div`
align-items: center;
display: grid;
grid-gap: 4px;
@@ -33,6 +80,37 @@ const ContentWrapper = styled.div`
grid-gap: 8px;
}
`
+const LearnMoreLink = styled(ExternalLink)<{ thin?: boolean }>`
+ align-items: center;
+ background-color: transparent;
+ border: 1px solid rgba(255, 255, 255, 0.4);
+ border-radius: 8px;
+ color: ${({ theme }) => theme.text1};
+ display: flex;
+ font-size: 16px;
+ height: 44px;
+ justify-content: space-between;
+ margin: 0 0 20px 0;
+ padding: 12px 16px;
+ text-decoration: none;
+ width: auto;
+ :hover,
+ :focus,
+ :active {
+ background-color: rgba(255, 255, 255, 0.05);
+ }
+ transition: background-color 150ms ease-in-out;
+ ${({ thin }) =>
+ thin &&
+ css`
+ font-size: 14px;
+ margin: auto;
+ width: 112px;
+ `}
+`
+const RootWrapper = styled.div`
+ position: relative;
+`
export const ArbitrumWrapperBackgroundDarkMode = css`
background: radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),
radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.3) 0%, rgba(33, 114, 229, 0.3) 100%), hsla(0, 0%, 100%, 0.1);
@@ -49,7 +127,7 @@ export const OptimismWrapperBackgroundLightMode = css`
background: radial-gradient(92% 105% at 50% 7%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.03) 100%),
radial-gradient(100% 97% at 0% 12%, rgba(235, 0, 255, 0.1) 0%, rgba(243, 19, 19, 0.1) 100%), hsla(0, 0%, 100%, 0.5);
`
-const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string }>`
+const ContentWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; logoUrl: string; thin?: boolean }>`
${({ chainId, darkMode }) =>
[SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
? darkMode
@@ -66,7 +144,13 @@ const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; l
overflow: hidden;
position: relative;
width: 100%;
-
+ ${({ thin }) =>
+ thin &&
+ css`
+ flex-direction: row;
+ max-width: max-content;
+ min-height: min-content;
+ `}
:before {
background-image: url(${({ logoUrl }) => logoUrl});
background-repeat: no-repeat;
@@ -80,36 +164,29 @@ const RootWrapper = styled.div<{ chainId: SupportedChainId; darkMode: boolean; l
z-index: -1;
}
`
-const Header = styled.h2`
+const Header = styled.h2<{ thin?: boolean }>`
font-weight: 600;
font-size: 20px;
margin: 0;
padding-right: 30px;
-`
-const Body = styled.p`
- font-size: 12px;
- grid-column: 1 / 3;
- line-height: 143%;
- margin: 0;
- @media screen and (min-width: ${MEDIA_WIDTHS.upToSmall}px) {
- grid-column: 2 / 3;
- }
+ display: ${({ thin }) => (thin ? 'none' : 'block')};
`
const LinkOutCircle = styled(ArrowDownCircle)`
+ margin-left: 12px;
transform: rotate(230deg);
width: 20px;
height: 20px;
`
-const LinkOutToBridge = styled(ExternalLink)`
+const LinkOutToBridge = styled(ExternalLink)<{ thin?: boolean }>`
align-items: center;
background-color: black;
- border-radius: 16px;
+ border-radius: 8px;
color: white;
display: flex;
font-size: 16px;
height: 44px;
justify-content: space-between;
- margin: 0 20px 20px 20px;
+ margin: 0 12px 20px 18px;
padding: 12px 16px;
text-decoration: none;
width: auto;
@@ -118,52 +195,85 @@ const LinkOutToBridge = styled(ExternalLink)`
:active {
background-color: black;
}
+ ${({ thin }) =>
+ thin &&
+ css`
+ font-size: 14px;
+ margin: auto 10px;
+ width: 168px;
+ `}
`
-export function NetworkAlert() {
+
+interface NetworkAlertProps {
+ thin?: boolean
+}
+
+export function NetworkAlert(props: NetworkAlertProps) {
const { account, chainId } = useActiveWeb3React()
const [darkMode] = useDarkModeManager()
const [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged] = useArbitrumAlphaAlert()
+ const [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged] = useOptimismAlphaAlert()
const [locallyDismissed, setLocallyDimissed] = useState(false)
const userEthBalance = useETHBalances(account ? [account] : [])?.[account ?? '']
const dismiss = useCallback(() => {
if (userEthBalance?.greaterThan(0)) {
- setArbitrumAlphaAcknowledged(true)
+ switch (chainId) {
+ case SupportedChainId.OPTIMISM:
+ setOptimismAlphaAcknowledged(true)
+ break
+ case SupportedChainId.ARBITRUM_ONE:
+ setArbitrumAlphaAcknowledged(true)
+ break
+ }
} else {
setLocallyDimissed(true)
}
- }, [setArbitrumAlphaAcknowledged, userEthBalance])
- if (!chainId || !L2_CHAIN_IDS.includes(chainId) || arbitrumAlphaAcknowledged || locallyDismissed) {
+ }, [chainId, setArbitrumAlphaAcknowledged, setOptimismAlphaAcknowledged, userEthBalance])
+
+ const onOptimismAndOptimismAcknowledged = SupportedChainId.OPTIMISM === chainId && optimismAlphaAcknowledged
+ const onArbitrumAndArbitrumAcknowledged = SupportedChainId.ARBITRUM_ONE === chainId && arbitrumAlphaAcknowledged
+ if (
+ !chainId ||
+ !L2_CHAIN_IDS.includes(chainId) ||
+ onArbitrumAndArbitrumAcknowledged ||
+ onOptimismAndOptimismAcknowledged ||
+ locallyDismissed
+ ) {
return null
}
const info = CHAIN_INFO[chainId as SupportedL2ChainId]
const isOptimism = [SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)
const depositUrl = isOptimism ? `${info.bridge}?chainId=1` : info.bridge
- const readMoreLink = isOptimism
- ? 'https://help.uniswap.org/en/articles/5392809-how-to-deposit-tokens-to-optimism'
- : 'https://help.uniswap.org/en/articles/5538618-how-to-deposit-tokens-to-arbitrum'
+ const helpCenterLink = isOptimism ? OPTIMISM_HELP_CENTER_LINK : ARBITRUM_HELP_CENTER_LINK
+ const showCloseIcon = Boolean(userEthBalance?.greaterThan(0) && !props.thin)
return (
-
-
-
-
-
- Uniswap on {info.label}
-
-
-
- This is an alpha release of Uniswap on the {info.label} network. You must bridge L1 assets to the network to
- swap them.
- {' '}
-
- Read more
-
-
+
+ Beta
+
+ {showCloseIcon && }
+
+
+
+ Uniswap on {info.label}
+
+
+
+ To starting trading on {info.label}, first bridge your assets from L1 to L2. Please treat this as a beta
+ release and learn about the risks before using {info.label}.
+
+
+
+
+
+ Deposit Assets
+
+
+
+ Learn More
+
+
-
- Deposit to {info.label}
-
-
)
}
diff --git a/src/components/NetworkAlert/styles.ts b/src/components/NetworkAlert/styles.ts
deleted file mode 100644
index 602183226f..0000000000
--- a/src/components/NetworkAlert/styles.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import styled from 'styled-components/macro'
-import { ExternalLink } from 'theme'
-
-export const ReadMoreLink = styled(ExternalLink)`
- color: ${({ theme }) => theme.text1};
- text-decoration: underline;
-`
diff --git a/src/components/OptimismDowntimeWarning/index.tsx b/src/components/OptimismDowntimeWarning/index.tsx
deleted file mode 100644
index fdcdf4a944..0000000000
--- a/src/components/OptimismDowntimeWarning/index.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Trans } from '@lingui/macro'
-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'
-
-const Root = styled.div`
- background-color: ${({ theme }) => theme.yellow3};
- border-radius: 18px;
- color: black;
- margin-top: 16px;
- padding: 16px;
- width: 100%;
- max-width: 880px;
-`
-const WarningIcon = styled(AlertOctagon)`
- margin: 0 8px 0 0;
-`
-const TitleRow = styled.div`
- align-items: center;
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- margin: 0;
- font-size: 20px;
- font-weight: 600;
- line-height: 25px;
-`
-const Body = styled.div`
- font-size: 12px;
- line-height: 15px;
- margin: 8px 0 0 0;
-`
-const ReadMoreLink = styled(ExternalLink)`
- color: black;
- text-decoration: underline;
-`
-
-export default function OptimismDowntimeWarning() {
- const { chainId } = useActiveWeb3React()
- if (!chainId || ![SupportedChainId.OPTIMISM, SupportedChainId.OPTIMISTIC_KOVAN].includes(chainId)) {
- return null
- }
-
- return (
-
-
-
- Optimism Planned Downtime
-
-
-
- Optimism expects planned downtime in the near future. Unplanned downtime may also occur. While the network is
- down, fees will not be generated and you will be unable to remove liquidity.{' '}
-
- Read more.
-
-
-
-
- )
-}
diff --git a/src/constants/chains.ts b/src/constants/chains.ts
index 0c87a51ec2..0e37fff34d 100644
--- a/src/constants/chains.ts
+++ b/src/constants/chains.ts
@@ -1,5 +1,6 @@
import arbitrumLogoUrl from 'assets/svg/arbitrum_logo.svg'
-import optimismLogoUrl from 'assets/svg/optimism_logo.svg'
+import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg'
+import ethereumLogoUrl from 'assets/images/ethereum-logo.png'
import ms from 'ms.macro'
export enum SupportedChainId {
@@ -47,12 +48,19 @@ export const L2_CHAIN_IDS = [
export type SupportedL2ChainId = typeof L2_CHAIN_IDS[number]
-interface L1ChainInfo {
+export interface L1ChainInfo {
readonly blockWaitMsBeforeWarning?: number
readonly docs: string
readonly explorer: string
readonly infoLink: string
readonly label: string
+ readonly logoUrl?: string
+ readonly rpcUrls?: string[]
+ readonly nativeCurrency: {
+ name: string // 'Goerli ETH',
+ symbol: string // 'gorETH',
+ decimals: number //18,
+ }
}
export interface L2ChainInfo extends L1ChainInfo {
readonly bridge: string
@@ -60,7 +68,7 @@ export interface L2ChainInfo extends L1ChainInfo {
readonly statusPage?: string
}
-type ChainInfo = { readonly [chainId: number]: L1ChainInfo | L2ChainInfo } & {
+export type ChainInfo = { readonly [chainId: number]: L1ChainInfo | L2ChainInfo } & {
readonly [chainId in SupportedL2ChainId]: L2ChainInfo
} &
{ readonly [chainId in SupportedL1ChainId]: L1ChainInfo }
@@ -74,6 +82,8 @@ export const CHAIN_INFO: ChainInfo = {
infoLink: 'https://info.uniswap.org/#/arbitrum',
label: 'Arbitrum',
logoUrl: arbitrumLogoUrl,
+ nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
+ rpcUrls: ['https://arb1.arbitrum.io/rpc'],
},
[SupportedChainId.ARBITRUM_RINKEBY]: {
blockWaitMsBeforeWarning: ms`10m`,
@@ -83,45 +93,55 @@ export const CHAIN_INFO: ChainInfo = {
infoLink: 'https://info.uniswap.org/#/arbitrum/',
label: 'Arbitrum Rinkeby',
logoUrl: arbitrumLogoUrl,
+ nativeCurrency: { name: 'Rinkeby ArbETH', symbol: 'rinkArbETH', decimals: 18 },
+ rpcUrls: ['https://rinkeby.arbitrum.io/rpc'],
},
[SupportedChainId.MAINNET]: {
docs: 'https://docs.uniswap.org/',
explorer: 'https://etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
- label: 'Mainnet',
+ label: 'Ethereum',
+ logoUrl: ethereumLogoUrl,
+ nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
},
[SupportedChainId.RINKEBY]: {
docs: 'https://docs.uniswap.org/',
explorer: 'https://rinkeby.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
label: 'Rinkeby',
+ nativeCurrency: { name: 'Rinkeby ETH', symbol: 'rinkETH', decimals: 18 },
},
[SupportedChainId.ROPSTEN]: {
docs: 'https://docs.uniswap.org/',
explorer: 'https://ropsten.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
label: 'Ropsten',
+ nativeCurrency: { name: 'Ropsten ETH', symbol: 'ropETH', decimals: 18 },
},
[SupportedChainId.KOVAN]: {
docs: 'https://docs.uniswap.org/',
explorer: 'https://kovan.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
label: 'Kovan',
+ nativeCurrency: { name: 'Kovan ETH', symbol: 'kovETH', decimals: 18 },
},
[SupportedChainId.GOERLI]: {
docs: 'https://docs.uniswap.org/',
explorer: 'https://goerli.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/',
label: 'Görli',
+ nativeCurrency: { name: 'Görli ETH', symbol: 'görETH', decimals: 18 },
},
[SupportedChainId.OPTIMISM]: {
blockWaitMsBeforeWarning: ms`10m`,
bridge: 'https://gateway.optimism.io/',
docs: 'https://optimism.io/',
explorer: 'https://optimistic.etherscan.io/',
- infoLink: 'https://info.uniswap.org/#/optimism/',
- label: 'Optimism',
+ infoLink: 'https://info.uniswap.org/#/optimism',
+ label: 'OΞ',
logoUrl: optimismLogoUrl,
+ nativeCurrency: { name: 'Optimistic ETH', symbol: 'ETH', decimals: 18 },
+ rpcUrls: ['https://mainnet.optimism.io'],
statusPage: 'https://optimism.io/status',
},
[SupportedChainId.OPTIMISTIC_KOVAN]: {
@@ -131,7 +151,13 @@ export const CHAIN_INFO: ChainInfo = {
explorer: 'https://optimistic.etherscan.io/',
infoLink: 'https://info.uniswap.org/#/optimism',
label: 'Optimistic Kovan',
+ rpcUrls: ['https://kovan.optimism.io'],
logoUrl: optimismLogoUrl,
+ nativeCurrency: { name: 'Optimistic kovETH', symbol: 'kovOpETH', decimals: 18 },
statusPage: 'https://optimism.io/status',
},
}
+
+export const ARBITRUM_HELP_CENTER_LINK = 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum'
+export const OPTIMISM_HELP_CENTER_LINK =
+ 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ'
diff --git a/src/pages/AddLiquidity/index.tsx b/src/pages/AddLiquidity/index.tsx
index 8ee502441e..fec3afec4b 100644
--- a/src/pages/AddLiquidity/index.tsx
+++ b/src/pages/AddLiquidity/index.tsx
@@ -28,7 +28,6 @@ import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallbac
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field, Bound } from '../../state/mint/v3/actions'
-import { AddLiquidityNetworkAlert } from 'components/NetworkAlert/AddLiquidityNetworkAlert'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserSlippageToleranceWithDefault } from '../../state/user/hooks'
import { TYPE, ExternalLink } from '../../theme'
@@ -71,7 +70,7 @@ import HoverInlineText from 'components/HoverInlineText'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import LiquidityChartRangeInput from 'components/LiquidityChartRangeInput'
import { SupportedChainId } from 'constants/chains'
-import OptimismDowntimeWarning from 'components/OptimismDowntimeWarning'
+import DowntimeWarning from 'components/DowntimeWarning'
import { CHAIN_INFO } from '../../constants/chains'
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
@@ -550,8 +549,7 @@ export default function AddLiquidity({
return (
<>
-
-
+
-
-
-
-
-
- Learn about providing liquidity ↗
-
-
- Check out our v3 LP walkthrough and migration guides.
-
-
-
-
-
-
- Top pools ↗
-
-
- Explore popular pools on Uniswap Analytics.
-
-
-
-
-
+
+
+
+
+ Learn about providing liquidity ↗
+
+
+ Check out our v3 LP walkthrough and migration guides.
+
+
+
+
+
+
+ Top pools ↗
+
+
+ Explore popular pools on Uniswap Analytics.
+
+
+
+
)
}
diff --git a/src/pages/Pool/index.tsx b/src/pages/Pool/index.tsx
index b0e8e29dfd..97bedac0e2 100644
--- a/src/pages/Pool/index.tsx
+++ b/src/pages/Pool/index.tsx
@@ -1,8 +1,10 @@
import { Trans } from '@lingui/macro'
import { ButtonGray, ButtonOutlined, ButtonPrimary } from 'components/Button'
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 PositionList from 'components/PositionList'
import { RowBetween, RowFixed } from 'components/Row'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
@@ -221,6 +223,8 @@ export default function Pool() {
+
+
diff --git a/src/state/application/actions.ts b/src/state/application/actions.ts
index 6c401226ba..fea08e33f4 100644
--- a/src/state/application/actions.ts
+++ b/src/state/application/actions.ts
@@ -18,7 +18,7 @@ export enum ApplicationModal {
DELEGATE,
VOTE,
POOL_OVERVIEW_OPTIONS,
- ARBITRUM_OPTIONS,
+ NETWORK_SELECTOR,
}
export const updateChainId = createAction<{ chainId: number | null }>('application/updateChainId')
@@ -27,4 +27,5 @@ export const setOpenModal = createAction('application/s
export const addPopup =
createAction<{ key?: string; removeAfterMs?: number | null; content: PopupContent }>('application/addPopup')
export const removePopup = createAction<{ key: string }>('application/removePopup')
+export const setImplements3085 = createAction<{ implements3085: boolean }>('application/setImplements3085')
export const setChainConnectivityWarning = createAction<{ warn: boolean }>('application/setChainConnectivityWarning')
diff --git a/src/state/application/reducer.ts b/src/state/application/reducer.ts
index d0b840c952..a56c86983d 100644
--- a/src/state/application/reducer.ts
+++ b/src/state/application/reducer.ts
@@ -2,32 +2,34 @@ import { createReducer, nanoid } from '@reduxjs/toolkit'
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
import {
addPopup,
+ ApplicationModal,
PopupContent,
removePopup,
- updateBlockNumber,
- ApplicationModal,
- setOpenModal,
- updateChainId,
setChainConnectivityWarning,
+ setImplements3085,
+ setOpenModal,
+ updateBlockNumber,
+ updateChainId,
} from './actions'
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
export interface ApplicationState {
- // used by RTK-Query to build dynamic subgraph urls
- readonly chainId: number | null
- readonly chainConnectivityWarning: boolean
readonly blockNumber: { readonly [chainId: number]: number }
- readonly popupList: PopupList
+ readonly chainConnectivityWarning: boolean
+ readonly chainId: number | null
+ readonly implements3085: boolean
readonly openModal: ApplicationModal | null
+ readonly popupList: PopupList
}
const initialState: ApplicationState = {
- chainId: null,
- chainConnectivityWarning: false,
blockNumber: {},
- popupList: [],
+ chainConnectivityWarning: false,
+ chainId: null,
+ implements3085: false,
openModal: null,
+ popupList: [],
}
export default createReducer(initialState, (builder) =>
@@ -64,6 +66,9 @@ export default createReducer(initialState, (builder) =>
}
})
})
+ .addCase(setImplements3085, (state, { payload: { implements3085 } }) => {
+ state.implements3085 = implements3085
+ })
.addCase(setChainConnectivityWarning, (state, { payload: { warn } }) => {
state.chainConnectivityWarning = warn
})
diff --git a/src/state/application/updater.ts b/src/state/application/updater.ts
index 1bd467cc44..b41f818d0d 100644
--- a/src/state/application/updater.ts
+++ b/src/state/application/updater.ts
@@ -7,7 +7,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { api, CHAIN_TAG } from 'state/data/enhanced'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { supportedChainId } from 'utils/supportedChainId'
-import { setChainConnectivityWarning, updateBlockNumber, updateChainId } from './actions'
+import { switchToNetwork } from 'utils/switchToNetwork'
+import { setChainConnectivityWarning, setImplements3085, updateBlockNumber, updateChainId } from './actions'
import { useBlockNumber } from './hooks'
function useQueryCacheInvalidator() {
@@ -61,7 +62,7 @@ function useBlockWarningTimer() {
}
export default function Updater(): null {
- const { library, chainId } = useActiveWeb3React()
+ const { account, chainId, library } = useActiveWeb3React()
const dispatch = useAppDispatch()
const windowVisible = useIsWindowVisible()
@@ -116,5 +117,14 @@ export default function Updater(): null {
)
}, [dispatch, debouncedState.chainId])
+ useEffect(() => {
+ if (!account || !library?.provider?.request || !library?.provider?.isMetaMask) {
+ return
+ }
+ switchToNetwork({ library })
+ .then((x) => x ?? dispatch(setImplements3085({ implements3085: true })))
+ .catch(() => dispatch(setImplements3085({ implements3085: false })))
+ }, [account, chainId, dispatch, library])
+
return null
}
diff --git a/src/state/user/actions.ts b/src/state/user/actions.ts
index 4f17d70b26..bc6d10f162 100644
--- a/src/state/user/actions.ts
+++ b/src/state/user/actions.ts
@@ -18,6 +18,9 @@ export const updateMatchesDarkMode = createAction<{ matchesDarkMode: boolean }>(
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 updateUserExpertMode = createAction<{ userExpertMode: boolean }>('user/updateUserExpertMode')
export const updateUserLocale = createAction<{ userLocale: SupportedLocale }>('user/updateUserLocale')
diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx
index 67093c182a..c5b6b705dc 100644
--- a/src/state/user/hooks.tsx
+++ b/src/state/user/hooks.tsx
@@ -20,6 +20,7 @@ import {
SerializedToken,
updateArbitrumAlphaAcknowledged,
updateHideClosedPositions,
+ updateOptimismAlphaAcknowledged,
updateUserDarkMode,
updateUserDeadline,
updateUserExpertMode,
@@ -353,3 +354,13 @@ export function useArbitrumAlphaAlert(): [boolean, (arbitrumAlphaAcknowledged: b
return [arbitrumAlphaAcknowledged, setArbitrumAlphaAcknowledged]
}
+
+export function useOptimismAlphaAlert(): [boolean, (optimismAlphaAcknowledged: boolean) => void] {
+ const dispatch = useAppDispatch()
+ const optimismAlphaAcknowledged = useAppSelector(({ user }) => user.optimismAlphaAcknowledged)
+ const setOptimismAlphaAcknowledged = (optimismAlphaAcknowledged: boolean) => {
+ dispatch(updateOptimismAlphaAcknowledged({ optimismAlphaAcknowledged }))
+ }
+
+ return [optimismAlphaAcknowledged, setOptimismAlphaAcknowledged]
+}
diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts
index 77f3160f36..cbd95824ba 100644
--- a/src/state/user/reducer.ts
+++ b/src/state/user/reducer.ts
@@ -12,11 +12,12 @@ import {
updateArbitrumAlphaAcknowledged,
updateHideClosedPositions,
updateMatchesDarkMode,
+ updateOptimismAlphaAcknowledged,
+ updateUserClientSideRouter,
updateUserDarkMode,
updateUserDeadline,
updateUserExpertMode,
updateUserLocale,
- updateUserClientSideRouter,
updateUserSlippageTolerance,
} from './actions'
@@ -28,9 +29,10 @@ export interface UserState {
// the timestamp of the last updateVersion action
lastUpdateVersionTimestamp?: number
- userDarkMode: boolean | null // the user's choice for dark mode or light mode
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
userExpertMode: boolean
@@ -70,8 +72,9 @@ function pairKey(token0Address: string, token1Address: string) {
export const initialState: UserState = {
arbitrumAlphaAcknowledged: false,
- userDarkMode: null,
matchesDarkMode: false,
+ optimismAlphaAcknowledged: false,
+ userDarkMode: null,
userExpertMode: false,
userLocale: null,
userClientSideRouter: false,
@@ -131,6 +134,9 @@ export default createReducer(initialState, (builder) =>
.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()
diff --git a/src/theme/index.tsx b/src/theme/index.tsx
index 282268a22b..e7792bb7fb 100644
--- a/src/theme/index.tsx
+++ b/src/theme/index.tsx
@@ -37,6 +37,7 @@ const black = '#000000'
function colors(darkMode: boolean): Colors {
return {
+ darkMode,
// base
white,
black,
diff --git a/src/theme/styled.d.ts b/src/theme/styled.d.ts
index 90194e7ace..96dc94f451 100644
--- a/src/theme/styled.d.ts
+++ b/src/theme/styled.d.ts
@@ -2,6 +2,8 @@ import { FlattenSimpleInterpolation, ThemedCssFunction } from 'styled-components
export type Color = string
export interface Colors {
+ darkMode: boolean
+
// base
white: Color
black: Color
diff --git a/src/utils/addNetwork.ts b/src/utils/addNetwork.ts
new file mode 100644
index 0000000000..52615b3de4
--- /dev/null
+++ b/src/utils/addNetwork.ts
@@ -0,0 +1,34 @@
+import { Web3Provider } from '@ethersproject/providers'
+import { L1ChainInfo, L2ChainInfo, SupportedChainId } from 'constants/chains'
+import { BigNumber, utils } from 'ethers'
+
+interface AddNetworkArguments {
+ library: Web3Provider
+ chainId: SupportedChainId
+ info: L1ChainInfo | L2ChainInfo
+}
+
+// provider.request returns Promise, 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 {
+ if (!library?.provider?.request) {
+ return
+ }
+ const formattedChainId = utils.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)
+ }
+}
diff --git a/src/utils/switchToNetwork.ts b/src/utils/switchToNetwork.ts
index a6119370e4..5ec95e3bb9 100644
--- a/src/utils/switchToNetwork.ts
+++ b/src/utils/switchToNetwork.ts
@@ -1,10 +1,11 @@
-import { SupportedChainId } from 'constants/chains'
-import { BigNumber, utils } from 'ethers'
import { Web3Provider } from '@ethersproject/providers'
+import { CHAIN_INFO, SupportedChainId } from 'constants/chains'
+import { BigNumber, utils } from 'ethers'
+import { addNetwork } from './addNetwork'
interface SwitchNetworkArguments {
library: Web3Provider
- chainId: SupportedChainId
+ chainId?: SupportedChainId
}
// provider.request returns Promise, but wallet_switchEthereumChain must return null or throw
@@ -13,9 +14,27 @@ export async function switchToNetwork({ library, chainId }: SwitchNetworkArgumen
if (!library?.provider?.request) {
return
}
+ if (!chainId && library?.getNetwork) {
+ ;({ chainId } = await library.getNetwork())
+ }
const formattedChainId = utils.hexStripZeros(BigNumber.from(chainId).toHexString())
- return library?.provider.request({
- method: 'wallet_switchEthereumChain',
- params: [{ chainId: formattedChainId }],
- })
+ try {
+ 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) {
+ const info = CHAIN_INFO[chainId]
+
+ // metamask (only known implementer) automatically switches after a network is added
+ // the second call is done here because that behavior is not a part of the spec and cannot be relied upon in the future
+ // metamask's behavior when switching to the current network is just to return null (a no-op)
+ await addNetwork({ library, chainId, info })
+ await switchToNetwork({ library, chainId })
+ } else {
+ throw error
+ }
+ }
}