feat: base network (#6997)

* feat: initial support for base goerli

* wip: update dependencies

* chore: yarn deduplicate

* feat: mainnet wip support

* feat: mainnet wip support

* fix: radial gradient

* fix: logo update

* fix: ur address

* fix: add weth to common bases

* fix: updates

* fix: yarn dedup

* fix: correct rpc url

* fix: correct explorer url

* feat: add USDbC to common bases

* fix lint warnings

* bump SOR version

* fix: fallback URLs

* feat: statsig flag for base support (#7079)

* feat: put base support behind statsig flag

* fix: null checks

* fix: hide pool page

* fix: baseEnabledChains

* feat: update sor

---------

Co-authored-by: Jordan Frankfurt <jordan@uniswap.org>
This commit is contained in:
eddie 2023-08-04 11:43:11 -07:00 committed by GitHub
parent f845695f6e
commit fa4e75b777
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 288 additions and 54 deletions

2
.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"

@ -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",

@ -0,0 +1,11 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 14C0 6.26801 6.26801 0 14 0V0C21.732 0 28 6.26801 28 14V14C28 21.732 21.732 28 14 28V28C6.26801 28 0 21.732 0 14V14Z" fill="#0052FF"/>
<g clip-path="url(#clip0_13924_33076)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3332 14.0003C23.3332 19.155 19.1472 23.3337 13.9836 23.3337C9.08459 23.3337 5.06565 19.5724 4.6665 14.7849H17.0245V13.2158H4.6665C5.06565 8.42825 9.08459 4.66699 13.9836 4.66699C19.1472 4.66699 23.3332 8.84566 23.3332 14.0003Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_13924_33076">
<rect width="18.6667" height="18.6667" fill="white" transform="translate(4.66675 4.66699)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 745 B

@ -0,0 +1,11 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="28" height="28" rx="8" fill="#0052FF"/>
<g clip-path="url(#clip0_13921_13252)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3332 14.0003C23.3332 19.155 19.1472 23.3337 13.9836 23.3337C9.08459 23.3337 5.06565 19.5724 4.6665 14.7849H17.0245V13.2158H4.6665C5.06565 8.42825 9.08459 4.66699 13.9836 4.66699C19.1472 4.66699 23.3332 8.84566 23.3332 14.0003Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_13921_13252">
<rect width="18.6667" height="18.6667" fill="white" transform="translate(4.66675 4.66699)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 651 B

@ -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<T extends BaseContract>(
chainIds?: ChainId[]
): ContractMap<T> {
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<T>, chainId) => {
const provider =
@ -50,7 +52,7 @@ function useContractMultichain<T extends BaseContract>(
}
return acc
}, {})
}, [ABI, addressMap, chainIds, walletChainId, walletProvider])
}, [ABI, addressMap, baseEnabledChains, chainIds, walletChainId, walletProvider])
}
export function useV3ManagerContracts(chainIds: ChainId[]): ContractMap<NonfungiblePositionManager> {

@ -47,6 +47,7 @@ const DEFAULT_CHAINS = [
ChainId.CELO,
ChainId.BNB,
ChainId.AVALANCHE,
ChainId.BASE,
]
type UseMultiChainPositionsData = { positions?: PositionInfo[]; loading: boolean }

@ -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()

@ -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 {

@ -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"
/>
<FeatureFlagOption
variant={BaseVariant}
value={useBaseEnabledFlag()}
featureFlag={FeatureFlag.baseEnabled}
label="Enable Base"
/>
<FeatureFlagGroup name="Debug">
<FeatureFlagOption
variant={TraceJsonRpcVariant}

@ -20,6 +20,8 @@ export const FEE_AMOUNT_DETAIL: Record<
ChainId.POLYGON,
ChainId.POLYGON_MUMBAI,
ChainId.AVALANCHE,
ChainId.BASE,
ChainId.BASE_GOERLI,
],
},
[FeeAmount.LOW]: {

@ -14,6 +14,7 @@ import {
TESTNET_CHAIN_IDS,
UniWalletSupportedChains,
} from 'constants/chains'
import { useBaseEnabled, useBaseEnabledChains } from 'featureFlags/flags/baseEnabled'
import { useOnClickOutside } from 'hooks/useOnClickOutside'
import useSelectChain from 'hooks/useSelectChain'
import useSyncChainQuery from 'hooks/useSyncChainQuery'
@ -59,13 +60,20 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => {
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<string, ChainId[]>
)
return [supported, unsupported]
}, [showTestnets, walletSupportsChain])
}, [baseEnabled, showTestnets, walletSupportsChain])
const ref = useRef<HTMLDivElement>(null)
const modalRef = useRef<HTMLDivElement>(null)
useOnClickOutside(ref, () => setIsOpen(false), [modalRef])
const info = chainId ? getChainInfo(chainId) : undefined
const baseEnabledChains = useBaseEnabledChains()
const info = getChainInfo(chainId, baseEnabledChains)
const selectChain = useSelectChain()
useSyncChainQuery()

