From fa4e75b7778dd8621f7a3536bc86e0808f945b39 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 4 Aug 2023 11:43:11 -0700 Subject: [PATCH] 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 --- .env | 2 + package.json | 2 +- src/assets/svg/base_logo.svg | 11 ++++ src/assets/svg/base_square_logo.svg | 11 ++++ .../MiniPortfolio/Pools/hooks.ts | 6 +- .../Pools/useMultiChainPositions.tsx | 1 + .../MiniPortfolio/PortfolioLogo.tsx | 6 +- src/components/AccountDrawer/index.tsx | 4 +- .../FeatureFlagModal/FeatureFlagModal.tsx | 7 ++ src/components/FeeSelector/shared.tsx | 2 + src/components/NavBar/ChainSelector.tsx | 19 ++++-- src/components/NavBar/ChainSelectorRow.tsx | 10 ++- src/components/NavBar/SearchBarDropdown.tsx | 3 + src/components/NetworkAlert/NetworkAlert.tsx | 17 ++++- .../TokenDetails/InvalidTokenDetails.tsx | 10 ++- src/components/Tokens/constants.ts | 2 +- src/components/Web3Status/index.tsx | 6 -- src/constants/chainInfo.ts | 66 ++++++++++++++++--- src/constants/chains.ts | 24 ++++++- src/constants/lists.ts | 3 + src/constants/networks.ts | 24 +++++-- src/constants/routing.ts | 3 + src/constants/tokens.ts | 15 +++++ src/featureFlags/flags/baseEnabled.ts | 19 ++++++ src/featureFlags/index.tsx | 1 + src/graphql/data/util.tsx | 2 +- src/pages/Pool/index.tsx | 4 +- src/pages/Swap/index.tsx | 7 +- src/theme/colors.ts | 2 + .../RadialGradientByChainUpdater.ts | 32 +++++++-- src/utils/getExplorerLink.test.ts | 8 +++ src/utils/getExplorerLink.ts | 2 + yarn.lock | 11 ++-- 33 files changed, 288 insertions(+), 54 deletions(-) create mode 100644 src/assets/svg/base_logo.svg create mode 100644 src/assets/svg/base_square_logo.svg create mode 100644 src/featureFlags/flags/baseEnabled.ts 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"