diff --git a/.env b/.env
index 78db6017c0..1992e32241 100644
--- a/.env
+++ b/.env
@@ -4,6 +4,8 @@ REACT_APP_AMPLITUDE_PROXY_URL="https://api.uniswap.org/v1/amplitude-proxy"
REACT_APP_AWS_API_REGION="us-east-2"
REACT_APP_AWS_API_ENDPOINT="https://beta.api.uniswap.org/v1/graphql"
REACT_APP_BNB_RPC_URL="https://rough-sleek-hill.bsc.quiknode.pro/413cc98cbc776cda8fdf1d0f47003583ff73d9bf"
+REACT_APP_BASE_GOERLI_RPC_URL="https://wiser-compatible-mansion.base-goerli.quiknode.pro/5874f36248e17020a1006149e7f68c63967e1f45/"
+REACT_APP_BASE_MAINNET_RPC_URL="https://cool-white-diagram.base-mainnet.quiknode.pro/d8f036f35dfab2c68f32dfa822cd971e7a25a117/"
REACT_APP_INFURA_KEY="4bf032f2d38a4ed6bb975b80d6340847"
REACT_APP_MOONPAY_API="https://api.moonpay.com"
REACT_APP_MOONPAY_LINK="https://us-central1-uniswap-mobile.cloudfunctions.net/signMoonpayLinkV2?platform=web&env=staging"
diff --git a/package.json b/package.json
index 937238ae56..1d51f3e2d5 100644
--- a/package.json
+++ b/package.json
@@ -181,7 +181,7 @@
"@uniswap/redux-multicall": "^1.1.8",
"@uniswap/router-sdk": "^1.6.0",
"@uniswap/sdk-core": "^4.0.3",
- "@uniswap/smart-order-router": "^3.13.7",
+ "@uniswap/smart-order-router": "^3.15.0",
"@uniswap/token-lists": "^1.0.0-beta.33",
"@uniswap/uniswapx-sdk": "^1.1.0",
"@uniswap/universal-router-sdk": "^1.5.6",
diff --git a/src/assets/svg/base_logo.svg b/src/assets/svg/base_logo.svg
new file mode 100644
index 0000000000..d0303fb153
--- /dev/null
+++ b/src/assets/svg/base_logo.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/assets/svg/base_square_logo.svg b/src/assets/svg/base_square_logo.svg
new file mode 100644
index 0000000000..32371c259b
--- /dev/null
+++ b/src/assets/svg/base_square_logo.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts b/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts
index d2866fcc49..2a42682b07 100644
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts
+++ b/src/components/AccountDrawer/MiniPortfolio/Pools/hooks.ts
@@ -11,6 +11,7 @@ import { useWeb3React } from '@web3-react/core'
import { isSupportedChain } from 'constants/chains'
import { RPC_PROVIDERS } from 'constants/providers'
import { BaseContract } from 'ethers/lib/ethers'
+import { useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import { ContractInput, useUniswapPricesQuery } from 'graphql/data/__generated__/types-and-hooks'
import { toContractInput } from 'graphql/data/util'
import useStablecoinPrice from 'hooks/useStablecoinPrice'
@@ -30,13 +31,14 @@ function useContractMultichain(
chainIds?: ChainId[]
): ContractMap {
const { chainId: walletChainId, provider: walletProvider } = useWeb3React()
+ const baseEnabledChains = useBaseEnabledChains()
return useMemo(() => {
const relevantChains =
chainIds ??
Object.keys(addressMap)
.map((chainId) => parseInt(chainId))
- .filter(isSupportedChain)
+ .filter((chainId) => isSupportedChain(chainId, baseEnabledChains))
return relevantChains.reduce((acc: ContractMap, chainId) => {
const provider =
@@ -50,7 +52,7 @@ function useContractMultichain(
}
return acc
}, {})
- }, [ABI, addressMap, chainIds, walletChainId, walletProvider])
+ }, [ABI, addressMap, baseEnabledChains, chainIds, walletChainId, walletProvider])
}
export function useV3ManagerContracts(chainIds: ChainId[]): ContractMap {
diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx
index bd85f9c861..e433848fc5 100644
--- a/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx
+++ b/src/components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions.tsx
@@ -47,6 +47,7 @@ const DEFAULT_CHAINS = [
ChainId.CELO,
ChainId.BNB,
ChainId.AVALANCHE,
+ ChainId.BASE,
]
type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean }
diff --git a/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx b/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx
index 707e0501ab..a556e0ce7e 100644
--- a/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx
+++ b/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx
@@ -5,6 +5,7 @@ import { MissingImageLogo } from 'components/Logo/AssetLogo'
import CurrencyLogo from 'components/Logo/CurrencyLogo'
import { Unicon } from 'components/Unicon'
import { getChainInfo } from 'constants/chainInfo'
+import { useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import useTokenLogoSource from 'hooks/useAssetLogoSource'
import useENSAvatar from 'hooks/useENSAvatar'
import React from 'react'
@@ -98,7 +99,10 @@ export function PortfolioLogo({
size = '40px',
style,
}: MultiLogoProps) {
- const { squareLogoUrl, logoUrl } = getChainInfo(chainId)
+ const baseEnabledChains = useBaseEnabledChains()
+ const chainInfo = getChainInfo(chainId, baseEnabledChains)
+ const squareLogoUrl = chainInfo?.squareLogoUrl
+ const logoUrl = chainInfo?.logoUrl
const chainLogo = squareLogoUrl ?? logoUrl
const { avatar, loading } = useENSAvatar(accountAddress, false)
const theme = useTheme()
diff --git a/src/components/AccountDrawer/index.tsx b/src/components/AccountDrawer/index.tsx
index dc402ae102..2d75578340 100644
--- a/src/components/AccountDrawer/index.tsx
+++ b/src/components/AccountDrawer/index.tsx
@@ -213,14 +213,14 @@ function AccountDrawer() {
}
},
// reset the yPosition when the user stops dragging
- onDragEnd: (state) => {
+ onDragEnd: () => {
setYPosition(0)
if (scrollRef.current) {
scrollRef.current.style.overflowY = 'auto'
}
},
// set dragStartTop to true if the user starts dragging from the top of the drawer
- onDragStart: (state) => {
+ onDragStart: () => {
if (!scrollRef.current?.scrollTop || scrollRef.current?.scrollTop < 30) {
setDragStartTop(true)
} else {
diff --git a/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/src/components/FeatureFlagModal/FeatureFlagModal.tsx
index 82d9c0f758..34d8608c6f 100644
--- a/src/components/FeatureFlagModal/FeatureFlagModal.tsx
+++ b/src/components/FeatureFlagModal/FeatureFlagModal.tsx
@@ -1,4 +1,5 @@
import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags'
+import { useBaseEnabledFlag } from 'featureFlags/flags/baseEnabled'
import { useForceUniswapXOnFlag } from 'featureFlags/flags/forceUniswapXOn'
import { DetailsV2Variant, useDetailsV2Flag } from 'featureFlags/flags/nftDetails'
import { useRoutingAPIForPriceFlag } from 'featureFlags/flags/priceRoutingApi'
@@ -235,6 +236,12 @@ export default function FeatureFlagModal() {
featureFlag={FeatureFlag.routingAPIPrice}
label="Use the routing-api v2 for price fetches"
/>
+
{
const theme = useTheme()
+ const baseEnabled = useBaseEnabled()
const showTestnets = useAtomValue(showTestnetsAtom)
const walletSupportsChain = useWalletSupportedChains()
const [supportedChains, unsupportedChains] = useMemo(() => {
- const { supported, unsupported } = NETWORK_SELECTOR_CHAINS.filter(
- (chain: number) => showTestnets || !TESTNET_CHAIN_IDS.includes(chain)
- )
+ const { supported, unsupported } = NETWORK_SELECTOR_CHAINS.filter((chain: number) => {
+ if (chain === ChainId.BASE) {
+ return baseEnabled
+ }
+ if (chain === ChainId.BASE_GOERLI) {
+ return showTestnets && baseEnabled
+ }
+ return showTestnets || !TESTNET_CHAIN_IDS.includes(chain)
+ })
.sort((a, b) => getChainPriority(a) - getChainPriority(b))
.reduce(
(acc, chain) => {
@@ -79,13 +87,14 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
{ supported: [], unsupported: [] } as Record
)
return [supported, unsupported]
- }, [showTestnets, walletSupportsChain])
+ }, [baseEnabled, showTestnets, walletSupportsChain])
const ref = useRef(null)
const modalRef = useRef(null)
useOnClickOutside(ref, () => setIsOpen(false), [modalRef])
- const info = chainId ? getChainInfo(chainId) : undefined
+ const baseEnabledChains = useBaseEnabledChains()
+ const info = getChainInfo(chainId, baseEnabledChains)
const selectChain = useSelectChain()
useSyncChainQuery()
diff --git a/src/components/NavBar/ChainSelectorRow.tsx b/src/components/NavBar/ChainSelectorRow.tsx
index 8a2a6aa849..6c9c2a9d72 100644
--- a/src/components/NavBar/ChainSelectorRow.tsx
+++ b/src/components/NavBar/ChainSelectorRow.tsx
@@ -3,6 +3,7 @@ import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import Loader from 'components/Icons/LoadingSpinner'
import { getChainInfo } from 'constants/chainInfo'
+import { useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import { CheckMarkIcon } from 'nft/components/icons'
import styled, { useTheme } from 'styled-components/macro'
@@ -70,7 +71,10 @@ interface ChainSelectorRowProps {
export default function ChainSelectorRow({ disabled, targetChain, onSelectChain, isPending }: ChainSelectorRowProps) {
const { chainId } = useWeb3React()
const active = chainId === targetChain
- const { label, logoUrl } = getChainInfo(targetChain)
+ const baseEnabledChains = useBaseEnabledChains()
+ const chainInfo = getChainInfo(targetChain, baseEnabledChains)
+ const label = chainInfo?.label
+ const logoUrl = chainInfo?.logoUrl
const theme = useTheme()
@@ -81,8 +85,8 @@ export default function ChainSelectorRow({ disabled, targetChain, onSelectChain,
if (!disabled) onSelectChain(targetChain)
}}
>
-
-
+ {logoUrl && }
+ {label && }
{disabled && (
Unsupported by your wallet
diff --git a/src/components/NavBar/SearchBarDropdown.tsx b/src/components/NavBar/SearchBarDropdown.tsx
index 9c0c68b968..82c8e0472b 100644
--- a/src/components/NavBar/SearchBarDropdown.tsx
+++ b/src/components/NavBar/SearchBarDropdown.tsx
@@ -362,6 +362,9 @@ function ComingSoonText({ chainId }: { chainId: ChainId }) {
return Coming soon: search and explore tokens on BNB Chain
case ChainId.AVALANCHE:
return Coming soon: search and explore tokens on Avalanche Chain
+ case ChainId.BASE:
+ case ChainId.BASE_GOERLI:
+ return Coming soon: search and explore tokens on Base
default:
return null
}
diff --git a/src/components/NetworkAlert/NetworkAlert.tsx b/src/components/NetworkAlert/NetworkAlert.tsx
index 23a645c1e5..d5eab38a49 100644
--- a/src/components/NetworkAlert/NetworkAlert.tsx
+++ b/src/components/NetworkAlert/NetworkAlert.tsx
@@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro'
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
import { getChainInfo } from 'constants/chainInfo'
+import { useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import { ArrowUpRight } from 'react-feather'
import styled from 'styled-components/macro'
import { ExternalLink, HideSmall } from 'theme'
@@ -39,6 +40,8 @@ const SHOULD_SHOW_ALERT = {
[ChainId.CELO_ALFAJORES]: true,
[ChainId.BNB]: true,
[ChainId.AVALANCHE]: true,
+ [ChainId.BASE]: true,
+ [ChainId.BASE_GOERLI]: true,
}
type NetworkAlertChains = keyof typeof SHOULD_SHOW_ALERT
@@ -67,6 +70,10 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.05) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(75% 75% at 0% 0%, rgba(150, 190, 220, 0.05) 0%, rgba(33, 114, 229, 0.1) 100%), hsla(0, 0%, 100%, 0.05)',
[ChainId.AVALANCHE]:
'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.01) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.01) 0%, rgba(235, 0, 255, 0.01) 96%)',
+ [ChainId.BASE]:
+ 'radial-gradient(100% 100% at 50% 0%, rgba(10, 41, 75, 0.7) 0%, rgba(0, 82, 255, .1) 40%, rgba(0, 82, 255, 0) 100%), rgb(13, 14, 14);',
+ [ChainId.BASE_GOERLI]:
+ 'radial-gradient(100% 100% at 50% 0%, rgba(10, 41, 75, 0.7) 0%, rgba(0, 82, 255, .1) 40%, rgba(0, 82, 255, 0) 100%), rgb(13, 14, 14);',
},
light: {
[ChainId.POLYGON]:
@@ -89,6 +96,10 @@ const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: {
'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.1) 0%, rgba(219, 255, 0, 0) 100%),radial-gradient(circle at top left, hsla(206, 50%, 75%, 0.01), hsla(215, 79%, 51%, 0.12)), hsla(0, 0%, 100%, 0.1)',
[ChainId.AVALANCHE]:
'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.1)',
+ [ChainId.BASE]:
+ 'radial-gradient(100% 100% at 50% 0%, rgba(0, 82, 255, 0.20) 0%, rgba(0, 82, 255, 0.08) 40.0%, rgba(252, 255, 82, 0.00) 100%), rgb(255, 255, 255)',
+ [ChainId.BASE_GOERLI]:
+ 'radial-gradient(100% 100% at 50% 0%, rgba(0, 82, 255, 0.20) 0%, rgba(0, 82, 255, 0.08) 40.0%, rgba(252, 255, 82, 0.00) 100%), rgb(255, 255, 255)',
},
}
@@ -149,6 +160,8 @@ const TEXT_COLORS: { [chainId in NetworkAlertChains]: string } = {
[ChainId.BNB]: colors.gold400,
[ChainId.ARBITRUM_GOERLI]: '#0490ed',
[ChainId.AVALANCHE]: '#ff3856',
+ [ChainId.BASE]: colors.networkBase,
+ [ChainId.BASE_GOERLI]: colors.networkBase,
}
function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertChains {
@@ -158,12 +171,14 @@ function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertCh
export function NetworkAlert() {
const { chainId } = useWeb3React()
const [darkMode] = useDarkModeManager()
+ const baseEnabledChains = useBaseEnabledChains()
if (!shouldShowAlert(chainId)) {
return null
}
- const chainInfo = getChainInfo(chainId)
+ const chainInfo = getChainInfo(chainId, baseEnabledChains)
+
if (!chainInfo) return null
const { label, logoUrl, bridge } = chainInfo
diff --git a/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx b/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx
index 220fb5642b..13ab6e190b 100644
--- a/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx
+++ b/src/components/Tokens/TokenDetails/InvalidTokenDetails.tsx
@@ -53,6 +53,8 @@ export default function InvalidTokenDetails({
// if the token's address is valid and the chains match, it's a non-existant token
const isNonExistantToken = !isInvalidAddress && pageChainId === chainId
+ const connectedChainLabel = chainId ? getChainInfo(chainId)?.label : undefined
+
return (
@@ -69,9 +71,11 @@ export default function InvalidTokenDetails({
>
) : (
<>
-
- This token doesn't exist on {getChainInfo(chainId)?.label}
-
+ {connectedChainLabel && (
+
+ This token doesn't exist on {connectedChainLabel}
+
+ )}
selectChain(pageChainId)}>
Switch to {getChainInfo(pageChainId).label}
diff --git a/src/components/Tokens/constants.ts b/src/components/Tokens/constants.ts
index e549d5097b..4ce71cf7f3 100644
--- a/src/components/Tokens/constants.ts
+++ b/src/components/Tokens/constants.ts
@@ -8,4 +8,4 @@ export const SMALL_MEDIA_BREAKPOINT = '540px'
export const MOBILE_MEDIA_BREAKPOINT = '420px'
// includes chains that the backend does not current source off-chain metadata for
-export const UNSUPPORTED_METADATA_CHAINS = [ChainId.BNB, ChainId.AVALANCHE]
+export const UNSUPPORTED_METADATA_CHAINS = [ChainId.BNB, ChainId.AVALANCHE, ChainId.BASE_GOERLI, ChainId.BASE]
diff --git a/src/components/Web3Status/index.tsx b/src/components/Web3Status/index.tsx
index 62fe105727..9d904364a4 100644
--- a/src/components/Web3Status/index.tsx
+++ b/src/components/Web3Status/index.tsx
@@ -21,7 +21,6 @@ import { colors } from 'theme/colors'
import { flexRowNoWrap } from 'theme/styles'
import { shortenAddress } from 'utils'
-import { TransactionDetails } from '../../state/transactions/types'
import { ButtonSecondary } from '../Button'
import StatusIcon from '../Identicon/StatusIcon'
import { RowBetween } from '../Row'
@@ -115,11 +114,6 @@ const Text = styled.p`
font-weight: 500;
`
-// we want the latest one to come first, so return negative if a is after b
-function newTransactionsFirst(a: TransactionDetails, b: TransactionDetails) {
- return b.addedTime - a.addedTime
-}
-
const StyledConnectButton = styled.button`
background-color: transparent;
border: none;
diff --git a/src/constants/chainInfo.ts b/src/constants/chainInfo.ts
index 45a06bfabb..e67ae7d33b 100644
--- a/src/constants/chainInfo.ts
+++ b/src/constants/chainInfo.ts
@@ -5,6 +5,8 @@ import polygonCircleLogoUrl from 'assets/images/polygonCircle.png'
import { default as arbitrumCircleLogoUrl, default as arbitrumLogoUrl } from 'assets/svg/arbitrum_logo.svg'
import avaxLogo from 'assets/svg/avax_logo.svg'
import avaxSquareLogo from 'assets/svg/avax_square_logo.svg'
+import baseLogo from 'assets/svg/base_logo.svg'
+import baseSquareLogo from 'assets/svg/base_square_logo.svg'
import bnbSquareLogoUrl from 'assets/svg/bnb_square_logo.svg'
import bnbLogo from 'assets/svg/bnb-logo.svg'
import celoLogo from 'assets/svg/celo_logo.svg'
@@ -17,7 +19,7 @@ import ms from 'ms.macro'
import { darkTheme } from 'theme/colors'
import { SupportedL1ChainId, SupportedL2ChainId } from './chains'
-import { ARBITRUM_LIST, AVALANCHE_LIST, CELO_LIST, OPTIMISM_LIST, PLASMA_BNB_LIST } from './lists'
+import { ARBITRUM_LIST, AVALANCHE_LIST, BASE_LIST, CELO_LIST, OPTIMISM_LIST, PLASMA_BNB_LIST } from './lists'
export const AVERAGE_L1_BLOCK_TIME = ms`12s`
@@ -241,13 +243,55 @@ const CHAIN_INFO: ChainInfoMap = {
color: darkTheme.chain_43114,
backgroundColor: darkTheme.chain_43114_background,
},
+ [ChainId.BASE]: {
+ networkType: NetworkType.L2,
+ blockWaitMsBeforeWarning: ms`25m`,
+ bridge: 'https://bridge.base.org/deposit',
+ defaultListUrl: BASE_LIST,
+ docs: 'https://docs.base.org',
+ explorer: 'https://basescan.org/',
+ infoLink: 'https://info.uniswap.org/#/base/',
+ label: 'Base',
+ logoUrl: baseLogo,
+ statusPage: 'https://status.base.org/',
+ circleLogoUrl: baseLogo,
+ squareLogoUrl: baseSquareLogo,
+ nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
+ color: darkTheme.chain_84531,
+ },
+ [ChainId.BASE_GOERLI]: {
+ networkType: NetworkType.L2,
+ blockWaitMsBeforeWarning: ms`25m`,
+ bridge: 'https://goerli-bridge.base.org/deposit',
+ defaultListUrl: BASE_LIST,
+ docs: 'https://docs.base.org',
+ explorer: 'https://goerli.basescan.org/',
+ infoLink: 'https://info.uniswap.org/#/base/', // base testnet not supported
+ label: 'Base Goerli',
+ logoUrl: baseLogo,
+ statusPage: 'https://status.base.org/',
+ circleLogoUrl: baseLogo,
+ squareLogoUrl: baseSquareLogo,
+ nativeCurrency: { name: 'Base Goerli Ether', symbol: 'ETH', decimals: 18 },
+ color: darkTheme.chain_84531,
+ },
} as const
-export function getChainInfo(chainId: SupportedL1ChainId): L1ChainInfo
-export function getChainInfo(chainId: SupportedL2ChainId): L2ChainInfo
-export function getChainInfo(chainId: ChainId): L1ChainInfo | L2ChainInfo
export function getChainInfo(
- chainId: ChainId | SupportedL1ChainId | SupportedL2ChainId | number | undefined
+ chainId: SupportedL1ChainId,
+ featureFlags?: Record
+): L1ChainInfo
+export function getChainInfo(
+ chainId: SupportedL2ChainId,
+ featureFlags?: Record
+): L2ChainInfo
+export function getChainInfo(
+ chainId: ChainId,
+ featureFlags?: Record
+): L1ChainInfo | L2ChainInfo
+export function getChainInfo(
+ chainId: ChainId | SupportedL1ChainId | SupportedL2ChainId | number | undefined,
+ featureFlags?: Record
): L1ChainInfo | L2ChainInfo | undefined
/**
@@ -258,7 +302,13 @@ export function getChainInfo(
* SupportedL1ChainId -> returns L1ChainInfo
* SupportedL2ChainId -> returns L2ChainInfo
*/
-export function getChainInfo(chainId: any): any {
+export function getChainInfo(
+ chainId: any,
+ featureFlags?: Record
+): any {
+ if (featureFlags && chainId in featureFlags) {
+ return featureFlags[chainId] ? CHAIN_INFO[chainId] : undefined
+ }
if (chainId) {
return CHAIN_INFO[chainId] ?? undefined
}
@@ -266,6 +316,6 @@ export function getChainInfo(chainId: any): any {
}
const MAINNET_INFO = CHAIN_INFO[ChainId.MAINNET]
-export function getChainInfoOrDefault(chainId: number | undefined) {
- return getChainInfo(chainId) ?? MAINNET_INFO
+export function getChainInfoOrDefault(chainId: number | undefined, featureFlags?: Record) {
+ return getChainInfo(chainId, featureFlags) ?? MAINNET_INFO
}
diff --git a/src/constants/chains.ts b/src/constants/chains.ts
index 4f1a49fa34..e3ee0acc4b 100644
--- a/src/constants/chains.ts
+++ b/src/constants/chains.ts
@@ -20,13 +20,27 @@ export const CHAIN_IDS_TO_NAMES = {
[ChainId.BASE_GOERLI]: 'base_goerli',
} as const
-const NOT_YET_UX_SUPPORTED_CHAIN_IDS = [ChainId.BASE, ChainId.BASE_GOERLI] as const
+// Include ChainIds in this array if they are not supported by the UX yet, but are already in the SDK.
+const NOT_YET_UX_SUPPORTED_CHAIN_IDS: number[] = []
-export function isSupportedChain(chainId: number | null | undefined | ChainId): chainId is SupportedChainsType {
+export function isSupportedChain(
+ chainId: number | null | undefined | ChainId,
+ featureFlags?: Record
+): chainId is SupportedChainsType {
+ if (featureFlags && chainId && chainId in featureFlags) {
+ return featureFlags[chainId]
+ }
return !!chainId && SUPPORTED_CHAINS.indexOf(chainId) !== -1 && NOT_YET_UX_SUPPORTED_CHAIN_IDS.indexOf(chainId) === -1
}
-export function asSupportedChain(chainId: number | null | undefined | ChainId): SupportedChainsType | undefined {
+export function asSupportedChain(
+ chainId: number | null | undefined | ChainId,
+ featureFlags?: Record
+): SupportedChainsType | undefined {
+ if (!chainId) return undefined
+ if (featureFlags && chainId in featureFlags && !featureFlags[chainId]) {
+ return undefined
+ }
return isSupportedChain(chainId) ? chainId : undefined
}
@@ -38,6 +52,7 @@ export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [
ChainId.ARBITRUM_ONE,
ChainId.BNB,
ChainId.AVALANCHE,
+ ChainId.BASE,
] as const
/**
@@ -52,6 +67,7 @@ export const TESTNET_CHAIN_IDS = [
ChainId.ARBITRUM_GOERLI,
ChainId.OPTIMISM_GOERLI,
ChainId.CELO_ALFAJORES,
+ ChainId.BASE_GOERLI,
] as const
/**
@@ -80,6 +96,8 @@ export const L2_CHAIN_IDS = [
ChainId.ARBITRUM_GOERLI,
ChainId.OPTIMISM,
ChainId.OPTIMISM_GOERLI,
+ ChainId.BASE,
+ ChainId.BASE_GOERLI,
] as const
export type SupportedL2ChainId = (typeof L2_CHAIN_IDS)[number]
diff --git a/src/constants/lists.ts b/src/constants/lists.ts
index 5b3d2a380d..385dbcdb14 100644
--- a/src/constants/lists.ts
+++ b/src/constants/lists.ts
@@ -24,6 +24,8 @@ export const CELO_LIST = 'https://celo-org.github.io/celo-token-list/celo.tokenl
export const PLASMA_BNB_LIST = 'https://raw.githubusercontent.com/plasmadlt/plasma-finance-token-list/master/bnb.json'
export const AVALANCHE_LIST =
'https://raw.githubusercontent.com/ava-labs/avalanche-bridge-resources/main/token_list.json'
+export const BASE_LIST =
+ 'https://raw.githubusercontent.com/ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json'
export const UNSUPPORTED_LIST_URLS: string[] = [BA_LIST, UNI_UNSUPPORTED_LIST]
@@ -50,6 +52,7 @@ export const DEFAULT_INACTIVE_LIST_URLS: string[] = [
CELO_LIST,
PLASMA_BNB_LIST,
AVALANCHE_LIST,
+ BASE_LIST,
...UNSUPPORTED_LIST_URLS,
]
diff --git a/src/constants/networks.ts b/src/constants/networks.ts
index 06b998a611..780b95bd48 100644
--- a/src/constants/networks.ts
+++ b/src/constants/networks.ts
@@ -4,10 +4,18 @@ const INFURA_KEY = process.env.REACT_APP_INFURA_KEY
if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
-const QUICKNODE_RPC_URL = process.env.REACT_APP_BNB_RPC_URL
-if (typeof QUICKNODE_RPC_URL === 'undefined') {
+const QUICKNODE_BNB_RPC_URL = process.env.REACT_APP_BNB_RPC_URL
+if (typeof QUICKNODE_BNB_RPC_URL === 'undefined') {
throw new Error(`REACT_APP_BNB_RPC_URL must be a defined environment variable`)
}
+const QUICKNODE_BASE_GOERLI_RPC_URL = process.env.REACT_APP_BASE_GOERLI_RPC_URL
+if (typeof QUICKNODE_BASE_GOERLI_RPC_URL === 'undefined') {
+ throw new Error(`REACT_APP_BASE_GOERLI_RPC_URL must be a defined environment variable`)
+}
+const QUICKNODE_BASE_RPC_URL = process.env.REACT_APP_BASE_MAINNET_RPC_URL
+if (typeof QUICKNODE_BASE_RPC_URL === 'undefined') {
+ throw new Error(`REACT_APP_BASE_MAINNET_RPC_URL must be a defined environment variable`)
+}
/**
* Fallback JSON-RPC endpoints.
@@ -107,11 +115,19 @@ export const FALLBACK_URLS = {
],
[ChainId.BASE]: [
// "Safe" URLs
- 'https://developer-access-mainnet.base.org',
+ 'https://mainnet.base.org',
+ // "Unsafe" URLs
+ QUICKNODE_BASE_RPC_URL,
+ 'https://base-mainnet.blastapi.io/b5a802d8-151d-4443-90a7-699108dc4e01',
+ 'https://svc.blockdaemon.com/base/mainnet/native?apiKey=zpka_1334e7c450464d06b6e33a972a7a4e57_75320f43',
],
[ChainId.BASE_GOERLI]: [
// "Safe" URLs
'https://goerli.base.org',
+ // "Unsafe" URLs
+ QUICKNODE_BASE_GOERLI_RPC_URL,
+ 'https://base-goerli.blastapi.io/b5a802d8-151d-4443-90a7-699108dc4e01',
+ 'https://svc.blockdaemon.com/base/testnet/native?apiKey=zpka_1334e7c450464d06b6e33a972a7a4e57_75320f43',
],
}
@@ -143,7 +159,7 @@ export const RPC_URLS = {
],
[ChainId.CELO]: FALLBACK_URLS[ChainId.CELO],
[ChainId.CELO_ALFAJORES]: FALLBACK_URLS[ChainId.CELO_ALFAJORES],
- [ChainId.BNB]: [QUICKNODE_RPC_URL, ...FALLBACK_URLS[ChainId.BNB]],
+ [ChainId.BNB]: [QUICKNODE_BNB_RPC_URL, ...FALLBACK_URLS[ChainId.BNB]],
[ChainId.AVALANCHE]: [`https://avalanche-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.AVALANCHE]],
[ChainId.BASE]: [`https://base-mainnet.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.BASE]],
[ChainId.BASE_GOERLI]: [`https://base-goerli.infura.io/v3/${INFURA_KEY}`, ...FALLBACK_URLS[ChainId.BASE_GOERLI]],
diff --git a/src/constants/routing.ts b/src/constants/routing.ts
index 5239d87fd5..3b6166959c 100644
--- a/src/constants/routing.ts
+++ b/src/constants/routing.ts
@@ -37,6 +37,7 @@ import {
USDC_ARBITRUM,
USDC_ARBITRUM_GOERLI,
USDC_AVALANCHE,
+ USDC_BASE,
USDC_BSC,
USDC_MAINNET,
USDC_OPTIMISM,
@@ -161,6 +162,8 @@ export const COMMON_BASES: ChainCurrencyList = {
],
[ChainId.OPTIMISM]: [nativeOnChain(ChainId.OPTIMISM), OP, DAI_OPTIMISM, USDC_OPTIMISM, USDT_OPTIMISM, WBTC_OPTIMISM],
[ChainId.OPTIMISM_GOERLI]: [nativeOnChain(ChainId.OPTIMISM_GOERLI)],
+ [ChainId.BASE]: [nativeOnChain(ChainId.BASE), WRAPPED_NATIVE_CURRENCY[ChainId.BASE] as Token, USDC_BASE],
+ [ChainId.BASE_GOERLI]: [nativeOnChain(ChainId.BASE_GOERLI), WRAPPED_NATIVE_CURRENCY[ChainId.BASE_GOERLI] as Token],
[ChainId.POLYGON]: [
nativeOnChain(ChainId.POLYGON),
WETH_POLYGON,
diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts
index a0c1319667..fef1aeff16 100644
--- a/src/constants/tokens.ts
+++ b/src/constants/tokens.ts
@@ -73,6 +73,13 @@ export const PORTAL_USDC_CELO = new Token(
'USDCet',
'USDC (Portal from Ethereum)'
)
+export const USDC_BASE = new Token(
+ ChainId.BASE,
+ '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA',
+ 6,
+ 'USD Base Coin',
+ 'USDbC'
+)
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin')
export const DAI_ARBITRUM_ONE = new Token(
@@ -298,6 +305,14 @@ export const WRAPPED_NATIVE_CURRENCY: { [chainId: number]: Token | undefined } =
'WETH',
'Wrapped Ether'
),
+ [ChainId.BASE]: new Token(ChainId.BASE, '0x4200000000000000000000000000000000000006', 18, 'WETH', 'Wrapped Ether'),
+ [ChainId.BASE_GOERLI]: new Token(
+ ChainId.BASE_GOERLI,
+ '0x4200000000000000000000000000000000000006',
+ 18,
+ 'WETH',
+ 'Wrapped Ether'
+ ),
[ChainId.ARBITRUM_ONE]: new Token(
ChainId.ARBITRUM_ONE,
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
diff --git a/src/featureFlags/flags/baseEnabled.ts b/src/featureFlags/flags/baseEnabled.ts
new file mode 100644
index 0000000000..77a663ea48
--- /dev/null
+++ b/src/featureFlags/flags/baseEnabled.ts
@@ -0,0 +1,19 @@
+import { ChainId } from '@uniswap/sdk-core'
+
+import { BaseVariant, FeatureFlag, useBaseFlag } from '../index'
+
+export function useBaseEnabledFlag(): BaseVariant {
+ return useBaseFlag(FeatureFlag.baseEnabled)
+}
+
+export function useBaseEnabled(): boolean {
+ return useBaseEnabledFlag() === BaseVariant.Enabled
+}
+
+export function useBaseEnabledChains(): Record {
+ const baseEnabled = useBaseEnabled()
+ return {
+ [ChainId.BASE]: baseEnabled,
+ [ChainId.BASE_GOERLI]: baseEnabled,
+ }
+}
diff --git a/src/featureFlags/index.tsx b/src/featureFlags/index.tsx
index 2a418a4ae0..851d74b34a 100644
--- a/src/featureFlags/index.tsx
+++ b/src/featureFlags/index.tsx
@@ -15,6 +15,7 @@ export enum FeatureFlag {
uniswapXSyntheticQuote = 'uniswapx_synthetic_quote',
routingAPIPrice = 'routing_api_price',
forceUniswapXOn = 'uniswapx_force_on', // forces routing-api's feature flag for uniswapx to turn on as well
+ baseEnabled = 'base_enabled',
}
interface FeatureFlagsContextType {
diff --git a/src/graphql/data/util.tsx b/src/graphql/data/util.tsx
index b7f19a8204..a7338aa5f1 100644
--- a/src/graphql/data/util.tsx
+++ b/src/graphql/data/util.tsx
@@ -184,7 +184,7 @@ export const BACKEND_SUPPORTED_CHAINS = [
Chain.Optimism,
Chain.Celo,
] as const
-export const BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS = [ChainId.BNB, ChainId.AVALANCHE] as const
+export const BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS = [ChainId.BNB, ChainId.AVALANCHE, ChainId.BASE] as const
export function getTokenDetailsURL({
address,
diff --git a/src/pages/Pool/index.tsx b/src/pages/Pool/index.tsx
index 2034c275d9..5047d05aa2 100644
--- a/src/pages/Pool/index.tsx
+++ b/src/pages/Pool/index.tsx
@@ -10,6 +10,7 @@ import PositionList from 'components/PositionList'
import { RowBetween, RowFixed } from 'components/Row'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import { isSupportedChain } from 'constants/chains'
+import { useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import { useFilterPossiblyMaliciousPositions } from 'hooks/useFilterPossiblyMaliciousPositions'
import { useNetworkSupportsV2 } from 'hooks/useNetworkSupportsV2'
import { useV3Positions } from 'hooks/useV3Positions'
@@ -212,8 +213,9 @@ export default function Pool() {
)
const filteredPositions = useFilterPossiblyMaliciousPositions(userSelectedPositionSet)
+ const baseEnabledChains = useBaseEnabledChains()
- if (!isSupportedChain(chainId)) {
+ if (!isSupportedChain(chainId, baseEnabledChains)) {
return
}
diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx
index 5e3e1ea255..4bd46b4dbf 100644
--- a/src/pages/Swap/index.tsx
+++ b/src/pages/Swap/index.tsx
@@ -33,6 +33,7 @@ import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal'
import { getChainInfo } from 'constants/chainInfo'
import { asSupportedChain, isSupportedChain } from 'constants/chains'
import { getSwapCurrencyId, TOKEN_SHORTHANDS } from 'constants/tokens'
+import { useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import { useCurrency, useDefaultActiveTokens } from 'hooks/Tokens'
import { useIsSwapUnsupported } from 'hooks/useIsSwapUnsupported'
import { useMaxAmountIn } from 'hooks/useMaxAmountIn'
@@ -135,19 +136,23 @@ function largerPercentValue(a?: Percent, b?: Percent) {
export default function SwapPage({ className }: { className?: string }) {
const { chainId: connectedChainId } = useWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch()
+ const baseEnabledChains = useBaseEnabledChains()
const location = useLocation()
+ const supportedChainId = asSupportedChain(connectedChainId, baseEnabledChains)
+
return (
diff --git a/src/theme/colors.ts b/src/theme/colors.ts
index bd55ed0ba7..5e0bbafaef 100644
--- a/src/theme/colors.ts
+++ b/src/theme/colors.ts
@@ -97,6 +97,7 @@ export const colors = {
networkBsc: '#F0B90B',
networkPolygonSoft: 'rgba(164, 87, 255, 0.16)',
networkEthereumSoft: 'rgba(98, 126, 234, 0.16)',
+ networkBase: '#0052FF',
}
type Theme = typeof darkTheme
@@ -122,6 +123,7 @@ const commonTheme = {
chain_10_background: colors.red900,
chain_43114_background: colors.red900,
chain_42161_background: colors.blue900,
+ chain_84531: colors.networkBase,
chain_56_background: colors.networkBsc,
promotional: colors.magenta300,
diff --git a/src/theme/components/RadialGradientByChainUpdater.ts b/src/theme/components/RadialGradientByChainUpdater.ts
index fd1c74b1f4..ee54b6fe3b 100644
--- a/src/theme/components/RadialGradientByChainUpdater.ts
+++ b/src/theme/components/RadialGradientByChainUpdater.ts
@@ -1,5 +1,6 @@
import { ChainId } from '@uniswap/sdk-core'
import { useWeb3React } from '@web3-react/core'
+import { useBaseEnabled } from 'featureFlags/flags/baseEnabled'
import { useIsNftPage } from 'hooks/useIsNftPage'
import { useEffect } from 'react'
import { useDarkModeManager } from 'theme/components/ThemeToggle'
@@ -27,10 +28,19 @@ const setBackground = (newValues: TargetBackgroundStyles) =>
}
})
+function setDefaultBackground(backgroundRadialGradientElement: HTMLElement, darkMode?: boolean) {
+ setBackground(initialStyles)
+ const defaultLightGradient =
+ 'radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF'
+ const defaultDarkGradient = 'linear-gradient(180deg, #202738 0%, #070816 100%)'
+ backgroundRadialGradientElement.style.background = darkMode ? defaultDarkGradient : defaultLightGradient
+}
+
export default function RadialGradientByChainUpdater(): null {
const { chainId } = useWeb3React()
const [darkMode] = useDarkModeManager()
const isNftPage = useIsNftPage()
+ const baseEnabled = useBaseEnabled()
// manage background color
useEffect(() => {
@@ -105,14 +115,24 @@ export default function RadialGradientByChainUpdater(): null {
backgroundRadialGradientElement.style.background = darkMode ? avaxDarkGradient : avaxLightGradient
break
}
+ case ChainId.BASE:
+ case ChainId.BASE_GOERLI: {
+ if (!baseEnabled) {
+ setDefaultBackground(backgroundRadialGradientElement, darkMode)
+ return
+ }
+ setBackground(backgroundResetStyles)
+ const baseLightGradient =
+ 'radial-gradient(100% 100% at 50% 0%, rgba(0, 82, 255, 0.20) 0%, rgba(0, 82, 255, 0.08) 40.0%, rgba(252, 255, 82, 0.00) 100%), rgb(255, 255, 255)'
+ const baseDarkGradient =
+ 'radial-gradient(100% 100% at 50% 0%, rgba(10, 41, 75, 0.7) 0%, rgba(0, 82, 255, .1) 40%, rgba(0, 82, 255, 0) 100%), rgb(13, 14, 14)'
+ backgroundRadialGradientElement.style.background = darkMode ? baseDarkGradient : baseLightGradient
+ break
+ }
default: {
- setBackground(initialStyles)
- const defaultLightGradient =
- 'radial-gradient(100% 100% at 50% 0%, rgba(255, 184, 226, 0.51) 0%, rgba(255, 255, 255, 0) 100%), #FFFFFF'
- const defaultDarkGradient = 'linear-gradient(180deg, #202738 0%, #070816 100%)'
- backgroundRadialGradientElement.style.background = darkMode ? defaultDarkGradient : defaultLightGradient
+ setDefaultBackground(backgroundRadialGradientElement, darkMode)
}
}
- }, [darkMode, chainId, isNftPage])
+ }, [darkMode, chainId, isNftPage, baseEnabled])
return null
}
diff --git a/src/utils/getExplorerLink.test.ts b/src/utils/getExplorerLink.test.ts
index 75f1424202..bddc9c3892 100644
--- a/src/utils/getExplorerLink.test.ts
+++ b/src/utils/getExplorerLink.test.ts
@@ -35,4 +35,12 @@ describe('#getExplorerLink', () => {
'https://snowtrace.io/address/abc'
)
})
+ it('base', () => {
+ expect(getExplorerLink(ChainId.BASE, 'abc', ExplorerDataType.ADDRESS)).toEqual('https://basescan.org/address/abc')
+ })
+ it('base goerli', () => {
+ expect(getExplorerLink(ChainId.BASE_GOERLI, 'abc', ExplorerDataType.ADDRESS)).toEqual(
+ 'https://goerli.basescan.org/address/abc'
+ )
+ })
})
diff --git a/src/utils/getExplorerLink.ts b/src/utils/getExplorerLink.ts
index 6aa217746d..2b508167e6 100644
--- a/src/utils/getExplorerLink.ts
+++ b/src/utils/getExplorerLink.ts
@@ -12,6 +12,8 @@ const BLOCK_EXPLORER_PREFIXES: { [chainId: number]: string } = {
[ChainId.CELO_ALFAJORES]: 'https://alfajores-blockscout.celo-testnet.org',
[ChainId.BNB]: 'https://bscscan.com',
[ChainId.AVALANCHE]: 'https://snowtrace.io',
+ [ChainId.BASE]: 'https://basescan.org',
+ [ChainId.BASE_GOERLI]: 'https://goerli.basescan.org',
}
export enum ExplorerDataType {
diff --git a/yarn.lock b/yarn.lock
index 0848e86b3e..2e0578061d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6307,7 +6307,7 @@
"@uniswap/v2-sdk" "^3.2.0"
"@uniswap/v3-sdk" "^3.10.0"
-"@uniswap/sdk-core@>= 3", "@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.0", "@uniswap/sdk-core@^4.0.2", "@uniswap/sdk-core@^4.0.3":
+"@uniswap/sdk-core@>= 3", "@uniswap/sdk-core@^4", "@uniswap/sdk-core@^4.0.0", "@uniswap/sdk-core@^4.0.2", "@uniswap/sdk-core@^4.0.3", "@uniswap/sdk-core@^4.0.6":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-4.0.6.tgz#7b6d562f54293bbe0a9d101fb84e619db325f7b6"
integrity sha512-6GzCVfnOiJtvo91zlF/VjnC2OEbBRThVclzrh7+Zmo8dBovXwSlXwqn3RkSWACn/XEOzAKH70TficfOWm6mWJA==
@@ -6319,14 +6319,15 @@
tiny-invariant "^1.1.0"
toformat "^2.0.0"
-"@uniswap/smart-order-router@^3.13.7":
- version "3.13.7"
- resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-3.13.7.tgz#0355402b4459a3a8e78a2dc68c1c6aa7d4386b5c"
- integrity sha512-fJDyUngHWw2lH0qIkDzeUqHgP2VLAq33o5O9yM75nQi6LrD8fEIgsfdzFbF8c+F7enAFtA6Xl4lf5AlHKNaXSg==
+"@uniswap/smart-order-router@^3.15.0":
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-3.15.0.tgz#f9e1f7ecdf48b9bae7c182acb99289e3ee1acce3"
+ integrity sha512-H2IY4U+gQQ9E5mqYbv6uJWpTBGMkQ/tLKCdwfyL875bhTrGXunur0/7HurCW0BFIN3aEz6PqzJG+6QKg3hVCAQ==
dependencies:
"@uniswap/default-token-list" "^11.2.0"
"@uniswap/permit2-sdk" "^1.2.0"
"@uniswap/router-sdk" "^1.6.0"
+ "@uniswap/sdk-core" "^4.0.6"
"@uniswap/swap-router-contracts" "^1.3.0"
"@uniswap/token-lists" "^1.0.0-beta.31"
"@uniswap/universal-router" "^1.0.1"