@ -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)
}}
>
<Logo src={logoUrl} alt={label} />
<Label>{label}</Label>
{logoUrl && <Logo src={logoUrl} alt={label} />}
{label && <Label>{label}</Label>}
{disabled && (
<CaptionText>
<Trans>Unsupported by your wallet</Trans>

@ -362,6 +362,9 @@ function ComingSoonText({ chainId }: { chainId: ChainId }) {
return <Trans>Coming soon: search and explore tokens on BNB Chain</Trans>
case ChainId.AVALANCHE:
return <Trans>Coming soon: search and explore tokens on Avalanche Chain</Trans>
case ChainId.BASE:
case ChainId.BASE_GOERLI:
return <Trans>Coming soon: search and explore tokens on Base</Trans>
default:
return null
}

@ -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

@ -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 (
<InvalidDetailsContainer>
<EyeIcon />
@ -69,9 +71,11 @@ export default function InvalidTokenDetails({
</>
) : (
<>
{connectedChainLabel && (
<InvalidDetailsText>
<Trans>This token doesn&apos;t exist on {getChainInfo(chainId)?.label}</Trans>
<Trans>This token doesn&apos;t exist on {connectedChainLabel}</Trans>
</InvalidDetailsText>
)}
<TokenExploreButton onClick={() => selectChain(pageChainId)}>
<ThemedText.SubHeader>
<Trans>Switch to {getChainInfo(pageChainId).label}</Trans>

@ -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]

@ -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;

@ -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<ChainId | SupportedL1ChainId | SupportedL2ChainId | number, boolean>
): L1ChainInfo
export function getChainInfo(
chainId: SupportedL2ChainId,
featureFlags?: Record<ChainId | SupportedL1ChainId | SupportedL2ChainId | number, boolean>
): L2ChainInfo
export function getChainInfo(
chainId: ChainId,
featureFlags?: Record<ChainId | SupportedL1ChainId | SupportedL2ChainId | number, boolean>
): L1ChainInfo | L2ChainInfo
export function getChainInfo(
chainId: ChainId | SupportedL1ChainId | SupportedL2ChainId | number | undefined,
featureFlags?: Record<ChainId | SupportedL1ChainId | SupportedL2ChainId | number, boolean>
): 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<ChainId | SupportedL1ChainId | SupportedL2ChainId | number, boolean>
): 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<number, boolean>) {
return getChainInfo(chainId, featureFlags) ?? MAINNET_INFO
}

@ -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<number, boolean>
): 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<number, boolean>
): 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]

@ -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,
]

@ -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]],

@ -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,

@ -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',

@ -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<number, boolean> {
const baseEnabled = useBaseEnabled()
return {
[ChainId.BASE]: baseEnabled,
[ChainId.BASE_GOERLI]: baseEnabled,
}
}

@ -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 {

@ -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,

@ -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 <WrongNetworkCard />
}

@ -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 (
<Trace page={InterfacePageName.SWAP_PAGE} shouldLogImpression>
<PageWrapper>
<Swap
className={className}
chainId={connectedChainId}
chainId={supportedChainId ?? ChainId.MAINNET}
prefilledState={{
[Field.INPUT]: { currencyId: loadedUrlParams?.[Field.INPUT]?.currencyId },
[Field.OUTPUT]: { currencyId: loadedUrlParams?.[Field.OUTPUT]?.currencyId },
}}
disableTokenInputs={supportedChainId === undefined}
/>
<NetworkAlert />
</PageWrapper>

@ -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,

@ -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
}

@ -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'
)
})
})

@ -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 {

@ -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"