From 0752314d87311d37fd4c6d8ea1484a9a47b075a8 Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Thu, 5 Oct 2023 11:08:39 -0700 Subject: [PATCH 01/61] fix: click on test row directly (#7424) --- cypress/e2e/universal-search.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/universal-search.test.ts b/cypress/e2e/universal-search.test.ts index aaddb41f9d1..f8335c56469 100644 --- a/cypress/e2e/universal-search.test.ts +++ b/cypress/e2e/universal-search.test.ts @@ -65,7 +65,7 @@ describe('Universal search bar', () => { cy.get(getTestSelector('searchbar-token-row-ETHEREUM-NATIVE')) // Validate that we go to the searched/selected result. - getSearchBar().type('{enter}') + cy.get(getTestSelector('searchbar-token-row-ETHEREUM-NATIVE')).click() cy.url().should('contain', 'tokens/ethereum/NATIVE') } ) From 040ebb54753202bfdc610a247cf2a26a81ceda5f Mon Sep 17 00:00:00 2001 From: Matthew Spector Date: Thu, 5 Oct 2023 11:33:28 -0700 Subject: [PATCH 02/61] fix: Remove Minus Sign for FOT Display (#7419) --- src/components/swap/SwapLineItem.tsx | 4 ++-- .../SwapDetailsDropdown.test.tsx.snap | 2 +- .../swap/__snapshots__/SwapLineItem.test.tsx.snap | 14 +++++++------- .../__snapshots__/SwapModalFooter.test.tsx.snap | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index da5f619ad1e..58548de1303 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -71,8 +71,8 @@ function ExchangeRateRow({ trade }: { trade: InterfaceTrade }) { } function ColoredPercentRow({ percent }: { percent: Percent }) { - const { formatPriceImpact } = useFormatter() - return {formatPriceImpact(percent)} + const { formatSlippage } = useFormatter() + return {formatSlippage(percent)} } function CurrencyAmountRow({ amount }: { amount: CurrencyAmount }) { diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index 0ff9d83c871..50660d221c6 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -316,7 +316,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` - 105566.373% + -105566.373% diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 1ae30f78f9a..111ab24d815 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -1288,7 +1288,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` - 105566.373% + -105566.373% @@ -2627,7 +2627,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` - 105566.373% + -105566.373% @@ -3966,7 +3966,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` - 105566.373% + -105566.373% @@ -5324,7 +5324,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` - -3.000% + 3.000% @@ -5511,7 +5511,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` - 105566.373% + -105566.373% @@ -6869,7 +6869,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` - -3.000% + 3.000% @@ -7056,7 +7056,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` - 105566.373% + -105566.373% diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index 9a80ee41550..61af8963a5a 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -130,7 +130,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = - 105566.373% + -105566.373% From 0381200fecc8697a1b2cc72ad0061d8ca2c726df Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:40:52 -0700 Subject: [PATCH 03/61] fix: ignore large slices in immutable check (#7425) --- src/state/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/state/index.ts b/src/state/index.ts index ebc17d3eb58..d61d31c3040 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -15,6 +15,9 @@ export function createDefaultStore() { middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: true, + immutableCheck: { + ignoredPaths: [routingApi.reducerPath, 'logs', 'lists'], + }, serializableCheck: { // meta.arg and meta.baseQueryMeta are defaults. payload.trade is a nonserializable return value, but that's ok // because we are not adding it into any persisted store that requires serialization (e.g. localStorage) From 53f0ca9b7ea74c8e7c06feff16e259c3238785d8 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 5 Oct 2023 11:54:24 -0700 Subject: [PATCH 04/61] fix: disable UniswapX opt-out in e2e tests (#7423) --- cypress/e2e/swap/uniswapx.test.ts | 17 +++++++++++++---- cypress/e2e/wallet-dropdown.test.ts | 8 ++++---- cypress/support/commands.ts | 7 +++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/cypress/e2e/swap/uniswapx.test.ts b/cypress/e2e/swap/uniswapx.test.ts index 371f0a946ad..d42981047f3 100644 --- a/cypress/e2e/swap/uniswapx.test.ts +++ b/cypress/e2e/swap/uniswapx.test.ts @@ -1,4 +1,5 @@ import { ChainId, CurrencyAmount } from '@uniswap/sdk-core' +import { FeatureFlag } from 'featureFlags' import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens' import { getTestSelector } from '../../utils' @@ -26,7 +27,9 @@ function stubSwapTxReceipt() { describe('UniswapX Toggle', () => { beforeEach(() => { cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter }) - cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`) + cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { + featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }], + }) }) it('only displays uniswapx ui when setting is on', () => { @@ -76,7 +79,9 @@ describe('UniswapX Orders', () => { stubSwapTxReceipt() cy.hardhat().then((hardhat) => hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8))) - cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`) + cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { + featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }], + }) }) it('can swap exact-in trades using uniswapX', () => { @@ -164,7 +169,9 @@ describe('UniswapX Eth Input', () => { stubSwapTxReceipt() - cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`) + cy.visit(`/swap/?inputCurrency=ETH&outputCurrency=${DAI.address}`, { + featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }], + }) }) it('can swap using uniswapX with ETH as input', () => { @@ -249,7 +256,9 @@ describe('UniswapX activity history', () => { cy.hardhat().then(async (hardhat) => { await hardhat.fund(hardhat.wallet, CurrencyAmount.fromRawAmount(USDC_MAINNET, 3e8)) }) - cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`) + cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { + featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }], + }) }) it('can view UniswapX order status progress in activity', () => { diff --git a/cypress/e2e/wallet-dropdown.test.ts b/cypress/e2e/wallet-dropdown.test.ts index 3e88e8c715c..67856c775e1 100644 --- a/cypress/e2e/wallet-dropdown.test.ts +++ b/cypress/e2e/wallet-dropdown.test.ts @@ -53,7 +53,7 @@ describe('Wallet Dropdown', () => { describe('should change locale with feature flag', () => { beforeEach(() => { - cy.visit('/', { featureFlags: [FeatureFlag.currencyConversion] }) + cy.visit('/', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] }) cy.get(getTestSelector('web3-status-connected')).click() cy.get(getTestSelector('wallet-settings')).click() }) @@ -147,19 +147,19 @@ describe('Wallet Dropdown', () => { describe('local currency', () => { it('loads local currency from the query param', () => { - cy.visit('/', { featureFlags: [FeatureFlag.currencyConversion] }) + cy.visit('/', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] }) cy.get(getTestSelector('web3-status-connected')).click() cy.get(getTestSelector('wallet-settings')).click() cy.contains('USD') - cy.visit('/?cur=AUD', { featureFlags: [FeatureFlag.currencyConversion] }) + cy.visit('/?cur=AUD', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] }) cy.get(getTestSelector('web3-status-connected')).click() cy.get(getTestSelector('wallet-settings')).click() cy.contains('AUD') }) it('loads local currency from menu', () => { - cy.visit('/', { featureFlags: [FeatureFlag.currencyConversion] }) + cy.visit('/', { featureFlags: [{ name: FeatureFlag.currencyConversion, value: true }] }) cy.get(getTestSelector('web3-status-connected')).click() cy.get(getTestSelector('wallet-settings')).click() cy.contains('USD') diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index acd5bfc04f2..3c7659f279e 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -24,7 +24,7 @@ declare global { } interface VisitOptions { serviceWorker?: true - featureFlags?: Array + featureFlags?: Array<{ name: FeatureFlag; value: boolean }> /** * Initial user state. * @default {@type import('../utils/user-state').CONNECTED_WALLET_USER_STATE} @@ -59,7 +59,10 @@ Cypress.Commands.overwrite( // Set feature flags, if configured. if (options?.featureFlags) { - const featureFlags = options.featureFlags.reduce((flags, flag) => ({ ...flags, [flag]: 'enabled' }), {}) + const featureFlags = options.featureFlags.reduce( + (flags, flag) => ({ ...flags, [flag.name]: flag.value ? 'enabled' : 'control' }), + {} + ) win.localStorage.setItem('featureFlags', JSON.stringify(featureFlags)) } From 4a79280edc54d5cb444f08c5b51d75e02a6d89a6 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:12:21 -0700 Subject: [PATCH 05/61] feat: allow manual test runs (#7415) --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f4b97911d8..1f1f2aad384 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,7 @@ on: - main - releases/staging pull_request: + workflow_dispatch: jobs: lint: From bab8506919936b92e55da02d043e7d7d6c63c577 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:12:34 -0700 Subject: [PATCH 06/61] fix: dont crash on invalid tokenId (#7410) --- src/pages/Pool/PositionPage.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pages/Pool/PositionPage.tsx b/src/pages/Pool/PositionPage.tsx index 39458e35237..62254ac4bb2 100644 --- a/src/pages/Pool/PositionPage.tsx +++ b/src/pages/Pool/PositionPage.tsx @@ -384,13 +384,22 @@ const PositionLabelRow = styled(RowFixed)({ gap: 8, }) +function parseTokenId(tokenId: string | undefined): BigNumber | undefined { + if (!tokenId) return + try { + return BigNumber.from(tokenId) + } catch (error) { + return + } +} + function PositionPageContent() { const { tokenId: tokenIdFromUrl } = useParams<{ tokenId?: string }>() const { chainId, account, provider } = useWeb3React() const theme = useTheme() const { formatTickPrice } = useFormatter() - const parsedTokenId = tokenIdFromUrl ? BigNumber.from(tokenIdFromUrl) : undefined + const parsedTokenId = parseTokenId(tokenIdFromUrl) const { loading, position: positionDetails } = useV3PositionFromTokenId(parsedTokenId) const { From 3ced65b8a41e0021e24df2d03f3532754d4a8a9a Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:19:58 -0700 Subject: [PATCH 07/61] feat: add sitemap for app.uniswap.org (#7408) * feat: add sitemap for app.uniswap.org * feat: script to update lastmod * fix: deps and snapshots * fix: use xml2js * fix: improve test and sitemap --- package.json | 5 +- public/sitemap.xml | 19 ++ scripts/generate-sitemap.js | 25 +++ src/pages/App.tsx | 169 ++-------------- src/pages/RouteDefinitions.tsx | 208 ++++++++++++++++++++ src/pages/__snapshots__/routes.test.ts.snap | 200 +++++++++++++++++++ src/pages/routes.test.ts | 26 +++ yarn.lock | 27 ++- 8 files changed, 518 insertions(+), 161 deletions(-) create mode 100644 public/sitemap.xml create mode 100644 scripts/generate-sitemap.js create mode 100644 src/pages/RouteDefinitions.tsx create mode 100644 src/pages/__snapshots__/routes.test.ts.snap create mode 100644 src/pages/routes.test.ts diff --git a/package.json b/package.json index 4dfaa269095..e28b5c6ad3f 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,11 @@ "graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts", "graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph", "graphql": "yarn graphql:fetch && yarn graphql:generate", + "sitemap:generate": "node scripts/generate-sitemap.js", "i18n:extract": "lingui extract --locale en-US", "i18n:compile": "lingui compile", "i18n": "yarn i18n:extract --clean && yarn i18n:compile", - "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"", + "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\" \"npm:sitemap:generate\"", "start": "craco start", "start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start", "build": "craco build", @@ -114,6 +115,7 @@ "@types/ua-parser-js": "^0.7.36", "@types/uuid": "^8.3.4", "@types/wcag-contrast": "^3.0.0", + "@types/xml2js": "^0.4.12", "@uniswap/default-token-list": "^11.2.0", "@uniswap/eslint-config": "^1.2.0", "@vanilla-extract/jest-transform": "^1.1.1", @@ -293,6 +295,7 @@ "workbox-navigation-preload": "^6.1.0", "workbox-precaching": "^6.1.0", "workbox-routing": "^6.1.0", + "xml2js": "^0.6.2", "zustand": "^4.3.6" }, "engines": { diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 00000000000..ca9f947beed --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js new file mode 100644 index 00000000000..64889262d95 --- /dev/null +++ b/scripts/generate-sitemap.js @@ -0,0 +1,25 @@ +/* eslint-env node */ + +const fs = require('fs') +const { parseStringPromise, Builder } = require('xml2js') + +fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { + try { + const sitemap = await parseStringPromise(data) + + const lastmodDate = new Date().toISOString() + if (sitemap.urlset.url) { + sitemap.urlset.url.forEach((url) => { + url['$'].lastmod = lastmodDate + }) + } + const builder = new Builder() + const xml = builder.buildObject(sitemap) + fs.writeFile('./public/sitemap.xml', xml, (error) => { + if (error) throw error + console.log('Sitemap updated') + }) + } catch { + throw new Error('Error parsing sitemap.xml') + } +}) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index d7f18e70095..17d3023a059 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -5,51 +5,27 @@ import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' import { useFeatureFlagsIsLoaded } from 'featureFlags' -import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage' import { useAtom } from 'jotai' import { useBag } from 'nft/hooks/useBag' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' -import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' +import { Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' import { useRouterPreference } from 'state/user/hooks' import { StatsigProvider, StatsigUser } from 'statsig-react' import styled from 'styled-components' -import { SpinnerSVG } from 'theme/components' import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader' import { useIsDarkMode } from 'theme/components/ThemeToggle' import { flexRowNoWrap } from 'theme/styles' import { Z_INDEX } from 'theme/zIndex' import { STATSIG_DUMMY_KEY } from 'tracing' -import { getEnvName, isBrowserRouterEnabled } from 'utils/env' +import { getEnvName } from 'utils/env' import { getDownloadAppLink } from 'utils/openDownloadApp' import { getCurrentPageFromLocation } from 'utils/urlRoutes' import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals' -// High-traffic pages (index and /swap) should not be lazy-loaded. -import Landing from './Landing' -import Swap from './Swap' +import { RouteDefinition, routes, useRouterConfig } from './RouteDefinitions' const AppChrome = lazy(() => import('./AppChrome')) -const NftExplore = lazy(() => import('nft/pages/explore')) -const Collection = lazy(() => import('nft/pages/collection')) -const Profile = lazy(() => import('nft/pages/profile')) -const Asset = lazy(() => import('nft/pages/asset/Asset')) -const AddLiquidity = lazy(() => import('pages/AddLiquidity')) -const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects')) -const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects')) -const MigrateV2 = lazy(() => import('pages/MigrateV2')) -const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair')) -const NotFound = lazy(() => import('pages/NotFound')) -const Pool = lazy(() => import('pages/Pool')) -const PositionPage = lazy(() => import('pages/Pool/PositionPage')) -const PoolV2 = lazy(() => import('pages/Pool/v2')) -const PoolDetails = lazy(() => import('pages/PoolDetails')) -const PoolFinder = lazy(() => import('pages/PoolFinder')) -const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity')) -const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3')) -const TokenDetails = lazy(() => import('pages/TokenDetails')) -const Tokens = lazy(() => import('pages/Tokens')) -const Vote = lazy(() => import('pages/Vote')) const BodyWrapper = styled.div` display: flex; @@ -93,32 +69,18 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>` z-index: ${Z_INDEX.dropdown}; ` -// this is the same svg defined in assets/images/blue-loader.svg -// it is defined here because the remote asset may not have had time to load when this file is executing -const LazyLoadSpinner = () => ( - - - -) - export default function App() { const isLoaded = useFeatureFlagsIsLoaded() - const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) + const [, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) - const browserRouterEnabled = isBrowserRouterEnabled() const location = useLocation() - const { hash, pathname } = location + const { pathname } = location const currentPage = getCurrentPageFromLocation(pathname) const isDarkMode = useIsDarkMode() const [routerPreference] = useRouterPreference() const [scrolledState, setScrolledState] = useState(false) - const infoPoolPageEnabled = useInfoPoolPageEnabled() + + const routerConfig = useRouterConfig() useEffect(() => { window.scrollTo(0, 0) @@ -224,116 +186,15 @@ export default function App() { }> {isLoaded ? ( - : - } - /> - - }> - - - } /> - {infoPoolPageEnabled && } />} - }> - - - } - /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - - } /> - } /> - } /> - } /> - - }> - - - - }> - {/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */} - - - - - - }> - - - - - - - } /> - } /> - - } /> - } /> - - {!shouldDisableNFTRoutes && ( - <> - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - - } - /> - - - - - } - /> - + {routes.map((route: RouteDefinition) => + route.enabled(routerConfig) ? ( + + {route.nestedPaths.map((nestedPath) => ( + + ))} + + ) : null )} - - } /> - } /> ) : ( diff --git a/src/pages/RouteDefinitions.tsx b/src/pages/RouteDefinitions.tsx new file mode 100644 index 00000000000..b87d31a53fd --- /dev/null +++ b/src/pages/RouteDefinitions.tsx @@ -0,0 +1,208 @@ +import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage' +import { useAtom } from 'jotai' +import { lazy, ReactNode, Suspense, useMemo } from 'react' +import { Navigate, useLocation } from 'react-router-dom' +import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' +import { SpinnerSVG } from 'theme/components' +import { isBrowserRouterEnabled } from 'utils/env' + +// High-traffic pages (index and /swap) should not be lazy-loaded. +import Landing from './Landing' +import Swap from './Swap' + +const NftExplore = lazy(() => import('nft/pages/explore')) +const Collection = lazy(() => import('nft/pages/collection')) +const Profile = lazy(() => import('nft/pages/profile')) +const Asset = lazy(() => import('nft/pages/asset/Asset')) +const AddLiquidity = lazy(() => import('pages/AddLiquidity')) +const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects')) +const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects')) +const MigrateV2 = lazy(() => import('pages/MigrateV2')) +const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair')) +const NotFound = lazy(() => import('pages/NotFound')) +const Pool = lazy(() => import('pages/Pool')) +const PositionPage = lazy(() => import('pages/Pool/PositionPage')) +const PoolV2 = lazy(() => import('pages/Pool/v2')) +const PoolDetails = lazy(() => import('pages/PoolDetails')) +const PoolFinder = lazy(() => import('pages/PoolFinder')) +const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity')) +const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3')) +const TokenDetails = lazy(() => import('pages/TokenDetails')) +const Tokens = lazy(() => import('pages/Tokens')) +const Vote = lazy(() => import('pages/Vote')) + +// this is the same svg defined in assets/images/blue-loader.svg +// it is defined here because the remote asset may not have had time to load when this file is executing +const LazyLoadSpinner = () => ( + + + +) + +interface RouterConfig { + browserRouterEnabled?: boolean + hash?: string + infoPoolPageEnabled?: boolean + shouldDisableNFTRoutes?: boolean +} + +/** + * Convenience hook which organizes the router configuration into a single object. + */ +export function useRouterConfig(): RouterConfig { + const browserRouterEnabled = isBrowserRouterEnabled() + const { hash } = useLocation() + const infoPoolPageEnabled = useInfoPoolPageEnabled() + const [shouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) + return useMemo( + () => ({ + browserRouterEnabled, + hash, + infoPoolPageEnabled, + shouldDisableNFTRoutes: Boolean(shouldDisableNFTRoutes), + }), + [browserRouterEnabled, hash, infoPoolPageEnabled, shouldDisableNFTRoutes] + ) +} + +export interface RouteDefinition { + path: string + nestedPaths: string[] + enabled: (args: RouterConfig) => boolean + getElement: (args: RouterConfig) => ReactNode +} + +// Assigns the defaults to the route definition. +function createRouteDefinition(route: Partial): RouteDefinition { + return { + getElement: () => null, + enabled: () => true, + path: '/', + nestedPaths: [], + // overwrite the defaults + ...route, + } +} + +export const routes: RouteDefinition[] = [ + createRouteDefinition({ + path: '/', + getElement: (args) => { + return args.browserRouterEnabled && args.hash ? : + }, + }), + createRouteDefinition({ + path: '/tokens', + nestedPaths: [':chainName'], + getElement: () => , + }), + createRouteDefinition({ path: '/tokens/:chainName/:tokenAddress', getElement: () => }), + createRouteDefinition({ + path: '/pools/:chainName/:poolAddress', + getElement: () => , + enabled: (args) => Boolean(args.infoPoolPageEnabled), + }), + createRouteDefinition({ + path: '/vote/*', + getElement: () => ( + }> + + + ), + }), + createRouteDefinition({ + path: '/create-proposal', + getElement: () => , + }), + createRouteDefinition({ + path: '/send', + getElement: () => , + }), + createRouteDefinition({ path: '/swap', getElement: () => }), + createRouteDefinition({ path: '/pool/v2/find', getElement: () => }), + createRouteDefinition({ path: '/pool/v2', getElement: () => }), + createRouteDefinition({ path: '/pool', getElement: () => }), + createRouteDefinition({ path: '/pool/:tokenId', getElement: () => }), + createRouteDefinition({ path: '/pools/v2/find', getElement: () => }), + createRouteDefinition({ path: '/pools/v2', getElement: () => }), + createRouteDefinition({ path: '/pools', getElement: () => }), + createRouteDefinition({ path: '/pools/:tokenId', getElement: () => }), + createRouteDefinition({ + path: '/add/v2', + nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB'], + getElement: () => , + }), + createRouteDefinition({ + path: '/add', + nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB', ':currencyIdA/:currencyIdB/:feeAmount'], + getElement: () => , + }), + + createRouteDefinition({ + path: '/increase', + nestedPaths: [ + ':currencyIdA', + ':currencyIdA/:currencyIdB', + ':currencyIdA/:currencyIdB/:feeAmount', + ':currencyIdA/:currencyIdB/:feeAmount/:tokenId', + ], + getElement: () => , + }), + createRouteDefinition({ path: '/remove/v2/:currencyIdA/:currencyIdB', getElement: () => }), + createRouteDefinition({ path: '/remove/:tokenId', getElement: () => }), + createRouteDefinition({ path: '/migrate/v2', getElement: () => }), + createRouteDefinition({ path: '/migrate/v2/:address', getElement: () => }), + createRouteDefinition({ + path: '/nfts', + getElement: () => ( + + + + ), + enabled: (args) => !args.shouldDisableNFTRoutes, + }), + createRouteDefinition({ + path: '/nfts/asset/:contractAddress/:tokenId', + getElement: () => ( + + + + ), + enabled: (args) => !args.shouldDisableNFTRoutes, + }), + createRouteDefinition({ + path: '/nfts/profile', + getElement: () => ( + + + + ), + enabled: (args) => !args.shouldDisableNFTRoutes, + }), + createRouteDefinition({ + path: '/nfts/collection/:contractAddress', + getElement: () => ( + + + + ), + enabled: (args) => !args.shouldDisableNFTRoutes, + }), + createRouteDefinition({ + path: '/nfts/collection/:contractAddress/activity', + getElement: () => ( + + + + ), + enabled: (args) => !args.shouldDisableNFTRoutes, + }), + createRouteDefinition({ path: '*', getElement: () => }), + createRouteDefinition({ path: '/not-found', getElement: () => }), +] diff --git a/src/pages/__snapshots__/routes.test.ts.snap b/src/pages/__snapshots__/routes.test.ts.snap new file mode 100644 index 00000000000..1e87906873b --- /dev/null +++ b/src/pages/__snapshots__/routes.test.ts.snap @@ -0,0 +1,200 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Routes router definition should match snapshot 1`] = ` +Array [ + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [ + ":chainName", + ], + "path": "/tokens", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/tokens/:chainName/:tokenAddress", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pools/:chainName/:poolAddress", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/vote/*", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/create-proposal", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/send", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/swap", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pool/v2/find", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pool/v2", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pool", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pool/:tokenId", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pools/v2/find", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pools/v2", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pools", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/pools/:tokenId", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [ + ":currencyIdA", + ":currencyIdA/:currencyIdB", + ], + "path": "/add/v2", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [ + ":currencyIdA", + ":currencyIdA/:currencyIdB", + ":currencyIdA/:currencyIdB/:feeAmount", + ], + "path": "/add", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [ + ":currencyIdA", + ":currencyIdA/:currencyIdB", + ":currencyIdA/:currencyIdB/:feeAmount", + ":currencyIdA/:currencyIdB/:feeAmount/:tokenId", + ], + "path": "/increase", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/remove/v2/:currencyIdA/:currencyIdB", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/remove/:tokenId", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/migrate/v2", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/migrate/v2/:address", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/nfts", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/nfts/asset/:contractAddress/:tokenId", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/nfts/profile", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/nfts/collection/:contractAddress", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/nfts/collection/:contractAddress/activity", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "*", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/not-found", + }, +] +`; diff --git a/src/pages/routes.test.ts b/src/pages/routes.test.ts new file mode 100644 index 00000000000..927925b92bc --- /dev/null +++ b/src/pages/routes.test.ts @@ -0,0 +1,26 @@ +import fs from 'fs' +import { parseStringPromise } from 'xml2js' + +import { routes } from './RouteDefinitions' + +describe('Routes', () => { + it('sitemap URLs should exist as Router paths', async () => { + const pathNames: string[] = routes.map((routeDef) => routeDef.path) + const contents = fs.readFileSync('./public/sitemap.xml', 'utf8') + const sitemap = await parseStringPromise(contents) + + const sitemapPaths = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname) + + sitemapPaths.forEach((path: string) => { + expect(pathNames).toContain(path) + }) + }) + + /** + * If you are updating the app routes, consider if you need to make a + * corresponding update to the sitemap.xml file. + */ + it('router definition should match snapshot', () => { + expect(routes).toMatchSnapshot() + }) +}) diff --git a/yarn.lock b/yarn.lock index e8435e0e72f..97c884f6725 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5921,6 +5921,13 @@ dependencies: "@types/node" "*" +"@types/xml2js@^0.4.12": + version "0.4.12" + resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.12.tgz#d9aae03295476fd5cbc74e0b572816208dbec6d1" + integrity sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "20.2.1" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" @@ -15542,9 +15549,9 @@ mz@^2.7.0: thenify-all "^1.0.0" nan@^2.14.0: - version "2.14.2" - resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== nano-time@1.0.0: version "1.0.0" @@ -17227,9 +17234,9 @@ punycode@1.3.2, punycode@^1.3.2: integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: version "6.0.2" @@ -21101,6 +21108,14 @@ xml2js@^0.4.5: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml2js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" + integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz" From e6519a7dd1ede77f6575f8bb65067c3440955af0 Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Thu, 5 Oct 2023 15:25:45 -0400 Subject: [PATCH 08/61] feat: support redirects for a list of header paths (#7411) * add country code to meta tag * use blocked paths header * proper types * add test * Update functions/components/metaTagInjector.ts Co-authored-by: Zach Pomerantz * Update functions/components/metaTagInjector.ts Co-authored-by: Zach Pomerantz * Update src/pages/App.tsx Co-authored-by: Zach Pomerantz * pr suggestions * skip failing e2e * revert test change * take file from main --------- Co-authored-by: Zach Pomerantz --- functions/[[index]].ts | 2 +- functions/components/metaTagInjector.test.ts | 34 ++++++++++++++++---- functions/components/metaTagInjector.ts | 7 +++- functions/nfts/asset/[[index]].ts | 2 +- functions/nfts/collection/[index].ts | 2 +- functions/tokens/[[index]].ts | 2 +- functions/utils/getRequest.ts | 6 ++-- src/pages/App.tsx | 6 ++++ 8 files changed, 47 insertions(+), 14 deletions(-) diff --git a/functions/[[index]].ts b/functions/[[index]].ts index 305b1f40a7f..6e3e6dc281d 100644 --- a/functions/[[index]].ts +++ b/functions/[[index]].ts @@ -11,7 +11,7 @@ export const onRequest: PagesFunction = async ({ request, next }) => { } const res = next() try { - return new HTMLRewriter().on('head', new MetaTagInjector(data)).transform(await res) + return new HTMLRewriter().on('head', new MetaTagInjector(data, request)).transform(await res) } catch (e) { return res } diff --git a/functions/components/metaTagInjector.test.ts b/functions/components/metaTagInjector.test.ts index dd4d540c792..e0a9af558cb 100644 --- a/functions/components/metaTagInjector.test.ts +++ b/functions/components/metaTagInjector.test.ts @@ -6,12 +6,15 @@ test('should append meta tag to element', () => { } as unknown as Element const property = 'property' const content = 'content' - const injector = new MetaTagInjector({ - title: 'test', - url: 'testUrl', - image: 'testImage', - description: 'testDescription', - }) + const injector = new MetaTagInjector( + { + title: 'test', + url: 'testUrl', + image: 'testImage', + description: 'testDescription', + }, + new Request('http://localhost') + ) injector.append(element, property, content) expect(element.append).toHaveBeenCalledWith(``, { html: true }) @@ -36,3 +39,22 @@ test('should append meta tag to element', () => { expect(element.append).toHaveBeenCalledTimes(13) }) + +test('should pass through header blocked paths', () => { + const element = { + append: jest.fn(), + } as unknown as Element + const request = new Request('http://localhost') + request.headers.set('x-blocked-paths', '/') + const injector = new MetaTagInjector( + { + title: 'test', + url: 'testUrl', + image: 'testImage', + description: 'testDescription', + }, + request + ) + injector.element(element) + expect(element.append).toHaveBeenCalledWith(``, { html: true }) +}) diff --git a/functions/components/metaTagInjector.ts b/functions/components/metaTagInjector.ts index ca561fd8b2c..2be810a8211 100644 --- a/functions/components/metaTagInjector.ts +++ b/functions/components/metaTagInjector.ts @@ -10,7 +10,7 @@ type MetaTagInjectorInput = { * to inject meta tags into the of an HTML document. */ export class MetaTagInjector implements HTMLRewriterElementContentHandlers { - constructor(private input: MetaTagInjectorInput) {} + constructor(private input: MetaTagInjectorInput, private request: Request) {} append(element: Element, property: string, content: string) { element.append(``, { html: true }) @@ -38,5 +38,10 @@ export class MetaTagInjector implements HTMLRewriterElementContentHandlers { this.append(element, 'twitter:image', this.input.image) this.append(element, 'twitter:image:alt', this.input.title) } + + const blockedPaths = this.request.headers.get('x-blocked-paths') + if (blockedPaths) { + this.append(element, 'x:blocked-paths', blockedPaths) + } } } diff --git a/functions/nfts/asset/[[index]].ts b/functions/nfts/asset/[[index]].ts index 9b548cab57b..c7a15a640cf 100644 --- a/functions/nfts/asset/[[index]].ts +++ b/functions/nfts/asset/[[index]].ts @@ -8,7 +8,7 @@ export const onRequest: PagesFunction = async ({ params, request, next }) => { const { index } = params const collectionAddress = index[0]?.toString() const tokenId = index[1]?.toString() - return getMetadataRequest(res, request.url, () => getAsset(collectionAddress, tokenId, request.url)) + return getMetadataRequest(res, request, () => getAsset(collectionAddress, tokenId, request.url)) } catch (e) { return res } diff --git a/functions/nfts/collection/[index].ts b/functions/nfts/collection/[index].ts index 8f1af7a2b69..381dcca6468 100644 --- a/functions/nfts/collection/[index].ts +++ b/functions/nfts/collection/[index].ts @@ -7,7 +7,7 @@ export const onRequest: PagesFunction = async ({ params, request, next }) => { try { const { index } = params const collectionAddress = index?.toString() - return getMetadataRequest(res, request.url, () => getCollection(collectionAddress, request.url)) + return getMetadataRequest(res, request, () => getCollection(collectionAddress, request.url)) } catch (e) { return res } diff --git a/functions/tokens/[[index]].ts b/functions/tokens/[[index]].ts index e70b11257f7..da2dce5ca91 100644 --- a/functions/tokens/[[index]].ts +++ b/functions/tokens/[[index]].ts @@ -11,7 +11,7 @@ export const onRequest: PagesFunction = async ({ params, request, next }) => { if (!tokenAddress) { return res } - return getMetadataRequest(res, request.url, () => getToken(networkName, tokenAddress, request.url)) + return getMetadataRequest(res, request, () => getToken(networkName, tokenAddress, request.url)) } catch (e) { return res } diff --git a/functions/utils/getRequest.ts b/functions/utils/getRequest.ts index 4f5c9d15f02..d64864c791b 100644 --- a/functions/utils/getRequest.ts +++ b/functions/utils/getRequest.ts @@ -4,13 +4,13 @@ import { Data } from './cache' export async function getMetadataRequest( res: Promise, - url: string, + request: Request, getData: () => Promise ) { try { - const cachedData = await getRequest(url, getData, (data): data is Data => true) + const cachedData = await getRequest(request.url, getData, (data): data is Data => true) if (cachedData) { - return new HTMLRewriter().on('head', new MetaTagInjector(cachedData)).transform(await res) + return new HTMLRewriter().on('head', new MetaTagInjector(cachedData, request)).transform(await res) } else { return res } diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 17d3023a059..87f9cd61153 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -162,6 +162,12 @@ export default function App() { return null } + const blockedPaths = document.querySelector('meta[name="x:blocked-paths"]')?.getAttribute('content')?.split(',') + const shouldBlockPath = blockedPaths?.includes(pathname) ?? false + if (shouldBlockPath && pathname !== '/swap') { + return + } + return ( From 1be62f0bec2ef975cab449494940a1f1cfc16a24 Mon Sep 17 00:00:00 2001 From: cartcrom <39385577+cartcrom@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:34:23 -0400 Subject: [PATCH 09/61] feat: updated slippage ui (#7409) * feat: updated slippage ui * fix: update settings to also have period in max slippage string * test: update e2e test search string --- cypress/e2e/swap/settings.test.ts | 4 +- .../MaxSlippageSettings/index.test.tsx | 6 +- .../Settings/MaxSlippageSettings/index.tsx | 19 +- src/components/Settings/MenuButton/index.tsx | 4 +- src/components/swap/MaxSlippageTooltip.tsx | 53 + src/components/swap/SwapDetailsDropdown.tsx | 4 +- src/components/swap/SwapLineItem.tsx | 46 +- src/components/swap/SwapModalFooter.tsx | 5 +- .../SwapDetailsDropdown.test.tsx.snap | 62 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 1090 ++++++++++++++--- .../SwapModalFooter.test.tsx.snap | 130 +- src/utils/formatNumbers.test.ts | 20 +- src/utils/formatNumbers.ts | 1 - 13 files changed, 1208 insertions(+), 236 deletions(-) create mode 100644 src/components/swap/MaxSlippageTooltip.tsx diff --git a/cypress/e2e/swap/settings.test.ts b/cypress/e2e/swap/settings.test.ts index 6538cb5d58e..9d878bbe7b4 100644 --- a/cypress/e2e/swap/settings.test.ts +++ b/cypress/e2e/swap/settings.test.ts @@ -6,7 +6,7 @@ describe('Swap settings', () => { cy.contains('Settings').should('not.exist') cy.get(getTestSelector('open-settings-dialog-button')).click() cy.get(getTestSelector('mobile-settings-menu')).should('not.exist') - cy.contains('Max slippage').should('exist') + cy.contains('Max. slippage').should('exist') cy.contains('Transaction deadline').should('exist') cy.contains('UniswapX').should('exist') cy.contains('Local routing').should('exist') @@ -26,7 +26,7 @@ describe('Swap settings', () => { cy.get(getTestSelector('mobile-settings-menu')) .should('exist') .within(() => { - cy.contains('Max slippage').should('exist') + cy.contains('Max. slippage').should('exist') cy.contains('UniswapX').should('exist') cy.contains('Local routing').should('exist') cy.contains('Transaction deadline').should('exist') diff --git a/src/components/Settings/MaxSlippageSettings/index.test.tsx b/src/components/Settings/MaxSlippageSettings/index.test.tsx index bfafb169550..ef520fc6264 100644 --- a/src/components/Settings/MaxSlippageSettings/index.test.tsx +++ b/src/components/Settings/MaxSlippageSettings/index.test.tsx @@ -46,7 +46,7 @@ describe('MaxSlippageSettings', () => { fireEvent.change(getSlippageInput(), { target: { value: '0.5' } }) - expect(screen.queryAllByText('0.50%').length).toEqual(1) + expect(screen.queryAllByText('0.5%').length).toEqual(1) }) it('updates input value on blur with the slippage in store', () => { renderSlippageSettings() @@ -56,7 +56,7 @@ describe('MaxSlippageSettings', () => { fireEvent.change(input, { target: { value: '0.5' } }) fireEvent.blur(input) - expect(input.value).toBe('0.50') + expect(input.value).toBe('0.5') }) it('clears errors on blur and overwrites incorrect value with the latest correct value', () => { renderSlippageSettings() @@ -68,7 +68,7 @@ describe('MaxSlippageSettings', () => { fireEvent.change(input, { target: { value: '500' } }) fireEvent.blur(input) - expect(input.value).toBe('50.00') + expect(input.value).toBe('50') }) it('does not allow to enter more than 2 digits after the decimal point', () => { renderSlippageSettings() diff --git a/src/components/Settings/MaxSlippageSettings/index.tsx b/src/components/Settings/MaxSlippageSettings/index.tsx index 6710372d257..f65251befa4 100644 --- a/src/components/Settings/MaxSlippageSettings/index.tsx +++ b/src/components/Settings/MaxSlippageSettings/index.tsx @@ -8,6 +8,7 @@ import { useUserSlippageTolerance } from 'state/user/hooks' import { SlippageTolerance } from 'state/user/types' import styled from 'styled-components' import { CautionTriangle, ThemedText } from 'theme/components' +import { useFormatter } from 'utils/formatNumbers' import { Input, InputContainer } from '../Input' @@ -37,15 +38,23 @@ const NUMBER_WITH_MAX_TWO_DECIMAL_PLACES = /^(?:\d*\.\d{0,2}|\d+)$/ const MINIMUM_RECOMMENDED_SLIPPAGE = new Percent(5, 10_000) const MAXIMUM_RECOMMENDED_SLIPPAGE = new Percent(1, 100) +function useFormatSlippageInput() { + const { formatSlippage } = useFormatter() + + return (slippage: Percent) => formatSlippage(slippage).slice(0, -1) // remove % sign +} + export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Percent }) { const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance() + const { formatSlippage } = useFormatter() + const formatSlippageInput = useFormatSlippageInput() // In order to trigger `custom` mode, we need to set `userSlippageTolerance` to a value that is not `auto`. // To do so, we use `autoSlippage` value. However, since users are likely to change that value, // we render it as a placeholder instead of a value. const defaultSlippageInputValue = userSlippageTolerance !== SlippageTolerance.Auto && !userSlippageTolerance.equalTo(autoSlippage) - ? userSlippageTolerance.toFixed(2) + ? formatSlippageInput(userSlippageTolerance) : '' // If user has previously entered a custom slippage, we want to show that value in the input field @@ -101,7 +110,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe header={ - Max slippage + Max. slippage Auto ) : ( - `${userSlippageTolerance.toFixed(2)}%` + formatSlippage(userSlippageTolerance) )} } @@ -149,7 +158,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe parseSlippageInput(e.target.value)} onBlur={() => { @@ -167,7 +176,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe {tooLow ? ( - Slippage below {MINIMUM_RECOMMENDED_SLIPPAGE.toFixed(2)}% may result in a failed transaction + Slippage below {formatSlippage(MINIMUM_RECOMMENDED_SLIPPAGE)} may result in a failed transaction ) : ( Your transaction may be frontrun and result in an unfavorable trade. diff --git a/src/components/Settings/MenuButton/index.tsx b/src/components/Settings/MenuButton/index.tsx index aac9e546984..25f82e7992c 100644 --- a/src/components/Settings/MenuButton/index.tsx +++ b/src/components/Settings/MenuButton/index.tsx @@ -5,6 +5,7 @@ import { useUserSlippageTolerance } from 'state/user/hooks' import { SlippageTolerance } from 'state/user/types' import styled from 'styled-components' import { ThemedText } from 'theme/components' +import { useFormatter } from 'utils/formatNumbers' import validateUserSlippageTolerance, { SlippageValidationResult } from 'utils/validateUserSlippageTolerance' const Icon = styled(Settings)` @@ -46,6 +47,7 @@ const IconContainerWithSlippage = styled(IconContainer)<{ displayWarning?: boole const ButtonContent = () => { const [userSlippageTolerance] = useUserSlippageTolerance() + const { formatSlippage } = useFormatter() if (userSlippageTolerance === SlippageTolerance.Auto) { return ( @@ -60,7 +62,7 @@ const ButtonContent = () => { return ( - {userSlippageTolerance.toFixed(2)}% slippage + {formatSlippage(userSlippageTolerance)} slippage diff --git a/src/components/swap/MaxSlippageTooltip.tsx b/src/components/swap/MaxSlippageTooltip.tsx new file mode 100644 index 00000000000..1adef89ebdb --- /dev/null +++ b/src/components/swap/MaxSlippageTooltip.tsx @@ -0,0 +1,53 @@ +import { Trans } from '@lingui/macro' +import { Percent, TradeType } from '@uniswap/sdk-core' +import Column from 'components/Column' +import { RowBetween } from 'components/Row' +import { InterfaceTrade } from 'state/routing/types' +import { ExternalLink, Separator, ThemedText } from 'theme/components' +import { NumberType, useFormatter } from 'utils/formatNumbers' + +const ExactInMessage = ({ amount }: { amount: string }) => ( + + If the price moves so that you will receive less than {amount}, your transaction will be reverted. This is the + minimum amount you are guaranteed to receive. + +) + +const ExactOutMessage = ({ amount }: { amount: string }) => ( + + If the price moves so that you will pay more than {amount}, your transaction will be reverted. This is the maximum + amount you are guaranteed to pay. + +) + +function SlippageHeader({ amount, isExactIn }: { amount: string; isExactIn: boolean }) { + return ( + + + {isExactIn ? Receive at least : Pay at most} + + {amount} + + ) +} + +export function MaxSlippageTooltip({ trade, allowedSlippage }: { trade: InterfaceTrade; allowedSlippage: Percent }) { + const isExactIn = trade.tradeType === TradeType.EXACT_INPUT + const amount = isExactIn ? trade.minimumAmountOut(allowedSlippage) : trade.maximumAmountIn(allowedSlippage) + + const formattedAmount = useFormatter().formatCurrencyAmount({ amount, type: NumberType.SwapDetailsAmount }) + const displayAmount = `${formattedAmount} ${amount.currency.symbol}` + + return ( + + + +
+ {isExactIn ? : }{' '} + + Learn more + +
+
+ ) +} diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index 6c6ca849f6f..6e412449a6d 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -108,11 +108,9 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) { + - - - diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index 58548de1303..8b500822ea1 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -11,12 +11,15 @@ import { useIsMobile } from 'nft/hooks' import React, { PropsWithChildren, useEffect, useState } from 'react' import { InterfaceTrade, TradeFillType } from 'state/routing/types' import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils' +import { useUserSlippageTolerance } from 'state/user/hooks' +import { SlippageTolerance } from 'state/user/types' import styled, { DefaultTheme } from 'styled-components' import { ExternalLink, ThemedText } from 'theme/components' import { NumberType, useFormatter } from 'utils/formatNumbers' import { getPriceImpactColor } from 'utils/prices' import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip' +import { MaxSlippageTooltip } from './MaxSlippageTooltip' import SwapRoute from './SwapRoute' export enum SwapLineItemType { @@ -25,9 +28,9 @@ export enum SwapLineItemType { INPUT_TOKEN_FEE_ON_TRANSFER, OUTPUT_TOKEN_FEE_ON_TRANSFER, PRICE_IMPACT, + MAX_SLIPPAGE, MAXIMUM_INPUT, MINIMUM_OUTPUT, - EXPECTED_OUTPUT, ROUTING_INFO, } @@ -43,6 +46,18 @@ const ColorWrapper = styled.span<{ textColor?: keyof DefaultTheme }>` ${({ textColor, theme }) => textColor && `color: ${theme[textColor]};`} ` +const AutoBadge = styled(ThemedText.LabelMicro).attrs({ fontWeight: 535 })` + background: ${({ theme }) => theme.surface3}; + border-radius: 8px; + color: ${({ theme }) => theme.neutral2}; + height: 20px; + padding: 0 6px; + + ::after { + content: '${t`Auto`}'; + } +` + function FOTTooltipContent() { return ( <> @@ -91,7 +106,8 @@ type LineItemData = { function useLineItem(props: SwapLineItemProps): LineItemData | undefined { const { trade, syncing, allowedSlippage, type } = props - const { formatNumber } = useFormatter() + const { formatNumber, formatSlippage } = useFormatter() + const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto const isUniswapX = isUniswapXTrade(trade) const isPreview = isPreviewTrade(trade) @@ -132,10 +148,20 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { TooltipBody: () => The impact your trade has on the market price of this pool., Value: () => (isPreview ? : ), } + case SwapLineItemType.MAX_SLIPPAGE: + return { + Label: () => Max. slippage, + TooltipBody: () => , + Value: () => ( + + {isAutoSlippage && } {formatSlippage(allowedSlippage)} + + ), + } case SwapLineItemType.MAXIMUM_INPUT: if (trade.tradeType === TradeType.EXACT_INPUT) return return { - Label: () => Maximum input, + Label: () => Pay at most, TooltipBody: () => ( The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will @@ -148,7 +174,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { case SwapLineItemType.MINIMUM_OUTPUT: if (trade.tradeType === TradeType.EXACT_OUTPUT) return return { - Label: () => Minimum output, + Label: () => Receive at least, TooltipBody: () => ( The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will @@ -158,18 +184,6 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { Value: () => , loaderWidth: 70, } - case SwapLineItemType.EXPECTED_OUTPUT: - return { - Label: () => Expected output, - TooltipBody: () => ( - - The amount you expect to receive at the current market price. You may receive less or more if the market - price changes while your transaction is pending. - - ), - Value: () => , - loaderWidth: 65, - } case SwapLineItemType.ROUTING_INFO: if (isPreview) return { Label: () => Order routing, Value: () => } return { diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 76e4e691835..86f5155c9df 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -74,10 +74,11 @@ export default function SwapModalFooter({ - - + + + {showAcceptChanges ? ( diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index 50660d221c6..9531da7d201 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -43,6 +43,24 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` gap: 4px; } +.c21 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + .c4 { -webkit-box-pack: justify; -webkit-justify-content: space-between; @@ -138,6 +156,18 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` color: #7D7D7D; } +.c22 { + background: #22222212; + border-radius: 8px; + color: #7D7D7D; + height: 20px; + padding: 0 6px; +} + +.c22::after { + content: 'Auto'; +} + .c8 { background-color: transparent; border: none; @@ -329,28 +359,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` class="c9 c19 css-142zc9n" data-testid="swap-li-label" > - Minimum output - -
-
-
- 0.00000000000000098 DEF -
-
-
- -
-
- Expected output + Max. slippage
- 0.000000000000001 DEF +
+
+ 2% +
diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 111ab24d815..77a6e7bb586 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -263,6 +263,24 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` justify-content: flex-start; } +.c7 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + .c2 { -webkit-box-pack: justify; -webkit-justify-content: space-between; @@ -274,11 +292,36 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` color: #222222; } -.c9 { +.c8 { color: #7D7D7D; } -.c7 { +.c14 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c14:hover { + opacity: 0.6; +} + +.c14:active { + opacity: 0.4; +} + +.c13 { + width: 100%; + height: 1px; + background-color: #22222212; +} + +.c10 { z-index: 1070; pointer-events: none; visibility: hidden; @@ -293,13 +336,13 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` height: inherit; } -.c10 { +.c15 { width: 8px; height: 8px; z-index: 9998; } -.c10::before { +.c15::before { position: absolute; width: 8px; height: 8px; @@ -313,43 +356,43 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` background: #FFFFFF; } -.c10.arrow-top { +.c15.arrow-top { bottom: -4px; } -.c10.arrow-top::before { +.c15.arrow-top::before { border-top: none; border-left: none; } -.c10.arrow-bottom { +.c15.arrow-bottom { top: -4px; } -.c10.arrow-bottom::before { +.c15.arrow-bottom::before { border-bottom: none; border-right: none; } -.c10.arrow-left { +.c15.arrow-left { right: -4px; } -.c10.arrow-left::before { +.c15.arrow-left::before { border-bottom: none; border-left: none; } -.c10.arrow-right { +.c15.arrow-right { left: -4px; } -.c10.arrow-right::before { +.c15.arrow-right::before { border-right: none; border-top: none; } -.c8 { +.c11 { max-width: 256px; width: calc(100vw - 16px); cursor: default; @@ -366,6 +409,21 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } +.c12 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 4px; +} + .c6 { text-align: right; overflow-wrap: break-word; @@ -376,6 +434,18 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` color: #7D7D7D; } +.c9 { + background: #22222212; + border-radius: 8px; + color: #7D7D7D; + height: 20px; + padding: 0 6px; +} + +.c9::after { + content: 'Auto'; +} + @supports (-webkit-background-clip:text) and (-webkit-text-fill-color:transparent) { } @@ -387,7 +457,7 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage
- 0.0000000000000009 DEF +
+
+ 2% +
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+ Receive at least +
+
+ 0.0000000000000009 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.0000000000000009 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
@@ -566,7 +674,7 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
- 0.000000000000001 DEF + 0.0000000000000009 DEF
@@ -589,7 +697,7 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
@@ -1455,7 +1633,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage
- 0.00000000000000098 DEF +
+
+ 2% +
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+ Receive at least +
+
+ 0.00000000000000098 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
@@ -1630,7 +1846,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
- 0.000000000000001 DEF + 0.00000000000000098 DEF
@@ -1653,7 +1869,7 @@ exports[`SwapLineItem.tsx exact input 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
@@ -2794,7 +3080,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage
- 0.00000000000000098 DEF +
+
+ 2% +
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+ Receive at least +
+
+ 0.00000000000000098 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
@@ -2969,7 +3293,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
- 0.000000000000001 DEF + 0.00000000000000098 DEF
@@ -2992,7 +3316,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
@@ -4133,7 +4527,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Maximum input + Max. slippage
- 0.00000000000000102 ABC +
+
+ 2% +
- The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert. +
+
+
+ Pay at most +
+
+ 0.00000000000000102 ABC +
+
+
+
+ If the price moves so that you will pay more than 0.00000000000000102 ABC, your transaction will be reverted. This is the maximum amount you are guaranteed to pay. + + Learn more + +
+
@@ -4308,7 +4740,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Pay at most
- 0.000000000000001 GHI + 0.00000000000000102 ABC
@@ -4331,7 +4763,7 @@ exports[`SwapLineItem.tsx exact output 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert.
@@ -5678,7 +6180,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage
- 0.000000000000000952 DEF +
+
+ 2% +
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+ Receive at least +
+
+ 0.000000000000000952 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
@@ -5853,7 +6393,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
- 0.00000000000000097 DEF + 0.000000000000000952 DEF
@@ -5876,7 +6416,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
@@ -7223,7 +7833,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage
- 0.000000000000000952 DEF +
+
+ 2% +
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+ Receive at least +
+
+ 0.000000000000000952 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
@@ -7398,7 +8046,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
- 0.00000000000000097 DEF + 0.000000000000000952 DEF
@@ -7421,7 +8069,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
@@ -8566,7 +9284,7 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage
- 0.00000000000000098 DEF +
+
+ 2% +
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+ Receive at least +
+
+ 0.00000000000000098 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
@@ -8741,7 +9497,7 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
- 0.000000000000001 DEF + 0.00000000000000098 DEF
@@ -8764,7 +9520,7 @@ exports[`SwapLineItem.tsx preview exact in 1`] = `
- The amount you expect to receive at the current market price. You may receive less or more if the market price changes while your transaction is pending. + The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert.
- Minimum output + Max. slippage
.c0 { @@ -9103,7 +9859,7 @@ exports[`SwapLineItem.tsx syncing 1`] = ` will-change: background-position; border-radius: 12px; height: 15px; - width: 65px; + width: 70px; } .c4 { @@ -9118,13 +9874,13 @@ exports[`SwapLineItem.tsx syncing 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Expected output + Receive at least
.c0 { diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index 61af8963a5a..3b67ae5e5b2 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -26,6 +26,24 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = } .c10 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + +.c13 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -54,6 +72,10 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = color: #222222; } +.c11 { + color: #7D7D7D; +} + .c0 { display: -webkit-box; display: -webkit-flex; @@ -89,6 +111,18 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = color: #7D7D7D; } +.c12 { + background: #22222212; + border-radius: 8px; + color: #7D7D7D; + height: 20px; + padding: 0 6px; +} + +.c12::after { + content: 'Auto'; +} + .c1 { padding: 0 8px; } @@ -143,7 +177,35 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = class="c5 c8 css-142zc9n" data-testid="swap-li-label" > - Minimum output + Max. slippage + +
+
+
+
+
+ 2% +
+
+
+
+
+
+
+ Receive at least
gas cost icon - Minimum output + Max. slippage +
+
+
+
+
+
+ 2% +
+
+
+
+
+
+
+ Receive at least
{ expect(formatSlippage(undefined)).toBe('-') }) - it('correctly formats a percent with 3 significant digits', () => { + it('correctly formats a percent with no trailing digits', () => { const { formatSlippage } = renderHook(() => useFormatter()).result.current expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%') - expect(formatSlippage(new Percent(1, 1000))).toBe('0.100%') - expect(formatSlippage(new Percent(1, 100))).toBe('1.000%') - expect(formatSlippage(new Percent(1, 10))).toBe('10.000%') - expect(formatSlippage(new Percent(1, 1))).toBe('100.000%') + expect(formatSlippage(new Percent(1, 1000))).toBe('0.1%') + expect(formatSlippage(new Percent(1, 100))).toBe('1%') + expect(formatSlippage(new Percent(1, 10))).toBe('10%') + expect(formatSlippage(new Percent(1, 1))).toBe('100%') }) - it('correctly formats a percent with 3 significant digits with french locale', () => { + it('correctly formats a percent with french locale', () => { mocked(useActiveLocale).mockReturnValue('fr-FR') const { formatSlippage } = renderHook(() => useFormatter()).result.current expect(formatSlippage(new Percent(1, 100000))).toBe('0,001%') - expect(formatSlippage(new Percent(1, 1000))).toBe('0,100%') - expect(formatSlippage(new Percent(1, 100))).toBe('1,000%') - expect(formatSlippage(new Percent(1, 10))).toBe('10,000%') - expect(formatSlippage(new Percent(1, 1))).toBe('100,000%') + expect(formatSlippage(new Percent(1, 1000))).toBe('0,1%') + expect(formatSlippage(new Percent(1, 100))).toBe('1%') + expect(formatSlippage(new Percent(1, 10))).toBe('10%') + expect(formatSlippage(new Percent(1, 1))).toBe('100%') }) }) diff --git a/src/utils/formatNumbers.ts b/src/utils/formatNumbers.ts index bf986679401..22ff6290c4b 100644 --- a/src/utils/formatNumbers.ts +++ b/src/utils/formatNumbers.ts @@ -472,7 +472,6 @@ function formatSlippage(slippage: Percent | undefined, locale: SupportedLocale = if (!slippage) return '-' return `${Number(slippage.toFixed(3)).toLocaleString(locale, { - minimumFractionDigits: 3, maximumFractionDigits: 3, useGrouping: false, })}%` From 0e3d188a9ab7da8ad792d26f9033a9cc7a6e80e4 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Thu, 5 Oct 2023 15:48:02 -0400 Subject: [PATCH 10/61] feat: add feature flags settings overrides box (#7406) * add feature flags settings overrides box * cat * useGate hook monstrosity * pr changes * exclude devflagsbox from code cov * pr review * mobile bottom bar * nit * initialize atom * fix atom initialization * remove comments * fix devbox initialization * nit remove diff --- codecov.yml | 1 + src/components/TopLevelModals/index.tsx | 4 ++ src/dev/DevFlagsBox.tsx | 67 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/dev/DevFlagsBox.tsx diff --git a/codecov.yml b/codecov.yml index db921b0b004..c865e13f93b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,6 +9,7 @@ ignore: - "**/styled.tsx" - "**/constants/**/*" - "constants/**/*" + - "src/dev/*" coverage: status: diff --git a/src/components/TopLevelModals/index.tsx b/src/components/TopLevelModals/index.tsx index daf41444a8d..44bddf38131 100644 --- a/src/components/TopLevelModals/index.tsx +++ b/src/components/TopLevelModals/index.tsx @@ -6,11 +6,13 @@ import BaseAnnouncementBanner from 'components/Banner/BaseAnnouncementBanner' import AddressClaimModal from 'components/claim/AddressClaimModal' import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked' import FiatOnrampModal from 'components/FiatOnrampModal' +import DevFlagsBox from 'dev/DevFlagsBox' import useAccountRiskCheck from 'hooks/useAccountRiskCheck' import Bag from 'nft/components/bag/Bag' import TransactionCompleteModal from 'nft/components/collection/TransactionCompleteModal' import { useModalIsOpen, useToggleModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' +import { isDevelopmentEnv, isStagingEnv } from 'utils/env' export default function TopLevelModals() { const addressClaimOpen = useModalIsOpen(ApplicationModal.ADDRESS_CLAIM) @@ -19,6 +21,7 @@ export default function TopLevelModals() { const { account } = useWeb3React() useAccountRiskCheck(account) const accountBlocked = Boolean(blockedAccountModalOpen && account) + const shouldShowDevFlags = isDevelopmentEnv() || isStagingEnv() return ( <> @@ -31,6 +34,7 @@ export default function TopLevelModals() { + {shouldShowDevFlags && } ) } diff --git a/src/dev/DevFlagsBox.tsx b/src/dev/DevFlagsBox.tsx new file mode 100644 index 00000000000..f8d60abbac0 --- /dev/null +++ b/src/dev/DevFlagsBox.tsx @@ -0,0 +1,67 @@ +import { BaseVariant, FeatureFlag, featureFlagSettings as featureFlagSettingsAtom } from 'featureFlags' +import { useAtomValue } from 'jotai/utils' +import { useMemo, useState } from 'react' +import { useGate } from 'statsig-react' +import styled from 'styled-components' +import { ThemedText } from 'theme/components' +import { isDevelopmentEnv, isStagingEnv } from 'utils/env' + +const Box = styled.div` + position: fixed; + bottom: 20px; + left: 20px; + background-color: ${({ theme }) => theme.surface1}; + padding: 10px; + border: 1px solid ${({ theme }) => theme.accent1}; + z-index: 1000; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + bottom: 70px; + } +` +const TopBar = styled.div` + display: flex; + justify-content: space-between; +` +const Gate = (flagName: string, featureFlagSettings: Record) => { + const gateResult = useGate(flagName) + if (gateResult) { + const { value: statsigValue }: { value: boolean } = gateResult + const flagSetting = featureFlagSettings[flagName] + const settingValue: boolean = flagSetting === BaseVariant.Enabled + if (flagSetting && statsigValue !== settingValue) { + return ( + + {flagName}: {flagSetting} + + ) + } + } + return null +} + +export default function DevFlagsBox() { + const featureFlagsAtom = useAtomValue(featureFlagSettingsAtom) + const featureFlags = useMemo(() => Object.values(FeatureFlag), []) + + const overrides = featureFlags.map((flagName) => Gate(flagName, featureFlagsAtom)) + const hasOverrides = overrides.some((g) => g !== null) + + const [isOpen, setIsOpen] = useState(true) + const toggleOpen = () => setIsOpen((open) => !open) + + return ( + + + {isOpen ? '😺👇' : '😿☝️'} + {isOpen && ( + + {isStagingEnv() && 'Staging build overrides'} + {isDevelopmentEnv() && 'Development build overrides'} + + )} + + {isOpen && (hasOverrides ? overrides : No overrides)} + + ) +} From 2d8dac5c1571ef5155a37214f69e62b863ca4397 Mon Sep 17 00:00:00 2001 From: Connor McEwen Date: Thu, 5 Oct 2023 16:30:57 -0400 Subject: [PATCH 11/61] fix: merge issue (#7427) * fix: merge issue * update snapshots --- src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap | 4 ++-- src/pages/App.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 77a6e7bb586..26748908c8d 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -5756,7 +5756,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` - 3.000% + 3%
@@ -7409,7 +7409,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` - 3.000% + 3%
diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 87f9cd61153..c0e6d41cb88 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -8,7 +8,7 @@ import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useAtom } from 'jotai' import { useBag } from 'nft/hooks/useBag' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' -import { Route, Routes, useLocation, useSearchParams } from 'react-router-dom' +import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' import { useRouterPreference } from 'state/user/hooks' import { StatsigProvider, StatsigUser } from 'statsig-react' From 932c4482d26cd33a3a45f9cb272b3588afed4c8f Mon Sep 17 00:00:00 2001 From: cartcrom <39385577+cartcrom@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:25:53 -0400 Subject: [PATCH 12/61] feat: updated rate/routing tooltips (#7412) * feat: updated routing tooltips * refactor: gas price formatting * fit: boolean rendering --- .../RoutingDiagram/RoutingDiagram.tsx | 3 +- src/components/swap/SwapLineItem.tsx | 22 +- src/components/swap/SwapRoute.tsx | 109 +-- .../SwapDetailsDropdown.test.tsx.snap | 44 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 749 ++++++++---------- .../SwapModalFooter.test.tsx.snap | 171 +++- 6 files changed, 556 insertions(+), 542 deletions(-) diff --git a/src/components/RoutingDiagram/RoutingDiagram.tsx b/src/components/RoutingDiagram/RoutingDiagram.tsx index 5813f988817..52025669280 100644 --- a/src/components/RoutingDiagram/RoutingDiagram.tsx +++ b/src/components/RoutingDiagram/RoutingDiagram.tsx @@ -14,7 +14,7 @@ import { Z_INDEX } from 'theme/zIndex' import { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries' import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg' -import { MouseoverTooltip } from '../Tooltip' +import { MouseoverTooltip, TooltipSize } from '../Tooltip' const Wrapper = styled(Box)` align-items: center; @@ -142,6 +142,7 @@ function Pool({ currency0, currency1, feeAmount }: { currency0: Currency; curren return ( {tokenInfo0?.symbol + '/' + tokenInfo1?.symbol + ' ' + feeAmount / 10000}% pool
} + size={TooltipSize.ExtraSmall} > diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index 8b500822ea1..397975b91a1 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -20,7 +20,8 @@ import { getPriceImpactColor } from 'utils/prices' import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip' import { MaxSlippageTooltip } from './MaxSlippageTooltip' -import SwapRoute from './SwapRoute' +import { RoutingTooltip, SwapRoute } from './SwapRoute' +import TradePrice from './TradePrice' export enum SwapLineItemType { EXCHANGE_RATE, @@ -76,15 +77,6 @@ function Loading({ width = 50 }: { width?: number }) { return } -function ExchangeRateRow({ trade }: { trade: InterfaceTrade }) { - const { formatNumber } = useFormatter() - const rate = `1 ${trade.executionPrice.quoteCurrency.symbol} = ${formatNumber({ - input: parseFloat(trade.executionPrice.toFixed(9)), - type: NumberType.TokenTx, - })} ${trade.executionPrice.baseCurrency.symbol}` - return <>{rate} -} - function ColoredPercentRow({ percent }: { percent: Percent }) { const { formatSlippage } = useFormatter() return {formatSlippage(percent)} @@ -122,8 +114,10 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { switch (type) { case SwapLineItemType.EXCHANGE_RATE: return { - Label: () => Exchange rate, - Value: () => , + Label: () => Rate, + Value: () => , + TooltipBody: !isPreview ? () => : undefined, + tooltipSize: isUniswapX ? TooltipSize.Small : TooltipSize.Large, } case SwapLineItemType.NETWORK_COST: if (!SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)) return @@ -185,12 +179,12 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { loaderWidth: 70, } case SwapLineItemType.ROUTING_INFO: - if (isPreview) return { Label: () => Order routing, Value: () => } + if (isPreview || syncing) return { Label: () => Order routing, Value: () => } return { Label: () => Order routing, TooltipBody: () => { if (isUniswapX) return - return + return }, tooltipSize: isUniswapX ? TooltipSize.Small : TooltipSize.Large, Value: () => , diff --git a/src/components/swap/SwapRoute.tsx b/src/components/swap/SwapRoute.tsx index c2de7d568e8..9fbebb663b8 100644 --- a/src/components/swap/SwapRoute.tsx +++ b/src/components/swap/SwapRoute.tsx @@ -1,64 +1,83 @@ import { Trans } from '@lingui/macro' -import { useWeb3React } from '@web3-react/core' import Column from 'components/Column' -import { LoadingRows } from 'components/Loader/styled' import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram' +import { RowBetween } from 'components/Row' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import useAutoRouterSupported from 'hooks/useAutoRouterSupported' -import { ClassicTrade } from 'state/routing/types' +import { ClassicTrade, SubmittableTrade } from 'state/routing/types' +import { isClassicTrade } from 'state/routing/utils' import { Separator, ThemedText } from 'theme/components' +import { NumberType, useFormatter } from 'utils/formatNumbers' import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries' import RouterLabel from '../RouterLabel' +import { UniswapXDescription } from './GasBreakdownTooltip' -export default function SwapRoute({ trade, syncing }: { trade: ClassicTrade; syncing: boolean }) { - const { chainId } = useWeb3React() - const autoRouterSupported = useAutoRouterSupported() +// TODO(WEB-2022) +// Can `trade.gasUseEstimateUSD` be defined when `chainId` is not in `SUPPORTED_GAS_ESTIMATE_CHAIN_IDS`? +function useGasPrice({ gasUseEstimateUSD, inputAmount }: ClassicTrade) { + const { formatNumber } = useFormatter() + if (!gasUseEstimateUSD || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(inputAmount.currency.chainId)) return undefined - const routes = getRoutingDiagramEntries(trade) + return gasUseEstimateUSD === 0 ? '<$0.01' : formatNumber({ input: gasUseEstimateUSD, type: NumberType.FiatGasPrice }) +} - const gasPrice = - // TODO(WEB-2022) - // Can `trade.gasUseEstimateUSD` be defined when `chainId` is not in `SUPPORTED_GAS_ESTIMATE_CHAIN_IDS`? - trade.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) - ? trade.gasUseEstimateUSD === 0 - ? '<$0.01' - : '$' + trade.gasUseEstimateUSD.toFixed(2) - : undefined +function RouteLabel({ trade }: { trade: SubmittableTrade }) { + return ( + + Order Routing + + + ) +} +function PriceImpactRow({ trade }: { trade: ClassicTrade }) { + const { formatPriceImpact } = useFormatter() return ( + + + Price Impact +
{formatPriceImpact(trade.priceImpact)}
+
+
+ ) +} + +export function RoutingTooltip({ trade }: { trade: SubmittableTrade }) { + return isClassicTrade(trade) ? ( + + + + + + + ) : ( - + - {syncing ? ( - -
- - ) : ( - - )} - {autoRouterSupported && ( - <> - - {syncing ? ( - -
- - ) : ( - - {gasPrice ? Best price route costs ~{gasPrice} in gas. : null}{' '} - - This route optimizes your total output by considering split routes, multiple hops, and the gas cost of - each step. - - - )} - - )} + + + ) +} + +export function SwapRoute({ trade }: { trade: ClassicTrade }) { + const { inputAmount, outputAmount } = trade + const routes = getRoutingDiagramEntries(trade) + const gasPrice = useGasPrice(trade) + + return useAutoRouterSupported() ? ( + + + + {Boolean(gasPrice) && Best price route costs ~{gasPrice} in gas. } + {Boolean(gasPrice) && ' '} + + This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each + step. + + + ) : ( + ) } diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index 9531da7d201..0391cdb5f22 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -146,28 +146,6 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` fill: #7D7D7D; } -.c20 { - text-align: right; - overflow-wrap: break-word; -} - -.c19 { - cursor: help; - color: #7D7D7D; -} - -.c22 { - background: #22222212; - border-radius: 8px; - color: #7D7D7D; - height: 20px; - padding: 0 6px; -} - -.c22::after { - content: 'Auto'; -} - .c8 { background-color: transparent; border: none; @@ -200,6 +178,28 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` user-select: text; } +.c20 { + text-align: right; + overflow-wrap: break-word; +} + +.c19 { + cursor: help; + color: #7D7D7D; +} + +.c22 { + background: #22222212; + border-radius: 8px; + color: #7D7D7D; + height: 20px; + padding: 0 6px; +} + +.c22::after { + content: 'Auto'; +} + .c5 { padding: 0; -webkit-align-items: center; diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 26748908c8d..05b2fcf01b1 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -1884,7 +1884,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` min-width: 0; } -.c24 { +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -1908,7 +1908,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` justify-content: flex-start; } -.c25 { +.c24 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -1933,14 +1933,14 @@ exports[`SwapLineItem.tsx exact input 1`] = ` justify-content: space-between; } -.c26 { +.c25 { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; } -.c26 > * { +.c25 > * { margin: 1px !important; } @@ -1952,12 +1952,6 @@ exports[`SwapLineItem.tsx exact input 1`] = ` color: #7D7D7D; } -.c11 { - width: 100%; - height: 1px; - background-color: #22222212; -} - .c7 { z-index: 1070; pointer-events: none; @@ -1973,13 +1967,13 @@ exports[`SwapLineItem.tsx exact input 1`] = ` height: inherit; } -.c32 { +.c31 { width: 8px; height: 8px; z-index: 9998; } -.c32::before { +.c31::before { position: absolute; width: 8px; height: 8px; @@ -1993,47 +1987,47 @@ exports[`SwapLineItem.tsx exact input 1`] = ` background: #FFFFFF; } -.c32.arrow-top { +.c31.arrow-top { bottom: -4px; } -.c32.arrow-top::before { +.c31.arrow-top::before { border-top: none; border-left: none; } -.c32.arrow-bottom { +.c31.arrow-bottom { top: -4px; } -.c32.arrow-bottom::before { +.c31.arrow-bottom::before { border-bottom: none; border-right: none; } -.c32.arrow-left { +.c31.arrow-left { right: -4px; } -.c32.arrow-left::before { +.c31.arrow-left::before { border-bottom: none; border-left: none; } -.c32.arrow-right { +.c31.arrow-right { left: -4px; } -.c32.arrow-right::before { +.c31.arrow-right::before { border-right: none; border-top: none; } -.c31 { - max-width: 256px; +.c8 { + max-width: 400px; width: calc(100vw - 16px); cursor: default; - padding: 12px; + padding: 16px 20px; pointer-events: auto; color: #222222; font-weight: 485; @@ -2046,11 +2040,11 @@ exports[`SwapLineItem.tsx exact input 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } -.c8 { - max-width: 400px; +.c30 { + max-width: 200px; width: calc(100vw - 16px); cursor: default; - padding: 16px 20px; + padding: 8px; pointer-events: auto; color: #222222; font-weight: 485; @@ -2078,7 +2072,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` gap: 12px; } -.c20 { +.c19 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -2099,7 +2093,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` font-weight: 535; } -.c16 { +.c15 { opacity: 0; -webkit-transition: opacity 250ms ease-in; transition: opacity 250ms ease-in; @@ -2108,7 +2102,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` border-radius: 50%; } -.c15 { +.c14 { width: 20px; height: 20px; background: #22222212; @@ -2118,7 +2112,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` border-radius: 50%; } -.c14 { +.c13 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -2126,7 +2120,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` display: flex; } -.c28 { +.c27 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -2137,16 +2131,16 @@ exports[`SwapLineItem.tsx exact input 1`] = ` flex-direction: row; } -.c29 { +.c28 { z-index: 1; } -.c30 { +.c29 { position: absolute; left: -10px !important; } -.c12 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -2154,12 +2148,12 @@ exports[`SwapLineItem.tsx exact input 1`] = ` width: 100%; } -.c13 { +.c12 { display: grid; grid-template-columns: 24px 1fr 24px; } -.c17 { +.c16 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -2176,7 +2170,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` position: relative; } -.c27 { +.c26 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -2184,7 +2178,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` padding: 4px 4px; } -.c18 { +.c17 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -2199,11 +2193,11 @@ exports[`SwapLineItem.tsx exact input 1`] = ` opacity: 0.5; } -.c19 path { +.c18 path { stroke: #22222212; } -.c21 { +.c20 { background-color: #F9F9F9; border-radius: 8px; display: grid; @@ -2217,7 +2211,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` z-index: 1020; } -.c22 { +.c21 { background-color: #F9F9F9; border-radius: 4px; color: #7D7D7D; @@ -2226,7 +2220,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` z-index: 1021; } -.c23 { +.c22 { word-break: normal; } @@ -2278,66 +2272,58 @@ exports[`SwapLineItem.tsx exact input 1`] = ` class="c10" >
- Uniswap Client -
-
-
ABC logo
dot_line.svg
V3
100%
@@ -2346,45 +2332,45 @@ exports[`SwapLineItem.tsx exact input 1`] = ` >
DEF logo
ABC logo
@@ -2393,7 +2379,7 @@ exports[`SwapLineItem.tsx exact input 1`] = `
1%
@@ -2405,36 +2391,33 @@ exports[`SwapLineItem.tsx exact input 1`] = ` style="position: fixed; left: 0px; top: 0px;" >
ABC/DEF 1% pool
DEF logo
-
@@ -2444,7 +2427,7 @@ exports[`SwapLineItem.tsx exact input 1`] = `
@@ -3331,7 +3314,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` min-width: 0; } -.c24 { +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -3355,7 +3338,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` justify-content: flex-start; } -.c25 { +.c24 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -3380,14 +3363,14 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` justify-content: space-between; } -.c26 { +.c25 { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; } -.c26 > * { +.c25 > * { margin: 1px !important; } @@ -3399,12 +3382,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` color: #7D7D7D; } -.c11 { - width: 100%; - height: 1px; - background-color: #22222212; -} - .c7 { z-index: 1070; pointer-events: none; @@ -3420,13 +3397,13 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` height: inherit; } -.c32 { +.c31 { width: 8px; height: 8px; z-index: 9998; } -.c32::before { +.c31::before { position: absolute; width: 8px; height: 8px; @@ -3440,47 +3417,47 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` background: #FFFFFF; } -.c32.arrow-top { +.c31.arrow-top { bottom: -4px; } -.c32.arrow-top::before { +.c31.arrow-top::before { border-top: none; border-left: none; } -.c32.arrow-bottom { +.c31.arrow-bottom { top: -4px; } -.c32.arrow-bottom::before { +.c31.arrow-bottom::before { border-bottom: none; border-right: none; } -.c32.arrow-left { +.c31.arrow-left { right: -4px; } -.c32.arrow-left::before { +.c31.arrow-left::before { border-bottom: none; border-left: none; } -.c32.arrow-right { +.c31.arrow-right { left: -4px; } -.c32.arrow-right::before { +.c31.arrow-right::before { border-right: none; border-top: none; } -.c31 { - max-width: 256px; +.c8 { + max-width: 400px; width: calc(100vw - 16px); cursor: default; - padding: 12px; + padding: 16px 20px; pointer-events: auto; color: #222222; font-weight: 485; @@ -3493,11 +3470,11 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } -.c8 { - max-width: 400px; +.c30 { + max-width: 200px; width: calc(100vw - 16px); cursor: default; - padding: 16px 20px; + padding: 8px; pointer-events: auto; color: #222222; font-weight: 485; @@ -3525,7 +3502,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` gap: 12px; } -.c20 { +.c19 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -3546,7 +3523,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` font-weight: 535; } -.c16 { +.c15 { opacity: 0; -webkit-transition: opacity 250ms ease-in; transition: opacity 250ms ease-in; @@ -3555,7 +3532,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` border-radius: 50%; } -.c15 { +.c14 { width: 20px; height: 20px; background: #22222212; @@ -3565,7 +3542,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` border-radius: 50%; } -.c14 { +.c13 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -3573,7 +3550,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` display: flex; } -.c28 { +.c27 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -3584,16 +3561,16 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` flex-direction: row; } -.c29 { +.c28 { z-index: 1; } -.c30 { +.c29 { position: absolute; left: -10px !important; } -.c12 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -3601,12 +3578,12 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` width: 100%; } -.c13 { +.c12 { display: grid; grid-template-columns: 24px 1fr 24px; } -.c17 { +.c16 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -3623,7 +3600,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` position: relative; } -.c27 { +.c26 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -3631,7 +3608,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` padding: 4px 4px; } -.c18 { +.c17 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -3646,11 +3623,11 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` opacity: 0.5; } -.c19 path { +.c18 path { stroke: #22222212; } -.c21 { +.c20 { background-color: #F9F9F9; border-radius: 8px; display: grid; @@ -3664,7 +3641,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` z-index: 1020; } -.c22 { +.c21 { background-color: #F9F9F9; border-radius: 4px; color: #7D7D7D; @@ -3673,7 +3650,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` z-index: 1021; } -.c23 { +.c22 { word-break: normal; } @@ -3725,66 +3702,58 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` class="c10" >
- Uniswap API -
-
-
ABC logo
dot_line.svg
V3
100%
@@ -3793,45 +3762,45 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` >
DEF logo
ABC logo
@@ -3840,7 +3809,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
1%
@@ -3852,36 +3821,33 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` style="position: fixed; left: 0px; top: 0px;" >
ABC/DEF 1% pool
DEF logo
-
@@ -3891,7 +3857,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
@@ -4778,7 +4744,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` min-width: 0; } -.c24 { +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -4802,7 +4768,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` justify-content: flex-start; } -.c25 { +.c24 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -4827,14 +4793,14 @@ exports[`SwapLineItem.tsx exact output 1`] = ` justify-content: space-between; } -.c26 { +.c25 { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; } -.c26 > * { +.c25 > * { margin: 1px !important; } @@ -4846,12 +4812,6 @@ exports[`SwapLineItem.tsx exact output 1`] = ` color: #7D7D7D; } -.c11 { - width: 100%; - height: 1px; - background-color: #22222212; -} - .c7 { z-index: 1070; pointer-events: none; @@ -4867,13 +4827,13 @@ exports[`SwapLineItem.tsx exact output 1`] = ` height: inherit; } -.c32 { +.c31 { width: 8px; height: 8px; z-index: 9998; } -.c32::before { +.c31::before { position: absolute; width: 8px; height: 8px; @@ -4887,47 +4847,47 @@ exports[`SwapLineItem.tsx exact output 1`] = ` background: #FFFFFF; } -.c32.arrow-top { +.c31.arrow-top { bottom: -4px; } -.c32.arrow-top::before { +.c31.arrow-top::before { border-top: none; border-left: none; } -.c32.arrow-bottom { +.c31.arrow-bottom { top: -4px; } -.c32.arrow-bottom::before { +.c31.arrow-bottom::before { border-bottom: none; border-right: none; } -.c32.arrow-left { +.c31.arrow-left { right: -4px; } -.c32.arrow-left::before { +.c31.arrow-left::before { border-bottom: none; border-left: none; } -.c32.arrow-right { +.c31.arrow-right { left: -4px; } -.c32.arrow-right::before { +.c31.arrow-right::before { border-right: none; border-top: none; } -.c31 { - max-width: 256px; +.c8 { + max-width: 400px; width: calc(100vw - 16px); cursor: default; - padding: 12px; + padding: 16px 20px; pointer-events: auto; color: #222222; font-weight: 485; @@ -4940,11 +4900,11 @@ exports[`SwapLineItem.tsx exact output 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } -.c8 { - max-width: 400px; +.c30 { + max-width: 200px; width: calc(100vw - 16px); cursor: default; - padding: 16px 20px; + padding: 8px; pointer-events: auto; color: #222222; font-weight: 485; @@ -4972,7 +4932,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` gap: 12px; } -.c20 { +.c19 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -4993,7 +4953,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` font-weight: 535; } -.c16 { +.c15 { opacity: 0; -webkit-transition: opacity 250ms ease-in; transition: opacity 250ms ease-in; @@ -5002,7 +4962,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` border-radius: 50%; } -.c15 { +.c14 { width: 20px; height: 20px; background: #22222212; @@ -5012,7 +4972,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` border-radius: 50%; } -.c14 { +.c13 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -5020,7 +4980,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` display: flex; } -.c28 { +.c27 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -5031,16 +4991,16 @@ exports[`SwapLineItem.tsx exact output 1`] = ` flex-direction: row; } -.c29 { +.c28 { z-index: 1; } -.c30 { +.c29 { position: absolute; left: -10px !important; } -.c12 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -5048,12 +5008,12 @@ exports[`SwapLineItem.tsx exact output 1`] = ` width: 100%; } -.c13 { +.c12 { display: grid; grid-template-columns: 24px 1fr 24px; } -.c17 { +.c16 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -5070,7 +5030,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` position: relative; } -.c27 { +.c26 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -5078,7 +5038,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` padding: 4px 4px; } -.c18 { +.c17 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -5093,11 +5053,11 @@ exports[`SwapLineItem.tsx exact output 1`] = ` opacity: 0.5; } -.c19 path { +.c18 path { stroke: #22222212; } -.c21 { +.c20 { background-color: #F9F9F9; border-radius: 8px; display: grid; @@ -5111,7 +5071,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` z-index: 1020; } -.c22 { +.c21 { background-color: #F9F9F9; border-radius: 4px; color: #7D7D7D; @@ -5120,7 +5080,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` z-index: 1021; } -.c23 { +.c22 { word-break: normal; } @@ -5172,66 +5132,58 @@ exports[`SwapLineItem.tsx exact output 1`] = ` class="c10" >
- Uniswap Client -
-
-
ABC logo
dot_line.svg
V3
100%
@@ -5240,45 +5192,45 @@ exports[`SwapLineItem.tsx exact output 1`] = ` >
GHI logo
ABC logo
@@ -5287,7 +5239,7 @@ exports[`SwapLineItem.tsx exact output 1`] = `
0.3%
@@ -5299,46 +5251,43 @@ exports[`SwapLineItem.tsx exact output 1`] = ` style="position: fixed; left: 0px; top: 0px;" >
ABC/GHI 0.3% pool
GHI logo
-
- This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. + This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step.
@@ -6431,7 +6380,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` min-width: 0; } -.c24 { +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -6455,7 +6404,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` justify-content: flex-start; } -.c25 { +.c24 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -6480,14 +6429,14 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` justify-content: space-between; } -.c26 { +.c25 { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; } -.c26 > * { +.c25 > * { margin: 1px !important; } @@ -6499,12 +6448,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } -.c11 { - width: 100%; - height: 1px; - background-color: #22222212; -} - .c7 { z-index: 1070; pointer-events: none; @@ -6520,13 +6463,13 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` height: inherit; } -.c32 { +.c31 { width: 8px; height: 8px; z-index: 9998; } -.c32::before { +.c31::before { position: absolute; width: 8px; height: 8px; @@ -6540,47 +6483,47 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` background: #FFFFFF; } -.c32.arrow-top { +.c31.arrow-top { bottom: -4px; } -.c32.arrow-top::before { +.c31.arrow-top::before { border-top: none; border-left: none; } -.c32.arrow-bottom { +.c31.arrow-bottom { top: -4px; } -.c32.arrow-bottom::before { +.c31.arrow-bottom::before { border-bottom: none; border-right: none; } -.c32.arrow-left { +.c31.arrow-left { right: -4px; } -.c32.arrow-left::before { +.c31.arrow-left::before { border-bottom: none; border-left: none; } -.c32.arrow-right { +.c31.arrow-right { left: -4px; } -.c32.arrow-right::before { +.c31.arrow-right::before { border-right: none; border-top: none; } -.c31 { - max-width: 256px; +.c8 { + max-width: 400px; width: calc(100vw - 16px); cursor: default; - padding: 12px; + padding: 16px 20px; pointer-events: auto; color: #222222; font-weight: 485; @@ -6593,11 +6536,11 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } -.c8 { - max-width: 400px; +.c30 { + max-width: 200px; width: calc(100vw - 16px); cursor: default; - padding: 16px 20px; + padding: 8px; pointer-events: auto; color: #222222; font-weight: 485; @@ -6625,7 +6568,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` gap: 12px; } -.c20 { +.c19 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -6646,7 +6589,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` font-weight: 535; } -.c16 { +.c15 { opacity: 0; -webkit-transition: opacity 250ms ease-in; transition: opacity 250ms ease-in; @@ -6655,7 +6598,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` border-radius: 50%; } -.c15 { +.c14 { width: 20px; height: 20px; background: #22222212; @@ -6665,7 +6608,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` border-radius: 50%; } -.c14 { +.c13 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -6673,7 +6616,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` display: flex; } -.c28 { +.c27 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -6684,16 +6627,16 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` flex-direction: row; } -.c29 { +.c28 { z-index: 1; } -.c30 { +.c29 { position: absolute; left: -10px !important; } -.c12 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -6701,12 +6644,12 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` width: 100%; } -.c13 { +.c12 { display: grid; grid-template-columns: 24px 1fr 24px; } -.c17 { +.c16 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -6723,7 +6666,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` position: relative; } -.c27 { +.c26 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -6731,7 +6674,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` padding: 4px 4px; } -.c18 { +.c17 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -6746,11 +6689,11 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` opacity: 0.5; } -.c19 path { +.c18 path { stroke: #22222212; } -.c21 { +.c20 { background-color: #F9F9F9; border-radius: 8px; display: grid; @@ -6764,7 +6707,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` z-index: 1020; } -.c22 { +.c21 { background-color: #F9F9F9; border-radius: 4px; color: #7D7D7D; @@ -6773,7 +6716,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` z-index: 1021; } -.c23 { +.c22 { word-break: normal; } @@ -6825,66 +6768,58 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` class="c10" >
- Uniswap API -
-
-
ABC logo
dot_line.svg
V3
100%
@@ -6893,45 +6828,45 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` >
DEF logo
ABC logo
@@ -6940,7 +6875,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
1%
@@ -6952,36 +6887,33 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` style="position: fixed; left: 0px; top: 0px;" >
ABC/DEF 1% pool
DEF logo
-
@@ -6991,7 +6923,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
@@ -8084,7 +8016,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` min-width: 0; } -.c24 { +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -8108,7 +8040,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` justify-content: flex-start; } -.c25 { +.c24 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -8133,14 +8065,14 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` justify-content: space-between; } -.c26 { +.c25 { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; } -.c26 > * { +.c25 > * { margin: 1px !important; } @@ -8152,12 +8084,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -.c11 { - width: 100%; - height: 1px; - background-color: #22222212; -} - .c7 { z-index: 1070; pointer-events: none; @@ -8173,13 +8099,13 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` height: inherit; } -.c32 { +.c31 { width: 8px; height: 8px; z-index: 9998; } -.c32::before { +.c31::before { position: absolute; width: 8px; height: 8px; @@ -8193,47 +8119,47 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` background: #FFFFFF; } -.c32.arrow-top { +.c31.arrow-top { bottom: -4px; } -.c32.arrow-top::before { +.c31.arrow-top::before { border-top: none; border-left: none; } -.c32.arrow-bottom { +.c31.arrow-bottom { top: -4px; } -.c32.arrow-bottom::before { +.c31.arrow-bottom::before { border-bottom: none; border-right: none; } -.c32.arrow-left { +.c31.arrow-left { right: -4px; } -.c32.arrow-left::before { +.c31.arrow-left::before { border-bottom: none; border-left: none; } -.c32.arrow-right { +.c31.arrow-right { left: -4px; } -.c32.arrow-right::before { +.c31.arrow-right::before { border-right: none; border-top: none; } -.c31 { - max-width: 256px; +.c8 { + max-width: 400px; width: calc(100vw - 16px); cursor: default; - padding: 12px; + padding: 16px 20px; pointer-events: auto; color: #222222; font-weight: 485; @@ -8246,11 +8172,11 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } -.c8 { - max-width: 400px; +.c30 { + max-width: 200px; width: calc(100vw - 16px); cursor: default; - padding: 16px 20px; + padding: 8px; pointer-events: auto; color: #222222; font-weight: 485; @@ -8278,7 +8204,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` gap: 12px; } -.c20 { +.c19 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -8299,7 +8225,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` font-weight: 535; } -.c16 { +.c15 { opacity: 0; -webkit-transition: opacity 250ms ease-in; transition: opacity 250ms ease-in; @@ -8308,7 +8234,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` border-radius: 50%; } -.c15 { +.c14 { width: 20px; height: 20px; background: #22222212; @@ -8318,7 +8244,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` border-radius: 50%; } -.c14 { +.c13 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -8326,7 +8252,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` display: flex; } -.c28 { +.c27 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -8337,16 +8263,16 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` flex-direction: row; } -.c29 { +.c28 { z-index: 1; } -.c30 { +.c29 { position: absolute; left: -10px !important; } -.c12 { +.c11 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -8354,12 +8280,12 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` width: 100%; } -.c13 { +.c12 { display: grid; grid-template-columns: 24px 1fr 24px; } -.c17 { +.c16 { -webkit-align-items: center; -webkit-box-align: center; -ms-flex-align: center; @@ -8376,7 +8302,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` position: relative; } -.c27 { +.c26 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -8384,7 +8310,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` padding: 4px 4px; } -.c18 { +.c17 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -8399,11 +8325,11 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` opacity: 0.5; } -.c19 path { +.c18 path { stroke: #22222212; } -.c21 { +.c20 { background-color: #F9F9F9; border-radius: 8px; display: grid; @@ -8417,7 +8343,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` z-index: 1020; } -.c22 { +.c21 { background-color: #F9F9F9; border-radius: 4px; color: #7D7D7D; @@ -8426,7 +8352,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` z-index: 1021; } -.c23 { +.c22 { word-break: normal; } @@ -8478,66 +8404,58 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` class="c10" >
- Uniswap API -
-
-
ABC logo
dot_line.svg
V3
100%
@@ -8546,45 +8464,45 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` >
DEF logo
ABC logo
@@ -8593,7 +8511,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
1%
@@ -8605,36 +8523,33 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` style="position: fixed; left: 0px; top: 0px;" >
ABC/DEF 1% pool
DEF logo
-
@@ -8644,7 +8559,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
@@ -9931,7 +9846,7 @@ exports[`SwapLineItem.tsx syncing 1`] = ` } .c4 { - cursor: help; + cursor: auto; color: #7D7D7D; } diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index 3b67ae5e5b2..66278adacc9 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -91,22 +91,49 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = gap: 12px; } -.c9 { +.c7 { display: inline-block; height: inherit; } -.c7 { +.c9 { + background-color: transparent; + border: none; + cursor: pointer; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + padding: 0; + grid-template-columns: 1fr auto; + grid-gap: 0.25rem; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + text-align: left; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.c8 { text-align: right; overflow-wrap: break-word; } .c6 { - cursor: auto; - color: #7D7D7D; -} - -.c8 { cursor: help; color: #7D7D7D; } @@ -137,29 +164,45 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = class="c5 c6 css-142zc9n" data-testid="swap-li-label" > - Exchange rate + Rate
- 1 DEF = 1.00 ABC +
+
+ +
+
Price impact
Max. slippage
Receive at least
0.00000000000000098 DEF
@@ -223,17 +266,17 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = class="c2 c3 c4" >
Network cost
- Exchange rate + Rate
- 1 DEF = 1.00 ABC +
Price impact
Max. slippage
2%
@@ -624,13 +709,13 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission class="c2 c3 c4" >
Receive at least
Network cost
Date: Fri, 6 Oct 2023 11:46:39 -0400 Subject: [PATCH 13/61] fix: meta tag injector uses property, not name (#7431) --- src/pages/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index c0e6d41cb88..b9181764785 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -162,7 +162,7 @@ export default function App() { return null } - const blockedPaths = document.querySelector('meta[name="x:blocked-paths"]')?.getAttribute('content')?.split(',') + const blockedPaths = document.querySelector('meta[property="x:blocked-paths"]')?.getAttribute('content')?.split(',') const shouldBlockPath = blockedPaths?.includes(pathname) ?? false if (shouldBlockPath && pathname !== '/swap') { return From db1d264ad3fcf5451c9a20c205eee6f2a6d67367 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Fri, 6 Oct 2023 12:11:44 -0400 Subject: [PATCH 14/61] fix: unhide native gas token from miniportfolio (#7374) * fix: unhide native gas token from miniportfolio * wip tests & gql types * fix tests, default hide small balances * pr review * fix e2e hidden count --- cypress/e2e/mini-portfolio/accounts.test.ts | 2 +- src/utils/splitHiddenTokens.test.tsx | 73 ++++++++++++++++----- src/utils/splitHiddenTokens.tsx | 26 ++++---- 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/cypress/e2e/mini-portfolio/accounts.test.ts b/cypress/e2e/mini-portfolio/accounts.test.ts index 3b62e1ccc4e..d8c198890db 100644 --- a/cypress/e2e/mini-portfolio/accounts.test.ts +++ b/cypress/e2e/mini-portfolio/accounts.test.ts @@ -53,7 +53,7 @@ describe('Mini Portfolio account drawer', () => { // Verify that wallet state loads correctly cy.get(getTestSelector('mini-portfolio-navbar')).contains('Tokens') - cy.get(getTestSelector('mini-portfolio-page')).contains('Hidden (201)') + cy.get(getTestSelector('mini-portfolio-page')).contains('Hidden (197)') cy.intercept(/graphql/, { fixture: 'mini-portfolio/nfts.json' }) cy.get(getTestSelector('mini-portfolio-navbar')).contains('NFTs').click() diff --git a/src/utils/splitHiddenTokens.test.tsx b/src/utils/splitHiddenTokens.test.tsx index c07a61af8aa..e8df81fa3a8 100644 --- a/src/utils/splitHiddenTokens.test.tsx +++ b/src/utils/splitHiddenTokens.test.tsx @@ -1,11 +1,23 @@ -import { Currency, TokenBalance } from 'graphql/data/__generated__/types-and-hooks' +import { Chain, Currency, Token, TokenBalance, TokenStandard } from 'graphql/data/__generated__/types-and-hooks' import { splitHiddenTokens } from './splitHiddenTokens' +const nativeToken: Token = { + id: 'native-token', + chain: Chain.Ethereum, + standard: TokenStandard.Native, +} + +const nonnativeToken: Token = { + id: 'nonnative-token', + chain: Chain.Ethereum, + standard: TokenStandard.Erc20, +} + const tokens: TokenBalance[] = [ // low balance { - id: 'low-balance', + id: 'low-balance-native', ownerAddress: '', __typename: 'TokenBalance', denominatedValue: { @@ -17,10 +29,31 @@ const tokens: TokenBalance[] = [ currency: Currency.Eth, tokenProject: { id: '', - tokens: [], + tokens: [nativeToken], isSpam: false, }, }, + token: nativeToken, + }, + { + id: 'low-balance-nonnative', + ownerAddress: '', + __typename: 'TokenBalance', + denominatedValue: { + id: '', + currency: Currency.Usd, + value: 0.01, + }, + tokenProjectMarket: { + id: '', + currency: Currency.Eth, + tokenProject: { + id: '', + tokens: [nonnativeToken], + isSpam: false, + }, + }, + token: nonnativeToken, }, // spam { @@ -36,10 +69,11 @@ const tokens: TokenBalance[] = [ currency: Currency.Eth, tokenProject: { id: '', - tokens: [], + tokens: [nonnativeToken], isSpam: true, }, }, + token: nonnativeToken, }, // valid { @@ -55,10 +89,11 @@ const tokens: TokenBalance[] = [ currency: Currency.Eth, tokenProject: { id: '', - tokens: [], + tokens: [nonnativeToken], isSpam: false, }, }, + token: nonnativeToken, }, // empty value { @@ -75,10 +110,11 @@ const tokens: TokenBalance[] = [ currency: Currency.Eth, tokenProject: { id: '', - tokens: [], + tokens: [nonnativeToken], isSpam: false, }, }, + token: nonnativeToken, }, ] @@ -91,27 +127,32 @@ describe('splitHiddenTokens', () => { expect(hiddenTokens.length).toBe(1) expect(hiddenTokens[0].id).toBe('spam') - expect(visibleTokens.length).toBe(3) - expect(visibleTokens[0].id).toBe('low-balance') - expect(visibleTokens[1].id).toBe('valid') + expect(visibleTokens.length).toBe(4) + expect(visibleTokens[0].id).toBe('low-balance-native') + expect(visibleTokens[1].id).toBe('low-balance-nonnative') + expect(visibleTokens[2].id).toBe('valid') + expect(visibleTokens[3].id).toBe('undefined-value') }) - it('splits low balance into hidden by default', () => { + it('splits non-native low balance into hidden by default', () => { const { visibleTokens, hiddenTokens } = splitHiddenTokens(tokens) expect(hiddenTokens.length).toBe(2) - expect(hiddenTokens[0].id).toBe('low-balance') + expect(hiddenTokens[0].id).toBe('low-balance-nonnative') expect(hiddenTokens[1].id).toBe('spam') - expect(visibleTokens.length).toBe(2) - expect(visibleTokens[0].id).toBe('valid') + expect(visibleTokens.length).toBe(3) + expect(visibleTokens[0].id).toBe('low-balance-native') + expect(visibleTokens[1].id).toBe('valid') + expect(visibleTokens[2].id).toBe('undefined-value') }) it('splits undefined value tokens into visible', () => { const { visibleTokens } = splitHiddenTokens(tokens) - expect(visibleTokens.length).toBe(2) - expect(visibleTokens[0].id).toBe('valid') - expect(visibleTokens[1].id).toBe('undefined-value') + expect(visibleTokens.length).toBe(3) + expect(visibleTokens[0].id).toBe('low-balance-native') + expect(visibleTokens[1].id).toBe('valid') + expect(visibleTokens[2].id).toBe('undefined-value') }) }) diff --git a/src/utils/splitHiddenTokens.tsx b/src/utils/splitHiddenTokens.tsx index a438765ae33..26956676f95 100644 --- a/src/utils/splitHiddenTokens.tsx +++ b/src/utils/splitHiddenTokens.tsx @@ -1,29 +1,25 @@ -import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks' +import { TokenBalance, TokenStandard } from 'graphql/data/__generated__/types-and-hooks' const HIDE_SMALL_USD_BALANCES_THRESHOLD = 1 export function splitHiddenTokens( tokenBalances: TokenBalance[], - options?: { + options: { hideSmallBalances?: boolean - } + } = { hideSmallBalances: true } ) { const visibleTokens: TokenBalance[] = [] const hiddenTokens: TokenBalance[] = [] for (const tokenBalance of tokenBalances) { - const isValidValue = - // if undefined we keep visible (see https://linear.app/uniswap/issue/WEB-1940/[mp]-update-how-we-handle-what-goes-in-hidden-token-section-of-mini) - typeof tokenBalance.denominatedValue?.value === 'undefined' || - // if below $1 - options?.hideSmallBalances === false || - meetsThreshold(tokenBalance) - - if ( - isValidValue && - // a spam token - !tokenBalance.tokenProjectMarket?.tokenProject?.isSpam - ) { + // if undefined we keep visible (see https://linear.app/uniswap/issue/WEB-1940/[mp]-update-how-we-handle-what-goes-in-hidden-token-section-of-mini) + const isUndefinedValue = typeof tokenBalance.denominatedValue?.value === 'undefined' + const shouldHideSmallBalance = + options?.hideSmallBalances && + !meetsThreshold(tokenBalance) && // if below $1 + tokenBalance.token?.standard !== TokenStandard.Native // do not hide native tokens regardless of small balance + const isSpamToken = tokenBalance.tokenProjectMarket?.tokenProject?.isSpam + if ((isUndefinedValue || !shouldHideSmallBalance) && !isSpamToken) { visibleTokens.push(tokenBalance) } else { hiddenTokens.push(tokenBalance) From 1b7f0d11fd02c0977d498a92aa281ded14fd8334 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 6 Oct 2023 09:21:18 -0700 Subject: [PATCH 15/61] fix: override user pref in analytics (#7420) --- src/pages/App.tsx | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index b9181764785..9f99222a060 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -5,12 +5,14 @@ import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' import { useFeatureFlagsIsLoaded } from 'featureFlags' +import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useAtom } from 'jotai' import { useBag } from 'nft/hooks/useBag' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' -import { useRouterPreference } from 'state/user/hooks' +import { RouterPreference } from 'state/routing/types' +import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' import { StatsigProvider, StatsigUser } from 'statsig-react' import styled from 'styled-components' import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader' @@ -79,7 +81,8 @@ export default function App() { const isDarkMode = useIsDarkMode() const [routerPreference] = useRouterPreference() const [scrolledState, setScrolledState] = useState(false) - + const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() + const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() const routerConfig = useRouterConfig() useEffect(() => { @@ -127,8 +130,21 @@ export default function App() { }, [isDarkMode]) useEffect(() => { + // If we're not in the transition period to UniswapX opt-out, set the router preference to whatever is specified. + if (!isUniswapXDefaultEnabled) { + user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) + return + } + + // In the transition period, override the stored API preference to UniswapX if the user hasn't opted out. + if (routerPreference === RouterPreference.API && !userOptedOutOfUniswapX) { + user.set(CustomUserProperties.ROUTER_PREFERENCE, RouterPreference.X) + return + } + + // Otherwise, the user has opted out or their preference is UniswapX/client, so set the preference to whatever is specified. user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) - }, [routerPreference]) + }, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX]) useEffect(() => { const scrollListener = () => { From 48379c66ce90223d205617f9c00e97decad2c8e4 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Fri, 6 Oct 2023 13:07:29 -0400 Subject: [PATCH 16/61] feat: [info] remove balance summaries from TDP (#7430) --- src/components/Tokens/TokenDetails/index.tsx | 6 ++++-- src/featureFlags/flags/infoTDP.ts | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Tokens/TokenDetails/index.tsx b/src/components/Tokens/TokenDetails/index.tsx index ac4eb0e161f..c5b73daf7d0 100644 --- a/src/components/Tokens/TokenDetails/index.tsx +++ b/src/components/Tokens/TokenDetails/index.tsx @@ -23,6 +23,7 @@ import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { checkWarning } from 'constants/tokenSafety' +import { useInfoTDPEnabled } from 'featureFlags/flags/infoTDP' import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks' import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token' import { QueryToken } from 'graphql/data/Token' @@ -128,6 +129,7 @@ export default function TokenDetails({ }, {} as { [key: string]: string | undefined }) ?? {}, [tokenQueryData] ) + const isInfoTDPEnabled = useInfoTDPEnabled() const { token: detailedToken, didFetchFromChain } = useRelevantToken(address, pageChainId, tokenQueryData) @@ -255,9 +257,9 @@ export default function TokenDetails({ />
{tokenWarning && } - {detailedToken && } + {!isInfoTDPEnabled && detailedToken && } - {detailedToken && } + {!isInfoTDPEnabled && detailedToken && } Date: Fri, 6 Oct 2023 11:00:07 -0700 Subject: [PATCH 17/61] feat: uk disclaimer banner (#7428) * feat: uk disclaimer banner * bad merge with sitemap * button * cypress test * intercept ordering * comments * sitemap was committed idk why * font weights * moving uk disclaimer * removing trash --- cypress/e2e/landing.test.ts | 26 +++++++ src/components/NavBar/UkBanner.tsx | 82 +++++++++++++++++++++ src/components/NavBar/UkDisclaimerModal.tsx | 62 ++++++++++++++++ src/components/TopLevelModals/index.tsx | 2 + src/pages/App.tsx | 28 +++++-- src/state/application/reducer.ts | 1 + 6 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 src/components/NavBar/UkBanner.tsx create mode 100644 src/components/NavBar/UkDisclaimerModal.tsx diff --git a/cypress/e2e/landing.test.ts b/cypress/e2e/landing.test.ts index acdcd02a35a..f4bbe855593 100644 --- a/cypress/e2e/landing.test.ts +++ b/cypress/e2e/landing.test.ts @@ -39,4 +39,30 @@ describe('Landing Page', () => { cy.get(getTestSelector('pool-nav-link')).last().click() cy.url().should('include', '/pools') }) + + it('does not render uk compliance banner in US', () => { + cy.visit('/swap') + cy.contains('UK disclaimer').should('not.exist') + }) + + it('renders uk compliance banner in uk', () => { + cy.intercept('https://api.uniswap.org/v1/amplitude-proxy', (req) => { + const requestBody = JSON.stringify(req.body) + const byteSize = new Blob([requestBody]).size + req.alias = 'amplitude' + req.reply( + JSON.stringify({ + code: 200, + server_upload_time: Date.now(), + payload_size_bytes: byteSize, + events_ingested: req.body.events.length, + }), + { + 'origin-country': 'GB', + } + ) + }) + cy.visit('/swap') + cy.contains('UK disclaimer') + }) }) diff --git a/src/components/NavBar/UkBanner.tsx b/src/components/NavBar/UkBanner.tsx new file mode 100644 index 00000000000..040420ea390 --- /dev/null +++ b/src/components/NavBar/UkBanner.tsx @@ -0,0 +1,82 @@ +import { t, Trans } from '@lingui/macro' +import { useOpenModal } from 'state/application/hooks' +import { ApplicationModal } from 'state/application/reducer' +import styled from 'styled-components' +import { ButtonText, ThemedText } from 'theme/components' +import { Z_INDEX } from 'theme/zIndex' + +export const UK_BANNER_HEIGHT = 64 +export const UK_BANNER_HEIGHT_MD = 112 +export const UK_BANNER_HEIGHT_SM = 136 + +const BannerWrapper = styled.div` + position: relative; + display: flex; + background-color: ${({ theme }) => theme.surface1}; + padding: 20px; + border-bottom: 1px solid ${({ theme }) => theme.surface3}; + z-index: ${Z_INDEX.fixed}; + box-sizing: border-box; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + flex-direction: column; + } +` + +const BannerTextWrapper = styled(ThemedText.BodySecondary)` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + @supports (-webkit-line-clamp: 2) { + white-space: initial; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + } + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { + @supports (-webkit-line-clamp: 3) { + white-space: initial; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + } + } +` + +const ReadMoreWrapper = styled(ButtonText)` + flex-shrink: 0; + width: max-content; + + :focus { + text-decoration: none; + } +` + +export const bannerText = t` + This web application is provided as a tool for users to interact with the Uniswap Protocol on + their own initiative, with no endorsement or recommendation of cryptocurrency trading activities. In doing so, + Uniswap is not recommending that users or potential users engage in cryptoasset trading activity, and users or + potential users of the web application should not regard this webpage or its contents as involving any form of + recommendation, invitation or inducement to deal in cryptoassets. +` + +export function UkBanner() { + const openDisclaimer = useOpenModal(ApplicationModal.UK_DISCLAIMER) + + return ( + + {t`UK disclaimer:` + ' ' + bannerText} + + + Read more + + + + ) +} diff --git a/src/components/NavBar/UkDisclaimerModal.tsx b/src/components/NavBar/UkDisclaimerModal.tsx new file mode 100644 index 00000000000..f45c2caa00f --- /dev/null +++ b/src/components/NavBar/UkDisclaimerModal.tsx @@ -0,0 +1,62 @@ +import { Trans } from '@lingui/macro' +import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button' +import Column from 'components/Column' +import Modal from 'components/Modal' +import { X } from 'react-feather' +import { useCloseModal, useModalIsOpen } from 'state/application/hooks' +import { ApplicationModal } from 'state/application/reducer' +import styled from 'styled-components' +import { ButtonText, ThemedText } from 'theme/components' + +import { bannerText } from './UkBanner' + +const Wrapper = styled(Column)` + padding: 8px; +` + +const ButtonContainer = styled(Column)` + padding: 8px 12px 4px; +` + +const CloseIconWrapper = styled(ButtonText)` + display: flex; + color: ${({ theme }) => theme.neutral1}; + justify-content: flex-end; + padding: 8px 0px 4px; + + :focus { + text-decoration: none; + } +` + +const StyledThemeButton = styled(ThemeButton)` + width: 100%; +` + +export function UkDisclaimerModal() { + const isOpen = useModalIsOpen(ApplicationModal.UK_DISCLAIMER) + const closeModal = useCloseModal() + + return ( + + + closeModal()}> + + + + + Disclaimer for UK residents + + + {bannerText} + + + + closeModal()}> + Dismiss + + + + + ) +} diff --git a/src/components/TopLevelModals/index.tsx b/src/components/TopLevelModals/index.tsx index 44bddf38131..cdf45402544 100644 --- a/src/components/TopLevelModals/index.tsx +++ b/src/components/TopLevelModals/index.tsx @@ -6,6 +6,7 @@ import BaseAnnouncementBanner from 'components/Banner/BaseAnnouncementBanner' import AddressClaimModal from 'components/claim/AddressClaimModal' import ConnectedAccountBlocked from 'components/ConnectedAccountBlocked' import FiatOnrampModal from 'components/FiatOnrampModal' +import { UkDisclaimerModal } from 'components/NavBar/UkDisclaimerModal' import DevFlagsBox from 'dev/DevFlagsBox' import useAccountRiskCheck from 'hooks/useAccountRiskCheck' import Bag from 'nft/components/bag/Bag' @@ -34,6 +35,7 @@ export default function TopLevelModals() { + {shouldShowDevFlags && } ) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 9f99222a060..a43d69444f9 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -4,6 +4,7 @@ import { getDeviceId, sendAnalyticsEvent, sendInitializationEvent, Trace, user } import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' +import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner' import { useFeatureFlagsIsLoaded } from 'featureFlags' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useAtom } from 'jotai' @@ -11,6 +12,8 @@ import { useBag } from 'nft/hooks/useBag' import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom' import { shouldDisableNFTRoutesAtom } from 'state/application/atoms' +import { useAppSelector } from 'state/hooks' +import { AppState } from 'state/reducer' import { RouterPreference } from 'state/routing/types' import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' import { StatsigProvider, StatsigUser } from 'statsig-react' @@ -60,15 +63,23 @@ const MobileBottomBar = styled.div` } ` -const HeaderWrapper = styled.div<{ transparent?: boolean }>` +const HeaderWrapper = styled.div<{ transparent?: boolean; bannerIsVisible?: boolean; scrollY: number }>` ${flexRowNoWrap}; background-color: ${({ theme, transparent }) => !transparent && theme.surface1}; border-bottom: ${({ theme, transparent }) => !transparent && `1px solid ${theme.surface3}`}; width: 100%; justify-content: space-between; position: fixed; - top: 0; + top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT - scrollY, 0) : 0)}px; z-index: ${Z_INDEX.dropdown}; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT_MD - scrollY, 0) : 0)}px; + } + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { + top: ${({ bannerIsVisible }) => (bannerIsVisible ? Math.max(UK_BANNER_HEIGHT_SM - scrollY, 0) : 0)}px; + } ` export default function App() { @@ -80,14 +91,18 @@ export default function App() { const currentPage = getCurrentPageFromLocation(pathname) const isDarkMode = useIsDarkMode() const [routerPreference] = useRouterPreference() - const [scrolledState, setScrolledState] = useState(false) + const [scrollY, setScrollY] = useState(0) + const scrolledState = scrollY > 0 const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() const routerConfig = useRouterConfig() + const originCountry = useAppSelector((state: AppState) => state.user.originCountry) + const renderUkBannner = Boolean(originCountry) && originCountry === 'GB' + useEffect(() => { window.scrollTo(0, 0) - setScrolledState(false) + setScrollY(0) }, [pathname]) const [searchParams] = useSearchParams() @@ -148,7 +163,7 @@ export default function App() { useEffect(() => { const scrollListener = () => { - setScrolledState(window.scrollY > 0) + setScrollY(window.scrollY) } window.addEventListener('scroll', scrollListener) return () => window.removeEventListener('scroll', scrollListener) @@ -198,7 +213,8 @@ export default function App() { api: process.env.REACT_APP_STATSIG_PROXY_URL, }} > - + {renderUkBannner && } + diff --git a/src/state/application/reducer.ts b/src/state/application/reducer.ts index f8fd3648585..747153d2fae 100644 --- a/src/state/application/reducer.ts +++ b/src/state/application/reducer.ts @@ -43,6 +43,7 @@ export enum ApplicationModal { TAX_SERVICE, TIME_SELECTOR, VOTE, + UK_DISCLAIMER, UNISWAP_NFT_AIRDROP_CLAIM, } From 2c7381ff47cd45969c4f2a70468b7215070c6d52 Mon Sep 17 00:00:00 2001 From: Jack Short Date: Fri, 6 Oct 2023 12:33:04 -0700 Subject: [PATCH 18/61] fix: removing scrollbar on swap with banner (#7434) --- src/components/NavBar/UkBanner.tsx | 6 +++--- src/pages/App.tsx | 14 +++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/NavBar/UkBanner.tsx b/src/components/NavBar/UkBanner.tsx index 040420ea390..76bcbce2aea 100644 --- a/src/components/NavBar/UkBanner.tsx +++ b/src/components/NavBar/UkBanner.tsx @@ -5,9 +5,9 @@ import styled from 'styled-components' import { ButtonText, ThemedText } from 'theme/components' import { Z_INDEX } from 'theme/zIndex' -export const UK_BANNER_HEIGHT = 64 -export const UK_BANNER_HEIGHT_MD = 112 -export const UK_BANNER_HEIGHT_SM = 136 +export const UK_BANNER_HEIGHT = 65 +export const UK_BANNER_HEIGHT_MD = 113 +export const UK_BANNER_HEIGHT_SM = 137 const BannerWrapper = styled.div` position: relative; diff --git a/src/pages/App.tsx b/src/pages/App.tsx index a43d69444f9..89648990c7d 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -32,14 +32,22 @@ import { RouteDefinition, routes, useRouterConfig } from './RouteDefinitions' const AppChrome = lazy(() => import('./AppChrome')) -const BodyWrapper = styled.div` +const BodyWrapper = styled.div<{ bannerIsVisible?: boolean }>` display: flex; flex-direction: column; width: 100%; - min-height: 100vh; + min-height: calc(100vh - ${({ bannerIsVisible }) => (bannerIsVisible ? UK_BANNER_HEIGHT : 0)}px); padding: ${({ theme }) => theme.navHeight}px 0px 5rem 0px; align-items: center; flex: 1; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + min-height: calc(100vh - ${({ bannerIsVisible }) => (bannerIsVisible ? UK_BANNER_HEIGHT_MD : 0)}px); + } + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { + min-height: calc(100vh - ${({ bannerIsVisible }) => (bannerIsVisible ? UK_BANNER_HEIGHT_SM : 0)}px); + } ` const MobileBottomBar = styled.div` @@ -217,7 +225,7 @@ export default function App() { - + From 3ffe7693cf922afb517a14a2daef7d1533df52f9 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:08:46 -0700 Subject: [PATCH 19/61] feat: add top token urls to sitemap and improve script (#7429) * feat: add top token urls to sitemap and improve script * fix: remove unnecessary header * fix: test --- package.json | 1 + public/sitemap.xml | 306 ++++++++++++++++++++++++++++++++++++ scripts/generate-sitemap.js | 52 +++++- src/pages/routes.test.ts | 8 +- yarn.lock | 33 +++- 5 files changed, 390 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index e28b5c6ad3f..39addfc51fe 100644 --- a/package.json +++ b/package.json @@ -256,6 +256,7 @@ "multicodec": "^3.0.1", "multihashes": "^4.0.2", "nock": "^13.3.3", + "node-fetch": "^3.3.2", "node-vibrant": "^3.2.1-alpha.1", "numbro": "^2.3.6", "polished": "^3.3.2", diff --git a/public/sitemap.xml b/public/sitemap.xml index ca9f947beed..9f51713817d 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -16,4 +16,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js index 64889262d95..a1c395d6f13 100644 --- a/scripts/generate-sitemap.js +++ b/scripts/generate-sitemap.js @@ -3,23 +3,65 @@ const fs = require('fs') const { parseStringPromise, Builder } = require('xml2js') +const weekMs = 7 * 24 * 60 * 60 * 1000 +const nowISO = new Date().toISOString() + +const getQuery = (chain) => ` + query { + topTokens(pageSize: 100, page: 1, chain: ${chain}, orderBy: VOLUME) { + address + } + } +` +const chains = ['ETHEREUM', 'ARBITRUM', 'OPTIMISM', 'POLYGON', 'BASE', 'BNB', 'CELO'] + fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { + const sitemapURLs = {} try { const sitemap = await parseStringPromise(data) - - const lastmodDate = new Date().toISOString() if (sitemap.urlset.url) { sitemap.urlset.url.forEach((url) => { - url['$'].lastmod = lastmodDate + const lastMod = new Date(url['$'].lastmod).getTime() + if (lastMod < Date.now() - weekMs) { + url['$'].lastmod = nowISO + } + sitemapURLs[url['$']['loc']] = true }) } + + for (const chainName of chains) { + const response = await fetch('https://api.uniswap.org/v1/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Origin: 'https://app.uniswap.org', + }, + body: JSON.stringify({ query: getQuery(chainName) }), + }) + const tokensJSON = await response.json() + const tokenAddresses = tokensJSON.data.topTokens.map((token) => token.address.toLowerCase()) + + tokenAddresses.forEach((address) => { + const tokenURL = `https://app.uniswap.org/tokens/${chainName.toLowerCase()}/${address}` + if (!(tokenURL in sitemapURLs)) { + sitemap.urlset.url.push({ + $: { + loc: [tokenURL], + lastmod: [nowISO], + priority: [0.8], + }, + }) + } + }) + } + const builder = new Builder() const xml = builder.buildObject(sitemap) fs.writeFile('./public/sitemap.xml', xml, (error) => { if (error) throw error console.log('Sitemap updated') }) - } catch { - throw new Error('Error parsing sitemap.xml') + } catch (e) { + console.error(e) } }) diff --git a/src/pages/routes.test.ts b/src/pages/routes.test.ts index 927925b92bc..9920a9fef1a 100644 --- a/src/pages/routes.test.ts +++ b/src/pages/routes.test.ts @@ -11,9 +11,11 @@ describe('Routes', () => { const sitemapPaths = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname) - sitemapPaths.forEach((path: string) => { - expect(pathNames).toContain(path) - }) + pathNames + .filter((p) => !p.includes(':') && !p.includes('*') && !p.includes('not-found')) + .forEach((path: string) => { + expect(sitemapPaths).toContain(path) + }) }) /** diff --git a/yarn.lock b/yarn.lock index 97c884f6725..5036ce978a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9985,6 +9985,11 @@ data-uri-to-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz#d296973d5a4897a5dbe31716d118211921f04770" integrity sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA== +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + data-urls@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" @@ -11690,6 +11695,14 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + fflate@^0.7.3: version "0.7.4" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50" @@ -11929,6 +11942,13 @@ formdata-node@^4.3.1: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.3" +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -15647,7 +15667,7 @@ node-cache@^5.1.2: dependencies: clone "2.x" -node-domexception@1.0.0: +node-domexception@1.0.0, node-domexception@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== @@ -15659,6 +15679,15 @@ node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.1, node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -20494,7 +20523,7 @@ web-streams-polyfill@4.0.0-beta.3: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== -web-streams-polyfill@^3.2.0, web-streams-polyfill@^3.2.1: +web-streams-polyfill@^3.0.3, web-streams-polyfill@^3.2.0, web-streams-polyfill@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== From 27ec2e018c4bd309cffad98be2180ff3139b4cd8 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:09:04 -0700 Subject: [PATCH 20/61] feat: add robots.txt (#7435) --- public/robots.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 public/robots.txt diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000000..852c90241c1 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,10 @@ +# * +User-agent: * +Disallow: /static/js/ +Allow: / + +# Host +Host: https://app.uniswap.org + +# Sitemaps +Sitemap: https://app.uniswap.org/sitemap.xml From 04bf0758268766e596bd1670334a2305514529d9 Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Tue, 10 Oct 2023 10:02:21 -0700 Subject: [PATCH 21/61] fix: broken Checkmark image for Mobile NFT Sort dropdown (#7401) fix broken image --- .../SortDropdown/FilterSortDropdown.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/nft/components/common/SortDropdown/FilterSortDropdown.tsx b/src/nft/components/common/SortDropdown/FilterSortDropdown.tsx index a3ec699b014..eed0d006bc1 100644 --- a/src/nft/components/common/SortDropdown/FilterSortDropdown.tsx +++ b/src/nft/components/common/SortDropdown/FilterSortDropdown.tsx @@ -1,8 +1,15 @@ -import { Box } from 'nft/components/Box' import { FilterDropdown, FilterItem } from 'nft/components/collection/MarketplaceSelect' import { useCollectionFilters } from 'nft/hooks' import { DropDownOption } from 'nft/types' import { useState } from 'react' +import { Check } from 'react-feather' +import styled from 'styled-components' + +const CheckIcon = styled(Check)` + height: 20px; + width: 20px; + color: ${({ theme }) => theme.accent1}; +` export const FilterSortDropdown = ({ sortDropDownOptions }: { sortDropDownOptions: DropDownOption[] }) => { const [isOpen, setOpen] = useState(false) @@ -24,19 +31,7 @@ const SortByItem = ({ parentOnClick: React.MouseEventHandler }) => { const sortBy = useCollectionFilters((state) => state.sortBy) - const checkMark = - dropDownOption.sortBy !== undefined && sortBy === dropDownOption.sortBy ? ( - - ) : ( - <> - ) + const checkMark = dropDownOption.sortBy !== undefined && sortBy === dropDownOption.sortBy ? : <> const onClick: React.MouseEventHandler = (e) => { e.preventDefault() parentOnClick(e) From b38ce038e63abba2a53a7ab63724155ddb503976 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:41:08 -0700 Subject: [PATCH 22/61] feat: remove sitemap script from prepare step (#7437) --- .github/workflows/1-main-to-staging.yml | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/1-main-to-staging.yml b/.github/workflows/1-main-to-staging.yml index 27ce771780b..fe43834ffb3 100644 --- a/.github/workflows/1-main-to-staging.yml +++ b/.github/workflows/1-main-to-staging.yml @@ -67,6 +67,12 @@ jobs: echo '* @uniswap/web-admins' > CODEOWNERS git add CODEOWNERS git commit -m 'ci: add global CODEOWNERS' + + - name: Update sitemap + run: | + yarn sitemap:generate + git add public/sitemap.xml + git commit -m 'ci: update sitemap' - name: Git push run: | diff --git a/package.json b/package.json index 39addfc51fe..86d377be8ae 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "i18n:extract": "lingui extract --locale en-US", "i18n:compile": "lingui compile", "i18n": "yarn i18n:extract --clean && yarn i18n:compile", - "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\" \"npm:sitemap:generate\"", + "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"", "start": "craco start", "start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start", "build": "craco build", From 24ddace1eb247e459d10eed709f2bfd83ef6d64e Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Tue, 10 Oct 2023 12:24:57 -0700 Subject: [PATCH 23/61] feat: [info] update color extract default and have PDP fallback (#7432) * feat: [info] update color extract default and have a fallback * uncomment section removed for testing * update snapshot --- src/hooks/useColor.ts | 8 +++++--- src/pages/PoolDetails/PoolDetailsStats.tsx | 9 +++++++-- .../__snapshots__/PoolDetailsStats.test.tsx.snap | 4 ++-- src/pages/PoolDetails/__snapshots__/index.test.tsx.snap | 4 ++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/hooks/useColor.ts b/src/hooks/useColor.ts index 777d471cc23..14ecaa9d738 100644 --- a/src/hooks/useColor.ts +++ b/src/hooks/useColor.ts @@ -4,6 +4,7 @@ import useTokenLogoSource from 'hooks/useAssetLogoSource' import { rgb } from 'polished' import { useEffect, useState } from 'react' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' +import { useTheme } from 'styled-components' import { getColor } from 'utils/getColor' function URIForEthToken(address: string) { @@ -54,7 +55,8 @@ function convertColorArrayToString([red, green, blue]: number[]): string { } export function useColor(token?: Token) { - const [color, setColor] = useState('#2172E5') + const theme = useTheme() + const [color, setColor] = useState(theme.accent1) const [src] = useTokenLogoSource(token?.address, token?.chainId, token?.isNative) useEffect(() => { @@ -70,9 +72,9 @@ export function useColor(token?: Token) { return () => { stale = true - setColor('#2172E5') + setColor(theme.accent1) } - }, [src, token]) + }, [src, theme.accent1, token]) return color } diff --git a/src/pages/PoolDetails/PoolDetailsStats.tsx b/src/pages/PoolDetails/PoolDetailsStats.tsx index 3fdcdff9168..9d98d4cf0f5 100644 --- a/src/pages/PoolDetails/PoolDetailsStats.tsx +++ b/src/pages/PoolDetails/PoolDetailsStats.tsx @@ -9,8 +9,9 @@ import { useColor } from 'hooks/useColor' import { useScreenSize } from 'hooks/useScreenSize' import { ReactNode, useMemo } from 'react' import { Text } from 'rebass' -import styled, { css } from 'styled-components' +import styled, { css, useTheme } from 'styled-components' import { BREAKPOINTS } from 'theme' +import { colors } from 'theme/colors' import { ThemedText } from 'theme/components' import { NumberType, useFormatter } from 'utils/formatNumbers' @@ -99,12 +100,16 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS const isScreenSize = useScreenSize() const screenIsNotLarge = isScreenSize['lg'] const { formatNumber } = useFormatter() + const theme = useTheme() const currency0 = useCurrency(poolData?.token0?.id, chainId) ?? undefined const currency1 = useCurrency(poolData?.token1?.id, chainId) ?? undefined const color0 = useColor(currency0?.wrapped) - const color1 = useColor(currency1?.wrapped) + let color1 = useColor(currency1?.wrapped) + if (color0 === color1 && color0 === theme.accent1) { + color1 = colors.blue400 + } const [token0, token1] = useMemo(() => { const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1 diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap index 2bea8407e2e..08cbabaab88 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap @@ -506,7 +506,7 @@ exports[`PoolDetailsStats renders stats text correctly 1`] = ` .c9 { height: 8px; width: 40.698463777008904%; - background: #2172E5; + background: #FC72FF; border-top-left-radius: 5px; border-bottom-left-radius: 5px; border-right: 1px solid #F9F9F9; @@ -515,7 +515,7 @@ exports[`PoolDetailsStats renders stats text correctly 1`] = ` .c10 { height: 8px; width: 59.3015362229911%; - background: #2172E5; + background: #4C82FB; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-left: 1px solid #F9F9F9; diff --git a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap index fde90bd8fbd..42eb6bc4df6 100644 --- a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap @@ -324,7 +324,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the .c27 { height: 8px; width: 40.698463777008904%; - background: #2172E5; + background: #FC72FF; border-top-left-radius: 5px; border-bottom-left-radius: 5px; border-right: 1px solid #F9F9F9; @@ -333,7 +333,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the .c28 { height: 8px; width: 59.3015362229911%; - background: #2172E5; + background: #4C82FB; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-left: 1px solid #F9F9F9; From 45c3e1dc7861cdcae8562dbe9c7cef2313262d03 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Tue, 10 Oct 2023 15:37:47 -0400 Subject: [PATCH 24/61] feat: add dynamicconfig modal setting (#7395) * feat: add dynamicconfig feature flags setting * better typing * use diff atom for configs * fix * add config to devflagsbox * fix devbox intiailization * lint --- public/sitemap.xml | 36 ++++++++++++++ .../FeatureFlagModal/FeatureFlagModal.tsx | 48 +++++++++++++++++-- src/dev/DevFlagsBox.tsx | 30 +++++++++++- .../dynamicConfig/quickRouteChains.ts | 14 ++++-- src/featureFlags/index.tsx | 24 ++++++++-- 5 files changed, 140 insertions(+), 12 deletions(-) diff --git a/public/sitemap.xml b/public/sitemap.xml index 9f51713817d..ac5e016f090 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -322,4 +322,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/src/components/FeatureFlagModal/FeatureFlagModal.tsx index a2d46bd2f16..caeb77d2c97 100644 --- a/src/components/FeatureFlagModal/FeatureFlagModal.tsx +++ b/src/components/FeatureFlagModal/FeatureFlagModal.tsx @@ -1,5 +1,8 @@ +import { ChainId } from '@uniswap/sdk-core' import Column from 'components/Column' -import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateFlag } from 'featureFlags' +import { BaseVariant, FeatureFlag, featureFlagSettings, useUpdateConfig, useUpdateFlag } from 'featureFlags' +import { DynamicConfigName } from 'featureFlags/dynamicConfig' +import { useQuickRouteChains } from 'featureFlags/dynamicConfig/quickRouteChains' import { useCurrencyConversionFlag } from 'featureFlags/flags/currencyConversion' import { useFallbackProviderEnabledFlag } from 'featureFlags/flags/fallbackProvider' import { useFotAdjustmentsFlag } from 'featureFlags/flags/fotAdjustments' @@ -218,16 +221,48 @@ function FeatureFlagOption({ value, variant, featureFlag, label }: FeatureFlagPr ) } +interface DynamicConfigDropdownProps { + configName: DynamicConfigName + label: string + options: any[] + selected: any[] + parser: (opt: string) => any +} + +function DynamicConfigDropdown({ configName, label, options, selected, parser }: DynamicConfigDropdownProps) { + const updateConfig = useUpdateConfig() + const handleSelectChange = (e: React.ChangeEvent) => { + const selectedValues = Array.from(e.target.selectedOptions, (opt) => parser(opt.value)) + // Saved to atom as { [configName]: { [configName]: values } } to match Statsig return format + updateConfig(configName, { [configName]: selectedValues }) + } + return ( + + + {configName} + {label} + + + + ) +} + export default function FeatureFlagModal() { const open = useModalIsOpen(ApplicationModal.FEATURE_FLAGS) - const toggle = useToggleFeatureFlags() + const toggleModal = useToggleFeatureFlags() return (
Feature Flag Settings - +
@@ -262,6 +297,13 @@ export default function FeatureFlagModal() { featureFlag={FeatureFlag.quickRouteMainnet} label="Enable quick routes for Mainnet" /> + !isNaN(Number(v))) as ChainId[]} + parser={Number.parseInt} + configName={DynamicConfigName.quickRouteChains} + label="Enable quick routes for these chains" + /> ) => { +const Gate = (flagName: FeatureFlag, featureFlagSettings: Record) => { const gateResult = useGate(flagName) if (gateResult) { const { value: statsigValue }: { value: boolean } = gateResult @@ -40,11 +46,31 @@ const Gate = (flagName: string, featureFlagSettings: Record) => return null } +const Config = (name: DynamicConfigName, savedSettings: Record) => { + const statsigConfig = useDynamicConfig(name) + if (statsigConfig) { + const statsigValue = statsigConfig.getValue() + const setting = savedSettings[name] + if (setting && statsigValue !== setting) { + return ( + + {name}: {JSON.stringify(setting[name])} + + ) + } + } + return null +} + export default function DevFlagsBox() { const featureFlagsAtom = useAtomValue(featureFlagSettingsAtom) const featureFlags = useMemo(() => Object.values(FeatureFlag), []) + const dynamicConfigsAtom = useAtomValue(dynamicConfigSettingsAtom) + const dynamicConfigs = useMemo(() => Object.values(DynamicConfigName), []) const overrides = featureFlags.map((flagName) => Gate(flagName, featureFlagsAtom)) + dynamicConfigs.forEach((configName) => overrides.push(Config(configName, dynamicConfigsAtom))) + const hasOverrides = overrides.some((g) => g !== null) const [isOpen, setIsOpen] = useState(true) diff --git a/src/featureFlags/dynamicConfig/quickRouteChains.ts b/src/featureFlags/dynamicConfig/quickRouteChains.ts index 5bd674da4b3..708e2acb221 100644 --- a/src/featureFlags/dynamicConfig/quickRouteChains.ts +++ b/src/featureFlags/dynamicConfig/quickRouteChains.ts @@ -1,13 +1,19 @@ import { ChainId } from '@uniswap/sdk-core' +import { useFeatureFlagsContext } from 'featureFlags' import { DynamicConfigName, useDynamicConfig } from '.' -// eslint-disable-next-line import/no-unused-modules export function useQuickRouteChains(): ChainId[] { - const config = useDynamicConfig(DynamicConfigName.quickRouteChains) - const chains = config.get('chains', []) + const statsigConfig = useDynamicConfig(DynamicConfigName.quickRouteChains) + const featureFlagsContext = useFeatureFlagsContext() + + const remoteConfigChains = statsigConfig.get(DynamicConfigName.quickRouteChains, []) as ChainId[] + const localConfigChains = + featureFlagsContext.configs[DynamicConfigName.quickRouteChains]?.[DynamicConfigName.quickRouteChains] + + const chains = Array.isArray(localConfigChains) ? localConfigChains : remoteConfigChains if (chains.every((c) => Object.values(ChainId).includes(c))) { - return chains as ChainId[] + return chains } else { console.error('feature flag config chains contain invalid ChainId') return [] diff --git a/src/featureFlags/index.tsx b/src/featureFlags/index.tsx index a6bafa32d88..f6951e69525 100644 --- a/src/featureFlags/index.tsx +++ b/src/featureFlags/index.tsx @@ -26,11 +26,12 @@ export enum FeatureFlag { interface FeatureFlagsContextType { isLoaded: boolean flags: Record + configs: Record } -const FeatureFlagContext = createContext({ isLoaded: false, flags: {} }) +const FeatureFlagContext = createContext({ isLoaded: false, flags: {}, configs: {} }) -function useFeatureFlagsContext(): FeatureFlagsContextType { +export function useFeatureFlagsContext(): FeatureFlagsContextType { const context = useContext(FeatureFlagContext) if (!context) { throw Error('Feature flag hooks can only be used by children of FeatureFlagProvider.') @@ -39,8 +40,9 @@ function useFeatureFlagsContext(): FeatureFlagsContextType { } } -/* update and save feature flag settings */ +/* update and save feature flag & dynamic config settings */ export const featureFlagSettings = atomWithStorage>('featureFlags', {}) +export const dynamicConfigSettings = atomWithStorage>('dynamicConfigs', {}) export function useUpdateFlag() { const setFeatureFlags = useUpdateAtom(featureFlagSettings) @@ -56,13 +58,29 @@ export function useUpdateFlag() { ) } +export function useUpdateConfig() { + const setConfigs = useUpdateAtom(dynamicConfigSettings) + + return useCallback( + (configName: string, option: any) => { + setConfigs((configs) => ({ + ...configs, + [configName]: option, + })) + }, + [setConfigs] + ) +} + export function FeatureFlagsProvider({ children }: { children: ReactNode }) { // TODO: `isLoaded` to `true` so `App.tsx` will render. Later, this will be dependent on // flags loading from Amplitude, with a timeout. const featureFlags = useAtomValue(featureFlagSettings) + const dynamicConfigs = useAtomValue(dynamicConfigSettings) const value = { isLoaded: true, flags: featureFlags, + configs: dynamicConfigs, } return {children} } From e16348e2e0b408fb610e68668cdbd1b77b0d3180 Mon Sep 17 00:00:00 2001 From: cartcrom <39385577+cartcrom@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:25:50 -0400 Subject: [PATCH 25/61] feat: new chain logos (#7438) * feat: new consolidated chain logos * test: update snapshots * refactor: simplify border radius formula * fix: pass style prop to portofolio logo container * lint * fix: accessibility * fix: pr nits' * fix: unnused styled component --- cypress/e2e/token-explore.test.ts | 2 +- .../wallet-connection/switch-network.test.ts | 2 +- src/assets/images/bnbCircle.svg | 20 - src/assets/images/celoCircle.png | Bin 1517 -> 0 bytes src/assets/images/polygonCircle.png | Bin 3395 -> 0 bytes src/assets/svg/arbitrum_logo.svg | 9 - src/assets/svg/avax_square_logo.svg | 11 - src/assets/svg/base_logo.svg | 11 - src/assets/svg/base_square_logo.svg | 11 - src/assets/svg/bnb_square_logo.svg | 21 - src/assets/svg/celo_square_logo.svg | 5 - src/assets/svg/ethereum_square_logo.svg | 16 - src/assets/svg/optimism_square_logo.svg | 15 - src/assets/svg/optimistic_ethereum.svg | 5 - src/assets/svg/polygon-matic-logo.svg | 1 - src/assets/svg/polygon_square_logo.svg | 1 - .../MiniPortfolio/PortfolioLogo.tsx | 31 +- .../__snapshots__/PortfolioLogo.test.tsx.snap | 42 +- src/components/DoubleLogo/index.tsx | 4 +- src/components/Logo/AssetLogo.tsx | 18 - src/components/Logo/ChainLogo.tsx | 122 ++++ src/components/Logo/ChainSymbols/arbitrum.svg | 9 + src/components/Logo/ChainSymbols/avax.svg | 10 + src/components/Logo/ChainSymbols/base.svg | 3 + src/components/Logo/ChainSymbols/bnb.svg | 3 + src/components/Logo/ChainSymbols/celo.svg | 3 + .../Logo/ChainSymbols/celo_light.svg | 3 + src/components/Logo/ChainSymbols/ethereum.svg | 4 + src/components/Logo/ChainSymbols/optimism.svg | 4 + src/components/Logo/ChainSymbols/polygon.svg | 3 + src/components/Logo/CurrencyLogo.tsx | 1 - src/components/Logo/QueryTokenLogo.tsx | 29 +- src/components/NavBar/ChainSelector.css.ts | 7 - src/components/NavBar/ChainSelector.tsx | 3 +- src/components/NavBar/ChainSelectorRow.tsx | 12 +- src/components/NavBar/SearchBarDropdown.tsx | 11 +- .../ChainSelectorRow.test.tsx.snap | 686 ++++++++++++------ src/components/NetworkAlert/NetworkAlert.tsx | 211 ++---- .../Tokens/TokenDetails/BalanceSummary.tsx | 7 +- src/components/Tokens/TokenDetails/index.tsx | 4 +- .../Tokens/TokenTable/NetworkFilter.tsx | 22 +- src/components/Tokens/TokenTable/TokenRow.tsx | 4 +- .../__snapshots__/TokenRow.test.tsx.snap | 133 ++-- .../TransactionConfirmationModal/index.tsx | 11 +- src/components/swap/ConfirmSwapModal.tsx | 8 +- src/components/swap/SwapLineItem.tsx | 4 +- .../SwapDetailsDropdown.test.tsx.snap | 31 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 186 ++++- .../SwapModalFooter.test.tsx.snap | 31 +- src/constants/chainInfo.ts | 47 -- src/graphql/data/util.tsx | 3 +- src/pages/PoolDetails/PoolDetailsHeader.tsx | 27 +- 52 files changed, 1036 insertions(+), 831 deletions(-) delete mode 100644 src/assets/images/bnbCircle.svg delete mode 100644 src/assets/images/celoCircle.png delete mode 100644 src/assets/images/polygonCircle.png delete mode 100644 src/assets/svg/arbitrum_logo.svg delete mode 100644 src/assets/svg/avax_square_logo.svg delete mode 100644 src/assets/svg/base_logo.svg delete mode 100644 src/assets/svg/base_square_logo.svg delete mode 100644 src/assets/svg/bnb_square_logo.svg delete mode 100644 src/assets/svg/celo_square_logo.svg delete mode 100644 src/assets/svg/ethereum_square_logo.svg delete mode 100644 src/assets/svg/optimism_square_logo.svg delete mode 100644 src/assets/svg/optimistic_ethereum.svg delete mode 100644 src/assets/svg/polygon-matic-logo.svg delete mode 100644 src/assets/svg/polygon_square_logo.svg create mode 100644 src/components/Logo/ChainLogo.tsx create mode 100644 src/components/Logo/ChainSymbols/arbitrum.svg create mode 100644 src/components/Logo/ChainSymbols/avax.svg create mode 100644 src/components/Logo/ChainSymbols/base.svg create mode 100644 src/components/Logo/ChainSymbols/bnb.svg create mode 100644 src/components/Logo/ChainSymbols/celo.svg create mode 100644 src/components/Logo/ChainSymbols/celo_light.svg create mode 100644 src/components/Logo/ChainSymbols/ethereum.svg create mode 100644 src/components/Logo/ChainSymbols/optimism.svg create mode 100644 src/components/Logo/ChainSymbols/polygon.svg diff --git a/cypress/e2e/token-explore.test.ts b/cypress/e2e/token-explore.test.ts index 4a0c9aecbee..036581b5497 100644 --- a/cypress/e2e/token-explore.test.ts +++ b/cypress/e2e/token-explore.test.ts @@ -69,6 +69,6 @@ describe('Token explore', () => { cy.get(getTestSelector('tokens-network-filter-selected')).click() cy.get(getTestSelector('tokens-network-filter-option-optimism')).click() cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Optimism') - cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', 'Ethereum') + cy.get(getTestSelector('chain-selector-logo')).find('title').should('include.text', 'Ethereum logo') }) }) diff --git a/cypress/e2e/wallet-connection/switch-network.test.ts b/cypress/e2e/wallet-connection/switch-network.test.ts index f75fcf0dfea..917a719ff8d 100644 --- a/cypress/e2e/wallet-connection/switch-network.test.ts +++ b/cypress/e2e/wallet-connection/switch-network.test.ts @@ -2,7 +2,7 @@ import { createDeferredPromise } from '../../../src/test-utils/promise' import { getTestSelector } from '../../utils' function waitsForActiveChain(chain: string) { - cy.get(getTestSelector('chain-selector-logo')).invoke('attr', 'alt').should('eq', chain) + cy.get(getTestSelector('chain-selector-logo')).find('title').should('include.text', `${chain} logo`) } function switchChain(chain: string) { diff --git a/src/assets/images/bnbCircle.svg b/src/assets/images/bnbCircle.svg deleted file mode 100644 index f39dba1be16..00000000000 --- a/src/assets/images/bnbCircle.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/images/celoCircle.png b/src/assets/images/celoCircle.png deleted file mode 100644 index 912b8f4f1e37971aa1ae2ede80f34e01d3201335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1517 zcmVy)(1j>CSekG(vv zU5sZ6?nm)B)5d)ytLbi>Nl8Fwd2A%*Vf7aFb^MCYJ5M4V*5oh7E1pN-vSlTbc!)^& zk_hPM^v_7jy9Pqo?)wTCBQ5c^DfR*^be5tpBNwgxJ!puw#6ZjyYs%zPm*(C24F!v;!abkKlZ`S^O`}nt*-xY(kFB zZVag+0r*Y%84X~=iS2NPy5Ye9K2jz_%5VW$Zpit|Amx+;;|eV2zSLBMx;~$%AG!B+ zxHEH&uDwKHo2;L`wQ{;}YTF^yo!pGV_C`3uLA+RyBidPiH(2NrV!lr?c<~J}&I+50 zR>te!cGo3#t>?OBXzn!`{auLF?}FNT5Oux1qG^INALS*FLdstbY5XEc#;!S08W*eI z0SxwX3#F% zzKZ<&h7a%I1bn6@wO=dA$r#`+bfbH@sQYN)-Q?jS#<&z1Bx~(zK8zOq{MQvOjUQzK ze4M~pQnsuVAA2rfEyaaDdM)k*oqv5KiXF|ZIC4XaywmJ%yh?Gy z0%y1%s6~P&lU>!`+Cu zw7W2q`aV>igH4jH2ppGmSIh&Eun+fWhFi7GdJB?kcG4^!l=cTsBB~CU+MRttlzLBV zL?)Htsj_v#au(0eK%Nnds;Jm1ng)UxJhw%&2Hk4g%r`A(atr*`=fw}fv+&;viVSI9 z;|UZ~uqtyn@c4>qICK}B0yPwjHd=;b)&FJO8OenY554bHU<~T@YN%bmX>&YJLoQy8 zG!Wrf9E_eK?KOI2PX*nDB8^Po7YZ^v!EQEN>*f*h^3J$&DZ7M}uS%zUuaTCePRV#Q zy!;*onLvD&M2fTA5^gOJGggPtt+e6pKTtqd2^zz2)8 z+!A;5!(=29{L`$8-8uLgqx&?outp6V9fG=ICEfZGZWCble?~C0+>7i35IFa+g<{q`dMJ<0>3BULOVk5&qT!b%n?89G ze6kdHHb8e__=-L1)E6`iUa{=8?n8DT^)BaEQ$17T4lJ}r*IG=dY)Hzp+u9NHmC4;%!$ ztmW-Zf)4@8F(BuWr#X)z&0#o27RHoWaH(<^oXXr!ovIw=P~`I^Bl1j!-b^i?8P96&t4ZC&BOV-to16=$X?E^$e@kjIF~A0cPVq=ROMD2 zOkU|=@|1(g3+zmRvN6Ris)-`4jp4$g$fGmDNE5?}qYa`Shu|h93+UW+1tY#a7#J?u zf$LbyVK}@oT;0j!!J*2p+nKxq!P%LDz@{!zR!ylJZjOh;pK}8Owuh4#(9K{t zAnI`rCeJxkc?4Itsf%D!7yo5{wAN?SE+I(DtSwR0>%t2}J#2AcMu_3+Bp5ik2o76& zmzozK!SKsd;8&%X2BuNfK`|Vw>k$d1_5IAI6G6|?CJv8m}1?k zE`n84GFvr8%AzR>%-S-tc&?vVE1nTXIINyH%8|0t3P#6T$q!o_P^PeY_Pq(1dMpQX ze_4UeItOT#&fkRBep3PS{tVb;IdEwU;NY$0Yz$VfY1Nd#qA6`yG^IVGnlfe4l=`T! zDB6$HD~g;Z>NSvh-PYbU7<4BX&Ve{Sdmp>Mfwij**t(jV9L&wGLhzR*7~Y=+o2meI zWGz$Vka`w%sczAfVN_dw#iA`CKtU@cduN1^Itd0hEh5%RI@bPRFl0QOSDJ*k4lTiQ zW}^q{oAd_czgX+}w_B^5@Xx?iaOx0T0c;vBgw0yvwXX7UP;1F?R)n z>kY;q6F!@|0$yznY~l>ezIX|)6mIwY&bgDT zU>wYYU0*`#aYVg_S-S*g?b7pR?Na;pI8SQc=C)w;SQ~>0Ij`Q>z8H80;}BG*z_B+D zADC+RKjkQ$>94Y$ zumDyA0mRl~^%}#vDh%nXPou8cLvDLaOc)$4^;M(?_fE-j=%r) zB^KW50-XIr4J?CcaBK76LU%2b$8~R%&V%cbQX6pL!!fOyih>n48-vY#DulJUX#5&9#TEb^9z@%aBjqC8{a}}6kZfF?cb#DJvcVZfGp81Ugm zD1GrA{$us^CY<$L2A?#6%Zgu_0uP-6r!Wb&fh3$UUTOoLe6|Yj9JvH@Z+{DJLjf#w z9^M^UhU+Y^Tx)&)`YIe16~SU?TZ;jQ^azmU0jI?CZcl-5{wQU z4C|}hkgnP=AwUCr3{7Y+kkmFvmrMZT;6;FEc?DOicOYa)gOBdCQXY94yi5kH1F2rX zXIVI7{$^X48h1E2cj{Yk|DuAGGiyjN+V-Mzhr+=&Oa>N=`r1JRcz^_>$R~vd-m=NS z?w|sSMJgm&{0jGa(%5FO4Vb`yrLS)A_G~oa-0>?AP^ZB@ki=cGi%#R~?A0gBP>FQT z+3bWA?czKoxLEMZ^`%= zlqbOba17@De68mkT`%8;_m0-UAxgo-V|lpn#tLjU_@1xTQoVYxiff)-Q^eb*}&?P@|{`$s*bhO-N%Kq)X=KTUmnhqgGy-gpXa9;pVm8{%+~Q zO<1kaycS=${96H?E>HjR3$-l|bw+lc{#bi99pj#EKt z>;!408l+3+TGJWoR;GTQg^Q=Iw>#wSb*+2{-Z^#|EYci|N^;=PpccN+fm4@wYg_7V zv$m~XoqSFrIHR5wAh7L;YT5fQwf${43@A%b0UE(21%6qQ z^2?J-pFFiruCqE;kAE3JPZxOj#(kbD*M{3>Z7UetYaVI_a5zkQwoYo^iqTLT#Oe`i zdjQc2&`BzQ;Zg!0dJUDQlwNuI8nL$SR%Dv3XA74SJzL~(pVy<_-GagQc^nBw6Ty)i zqh>O&7|vv1??YhQ;JTMjo~C^Al)x)bQyzJGw?~ma-?mnnt-DmYCh3yVvjut?0P-^M zL&4xibhwR2);5Q9)jDpac(}7BJ-Y*+H$8iwNskr$@i#*Bmh6?M1s+A3^5|22ZbimC zrp(^%dRJ*UnEa-L$v3-R1~@$1^5Y{%g24|#+R7&Cl7~Cit{DxrmybPiiNcB1vjU_V z`hHK#gVaNCigdq6m7?6}9a)v#<5K2cajJ4RwgjVTQx_Xnyh{y>rqn>Q?Wkr6&9}`h z&A_Tj&rX^Q>;p)(r*#~H>L=09=ctsEahZ!yBwe)X2=jx9F Z{0CXV!f+Fv49Wli002ovPDHLkV1n0^TkZe= diff --git a/src/assets/svg/arbitrum_logo.svg b/src/assets/svg/arbitrum_logo.svg deleted file mode 100644 index a1cce5c33dc..00000000000 --- a/src/assets/svg/arbitrum_logo.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/svg/avax_square_logo.svg b/src/assets/svg/avax_square_logo.svg deleted file mode 100644 index a3827f1c126..00000000000 --- a/src/assets/svg/avax_square_logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/svg/base_logo.svg b/src/assets/svg/base_logo.svg deleted file mode 100644 index d0303fb1532..00000000000 --- a/src/assets/svg/base_logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/svg/base_square_logo.svg b/src/assets/svg/base_square_logo.svg deleted file mode 100644 index 32371c259be..00000000000 --- a/src/assets/svg/base_square_logo.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/svg/bnb_square_logo.svg b/src/assets/svg/bnb_square_logo.svg deleted file mode 100644 index 9e1b2e0ec18..00000000000 --- a/src/assets/svg/bnb_square_logo.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/src/assets/svg/celo_square_logo.svg b/src/assets/svg/celo_square_logo.svg deleted file mode 100644 index 801df121761..00000000000 --- a/src/assets/svg/celo_square_logo.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/src/assets/svg/ethereum_square_logo.svg b/src/assets/svg/ethereum_square_logo.svg deleted file mode 100644 index ea3c75c4442..00000000000 --- a/src/assets/svg/ethereum_square_logo.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/assets/svg/optimism_square_logo.svg b/src/assets/svg/optimism_square_logo.svg deleted file mode 100644 index fc615102622..00000000000 --- a/src/assets/svg/optimism_square_logo.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/assets/svg/optimistic_ethereum.svg b/src/assets/svg/optimistic_ethereum.svg deleted file mode 100644 index 2e5ce3103d0..00000000000 --- a/src/assets/svg/optimistic_ethereum.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/assets/svg/polygon-matic-logo.svg b/src/assets/svg/polygon-matic-logo.svg deleted file mode 100644 index 6189792a285..00000000000 --- a/src/assets/svg/polygon-matic-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/svg/polygon_square_logo.svg b/src/assets/svg/polygon_square_logo.svg deleted file mode 100644 index b2e21f59908..00000000000 --- a/src/assets/svg/polygon_square_logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx b/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx index 48b391d3066..4746de6406d 100644 --- a/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.tsx @@ -2,8 +2,8 @@ import { ChainId, Currency } from '@uniswap/sdk-core' import blankTokenUrl from 'assets/svg/blank_token.svg' import { ReactComponent as UnknownStatus } from 'assets/svg/contract-interaction.svg' import { MissingImageLogo } from 'components/Logo/AssetLogo' +import { ChainLogo, getDefaultBorderRadius } from 'components/Logo/ChainLogo' import { Unicon } from 'components/Unicon' -import { getChainInfo } from 'constants/chainInfo' import useTokenLogoSource from 'hooks/useAssetLogoSource' import useENSAvatar from 'hooks/useENSAvatar' import React from 'react' @@ -48,25 +48,14 @@ const ENSAvatarImg = styled.img` width: 40px; ` -const StyledChainLogo = styled.img` - height: 14px; - width: 14px; -` - -const SquareChainLogo = styled.img` - height: 100%; - width: 100%; -` - const CircleLogoImage = styled.img<{ size: string }>` width: ${({ size }) => size}; height: ${({ size }) => size}; border-radius: 50%; ` -const L2LogoContainer = styled.div<{ hasSquareLogo?: boolean }>` - background-color: ${({ theme, hasSquareLogo }) => (hasSquareLogo ? theme.surface2 : theme.neutral1)}; - border-radius: 2px; +const L2LogoContainer = styled.div` + border-radius: ${getDefaultBorderRadius(16)}px; height: 16px; left: 60%; position: absolute; @@ -152,27 +141,21 @@ interface PortfolioLogoProps { function SquareL2Logo({ chainId }: { chainId: ChainId }) { if (chainId === ChainId.MAINNET) return null - const { squareLogoUrl, logoUrl } = getChainInfo(chainId) - - const chainLogo = squareLogoUrl ?? logoUrl return ( - - {squareLogoUrl ? ( - - ) : ( - - )} + + ) } +// TODO(WEB-2983) /** * Renders an image by prioritizing a list of sources, and then eventually a fallback contract icon */ export function PortfolioLogo(props: PortfolioLogoProps) { return ( - + {getLogo(props)} diff --git a/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap b/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap index 6eb8b28d14d..4f273f7e347 100644 --- a/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap +++ b/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap @@ -37,11 +37,6 @@ exports[`PortfolioLogo renders with L2 icon 1`] = ` left: 0; } -.c4 { - height: 14px; - width: 14px; -} - .c2 { width: 40px; height: 40px; @@ -49,8 +44,7 @@ exports[`PortfolioLogo renders with L2 icon 1`] = ` } .c3 { - background-color: #222222; - border-radius: 2px; + border-radius: 4px; height: 16px; left: 60%; position: absolute; @@ -90,11 +84,35 @@ exports[`PortfolioLogo renders with L2 icon 1`] = `
- chainLogo + + + Arbitrum logo + + + + + arbitrum.svg + +
diff --git a/src/components/DoubleLogo/index.tsx b/src/components/DoubleLogo/index.tsx index 73a8ca7cee8..6b7ef9dfc83 100644 --- a/src/components/DoubleLogo/index.tsx +++ b/src/components/DoubleLogo/index.tsx @@ -35,12 +35,12 @@ export default function DoubleCurrencyLogo({ {currency0 && ( - + )} {currency1 && ( - + )} diff --git a/src/components/Logo/AssetLogo.tsx b/src/components/Logo/AssetLogo.tsx index cc709fb086b..ad8fa1e0177 100644 --- a/src/components/Logo/AssetLogo.tsx +++ b/src/components/Logo/AssetLogo.tsx @@ -1,5 +1,4 @@ import { ChainId } from '@uniswap/sdk-core' -import { getChainInfo } from 'constants/chainInfo' import useTokenLogoSource from 'hooks/useAssetLogoSource' import React, { useState } from 'react' import styled from 'styled-components' @@ -42,7 +41,6 @@ export type AssetLogoBaseProps = { backupImg?: string | null size?: string style?: React.CSSProperties - hideL2Icon?: boolean } type AssetLogoProps = AssetLogoBaseProps & { isNative?: boolean; address?: string | null; chainId?: number } @@ -51,19 +49,6 @@ const LogoContainer = styled.div` display: flex; ` -const L2NetworkLogo = styled.div<{ networkUrl?: string; parentSize: string }>` - --size: ${({ parentSize }) => `calc(${parentSize} / 2)`}; - width: var(--size); - height: var(--size); - position: absolute; - left: 50%; - bottom: 0; - background: url(${({ networkUrl }) => networkUrl}); - background-repeat: no-repeat; - background-size: ${({ parentSize }) => `calc(${parentSize} / 2) calc(${parentSize} / 2)`}; - display: ${({ networkUrl }) => !networkUrl && 'none'}; -` - /** * Renders an image by prioritizing a list of sources, and then eventually a fallback triangle alert */ @@ -75,10 +60,8 @@ export default function AssetLogo({ backupImg, size = '24px', style, - hideL2Icon = false, }: AssetLogoProps) { const [src, nextSrc] = useTokenLogoSource(address, chainId, isNative, backupImg) - const L2Icon = getChainInfo(chainId)?.circleLogoUrl const [imgLoaded, setImgLoaded] = useState(() => { const img = document.createElement('img') img.src = src ?? '' @@ -104,7 +87,6 @@ export default function AssetLogo({ {symbol?.toUpperCase().replace('$', '').replace(/\s+/g, '').slice(0, 3)} )} - {!hideL2Icon && } ) } diff --git a/src/components/Logo/ChainLogo.tsx b/src/components/Logo/ChainLogo.tsx new file mode 100644 index 00000000000..b15b135e259 --- /dev/null +++ b/src/components/Logo/ChainLogo.tsx @@ -0,0 +1,122 @@ +import { ChainId } from '@uniswap/sdk-core' +import { getChainInfo } from 'constants/chainInfo' +import { isSupportedChain, SupportedInterfaceChain } from 'constants/chains' +import { CSSProperties, FunctionComponent } from 'react' +import { useTheme } from 'styled-components' +import { useIsDarkMode } from 'theme/components/ThemeToggle' + +import { ReactComponent as arbitrum } from './ChainSymbols/arbitrum.svg' +import { ReactComponent as avax } from './ChainSymbols/avax.svg' +import { ReactComponent as base } from './ChainSymbols/base.svg' +import { ReactComponent as bnb } from './ChainSymbols/bnb.svg' +import { ReactComponent as celo } from './ChainSymbols/celo.svg' +import { ReactComponent as celoLight } from './ChainSymbols/celo_light.svg' +import { ReactComponent as ethereum } from './ChainSymbols/ethereum.svg' +import { ReactComponent as optimism } from './ChainSymbols/optimism.svg' +import { ReactComponent as polygon } from './ChainSymbols/polygon.svg' + +type SVG = FunctionComponent> +type ChainUI = { Symbol: SVG; bgColor: string; textColor: string } + +export function getChainUI(chainId: SupportedInterfaceChain, darkMode: boolean): ChainUI +export function getChainUI(chainId: ChainId, darkMode: boolean): ChainUI | undefined { + switch (chainId) { + case ChainId.MAINNET: + case ChainId.GOERLI: + case ChainId.SEPOLIA: + return { + Symbol: ethereum, + bgColor: '#6B8AFF33', + textColor: '#6B8AFF', + } + case ChainId.POLYGON: + case ChainId.POLYGON_MUMBAI: + return { + Symbol: polygon, + bgColor: '#9558FF33', + textColor: '#9558FF', + } + case ChainId.ARBITRUM_ONE: + case ChainId.ARBITRUM_GOERLI: + return { + Symbol: arbitrum, + bgColor: '#00A3FF33', + textColor: '#00A3FF', + } + case ChainId.OPTIMISM: + case ChainId.OPTIMISM_GOERLI: + return { + Symbol: optimism, + bgColor: '#FF042033', + textColor: '#FF0420', + } + case ChainId.CELO: + case ChainId.CELO_ALFAJORES: + return darkMode + ? { + Symbol: celo, + bgColor: '#FCFF5233', + textColor: '#FCFF52', + } + : { + Symbol: celoLight, + bgColor: '#FCFF5299', + textColor: '#655947', + } + case ChainId.AVALANCHE: + return { + Symbol: avax, + bgColor: '#E8414233', + textColor: '#E84142', + } + case ChainId.BNB: + return { + Symbol: bnb, + bgColor: '#EAB20033', + textColor: '#EAB200', + } + case ChainId.BASE: + return { + Symbol: base, + bgColor: '#0052FF33', + textColor: '#0052FF', + } + default: + return undefined + } +} + +export const getDefaultBorderRadius = (size: number) => size / 2 - 4 + +type ChainLogoProps = { + chainId: ChainId + className?: string + size?: number + borderRadius?: number + style?: CSSProperties + testId?: string +} +export function ChainLogo({ + chainId, + className, + style, + size = 16, + borderRadius = getDefaultBorderRadius(size), + testId, +}: ChainLogoProps) { + const darkMode = useIsDarkMode() + const { surface2 } = useTheme() + + if (!isSupportedChain(chainId)) return null + const { label } = getChainInfo(chainId) + + const { Symbol, bgColor } = getChainUI(chainId, darkMode) + return ( + + {`${label} logo`} + + + + + ) +} diff --git a/src/components/Logo/ChainSymbols/arbitrum.svg b/src/components/Logo/ChainSymbols/arbitrum.svg new file mode 100644 index 00000000000..ebcbdd5cd85 --- /dev/null +++ b/src/components/Logo/ChainSymbols/arbitrum.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/Logo/ChainSymbols/avax.svg b/src/components/Logo/ChainSymbols/avax.svg new file mode 100644 index 00000000000..ee1f505f05c --- /dev/null +++ b/src/components/Logo/ChainSymbols/avax.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/components/Logo/ChainSymbols/base.svg b/src/components/Logo/ChainSymbols/base.svg new file mode 100644 index 00000000000..39a6229dd20 --- /dev/null +++ b/src/components/Logo/ChainSymbols/base.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Logo/ChainSymbols/bnb.svg b/src/components/Logo/ChainSymbols/bnb.svg new file mode 100644 index 00000000000..9c04309fc4b --- /dev/null +++ b/src/components/Logo/ChainSymbols/bnb.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Logo/ChainSymbols/celo.svg b/src/components/Logo/ChainSymbols/celo.svg new file mode 100644 index 00000000000..48bffb342af --- /dev/null +++ b/src/components/Logo/ChainSymbols/celo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Logo/ChainSymbols/celo_light.svg b/src/components/Logo/ChainSymbols/celo_light.svg new file mode 100644 index 00000000000..3a99877f96e --- /dev/null +++ b/src/components/Logo/ChainSymbols/celo_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Logo/ChainSymbols/ethereum.svg b/src/components/Logo/ChainSymbols/ethereum.svg new file mode 100644 index 00000000000..15053e9206d --- /dev/null +++ b/src/components/Logo/ChainSymbols/ethereum.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Logo/ChainSymbols/optimism.svg b/src/components/Logo/ChainSymbols/optimism.svg new file mode 100644 index 00000000000..83b6d2abc86 --- /dev/null +++ b/src/components/Logo/ChainSymbols/optimism.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Logo/ChainSymbols/polygon.svg b/src/components/Logo/ChainSymbols/polygon.svg new file mode 100644 index 00000000000..074d2d6171d --- /dev/null +++ b/src/components/Logo/ChainSymbols/polygon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Logo/CurrencyLogo.tsx b/src/components/Logo/CurrencyLogo.tsx index 0fccd6a9983..08a7078ab4d 100644 --- a/src/components/Logo/CurrencyLogo.tsx +++ b/src/components/Logo/CurrencyLogo.tsx @@ -15,7 +15,6 @@ export default function CurrencyLogo( address={props.currency?.wrapped.address} symbol={props.symbol ?? props.currency?.symbol} backupImg={(props.currency as TokenInfo)?.logoURI} - hideL2Icon={props.hideL2Icon ?? true} {...props} /> ) diff --git a/src/components/Logo/QueryTokenLogo.tsx b/src/components/Logo/QueryTokenLogo.tsx index b969e19b91f..386cc97c138 100644 --- a/src/components/Logo/QueryTokenLogo.tsx +++ b/src/components/Logo/QueryTokenLogo.tsx @@ -1,32 +1,21 @@ -import { NATIVE_CHAIN_ID } from 'constants/tokens' -import { TokenStandard } from 'graphql/data/__generated__/types-and-hooks' +import { ChainId } from '@uniswap/sdk-core' +import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' import { SearchToken } from 'graphql/data/SearchTokens' import { TokenQueryData } from 'graphql/data/Token' import { TopToken } from 'graphql/data/TopTokens' -import { supportedChainIdFromGQLChain } from 'graphql/data/util' +import { gqlToCurrency, supportedChainIdFromGQLChain } from 'graphql/data/util' +import { useMemo } from 'react' -import AssetLogo, { AssetLogoBaseProps } from './AssetLogo' +import { AssetLogoBaseProps } from './AssetLogo' export default function QueryTokenLogo( props: AssetLogoBaseProps & { token?: TopToken | TokenQueryData | SearchToken } ) { - const chainId = props.token?.chain ? supportedChainIdFromGQLChain(props.token?.chain) : undefined + const chainId = + (props.token?.chain ? supportedChainIdFromGQLChain(props.token?.chain) : ChainId.MAINNET) ?? ChainId.MAINNET + const currency = props.token ? gqlToCurrency(props.token) : undefined - return ( - - ) + return [currency], [currency])} chainId={chainId} {...props} /> } diff --git a/src/components/NavBar/ChainSelector.css.ts b/src/components/NavBar/ChainSelector.css.ts index 0c887ead605..eed1b6c46a7 100644 --- a/src/components/NavBar/ChainSelector.css.ts +++ b/src/components/NavBar/ChainSelector.css.ts @@ -14,10 +14,3 @@ export const ChainSelector = style([ background: 'none', }), ]) - -export const Image = style([ - sprinkles({ - width: '24', - height: '24', - }), -]) diff --git a/src/components/NavBar/ChainSelector.tsx b/src/components/NavBar/ChainSelector.tsx index eb38b64245e..59b9c9963cb 100644 --- a/src/components/NavBar/ChainSelector.tsx +++ b/src/components/NavBar/ChainSelector.tsx @@ -2,6 +2,7 @@ import { t } from '@lingui/macro' import { ChainId } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { showTestnetsAtom } from 'components/AccountDrawer/TestnetsToggle' +import { ChainLogo } from 'components/Logo/ChainLogo' import { MouseoverTooltip } from 'components/Tooltip' import { getConnection } from 'connection' import { ConnectionType } from 'connection/types' @@ -145,7 +146,7 @@ export const ChainSelector = ({ leftAlign }: ChainSelectorProps) => { {!isSupported ? ( ) : ( - {info.label} + )} {isOpen ? : } diff --git a/src/components/NavBar/ChainSelectorRow.tsx b/src/components/NavBar/ChainSelectorRow.tsx index 2b14f3004a1..4adf3b59e87 100644 --- a/src/components/NavBar/ChainSelectorRow.tsx +++ b/src/components/NavBar/ChainSelectorRow.tsx @@ -4,6 +4,7 @@ import { ChainId } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { TraceEvent } from 'analytics' import Loader from 'components/Icons/LoadingSpinner' +import { ChainLogo } from 'components/Logo/ChainLogo' import { getChainInfo } from 'constants/chainInfo' import { CheckMarkIcon } from 'nft/components/icons' import styled, { useTheme } from 'styled-components' @@ -36,14 +37,12 @@ const Container = styled.button<{ disabled: boolean }>` background-color: ${({ disabled, theme }) => (disabled ? 'none' : theme.surface3)}; } ` - const Label = styled.div` grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; ` - const Status = styled.div` grid-column: 3; grid-row: 1; @@ -51,7 +50,6 @@ const Status = styled.div` align-items: center; width: ${LOGO_SIZE}px; ` - const CaptionText = styled.div` color: ${({ theme }) => theme.neutral2}; font-size: 12px; @@ -59,11 +57,6 @@ const CaptionText = styled.div` grid-row: 2; ` -const Logo = styled.img` - height: ${LOGO_SIZE}px; - width: ${LOGO_SIZE}px; - margin-right: 12px; -` interface ChainSelectorRowProps { disabled?: boolean targetChain: ChainId @@ -75,7 +68,6 @@ export default function ChainSelectorRow({ disabled, targetChain, onSelectChain, const active = chainId === targetChain const chainInfo = getChainInfo(targetChain) const label = chainInfo?.label - const logoUrl = chainInfo?.logoUrl const theme = useTheme() @@ -88,7 +80,7 @@ export default function ChainSelectorRow({ disabled, targetChain, onSelectChain, if (!disabled) onSelectChain(targetChain) }} > - {logoUrl && } + {label && } {disabled && ( diff --git a/src/components/NavBar/SearchBarDropdown.tsx b/src/components/NavBar/SearchBarDropdown.tsx index 4ff6f9e52a3..62e44377718 100644 --- a/src/components/NavBar/SearchBarDropdown.tsx +++ b/src/components/NavBar/SearchBarDropdown.tsx @@ -5,7 +5,7 @@ import { useWeb3React } from '@web3-react/core' import { useTrace } from 'analytics' import clsx from 'clsx' import Badge from 'components/Badge' -import { getChainInfo } from 'constants/chainInfo' +import { ChainLogo } from 'components/Logo/ChainLogo' import { HistoryDuration, SafetyLevel } from 'graphql/data/__generated__/types-and-hooks' import { useTrendingCollections } from 'graphql/data/nft/TrendingCollections' import { SearchToken } from 'graphql/data/SearchTokens' @@ -107,11 +107,6 @@ function isKnownToken(token: SearchToken) { return token.project?.safetyLevel == SafetyLevel.Verified || token.project?.safetyLevel == SafetyLevel.MediumWarning } -const ChainLogo = styled.img` - height: 20px; - width: 20px; - margin-right: 8px; -` const ChainComingSoonBadge = styled(Badge)` align-items: center; background-color: ${({ theme }) => theme.surface2}; @@ -123,6 +118,7 @@ const ChainComingSoonBadge = styled(Badge)` padding: 8px; margin: 16px 16px 4px; width: calc(100% - 32px); + gap: 8px; ` interface SearchBarDropdownProps { @@ -138,7 +134,6 @@ export const SearchBarDropdown = (props: SearchBarDropdownProps) => { const { isLoading } = props const { chainId } = useWeb3React() const showChainComingSoonBadge = chainId && BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS.includes(chainId) && !isLoading - const logoUri = getChainInfo(chainId)?.logoUrl return ( @@ -150,7 +145,7 @@ export const SearchBarDropdown = (props: SearchBarDropdownProps) => { {showChainComingSoonBadge && ( - + diff --git a/src/components/NavBar/__snapshots__/ChainSelectorRow.test.tsx.snap b/src/components/NavBar/__snapshots__/ChainSelectorRow.test.tsx.snap index df639228f67..c535254596c 100644 --- a/src/components/NavBar/__snapshots__/ChainSelectorRow.test.tsx.snap +++ b/src/components/NavBar/__snapshots__/ChainSelectorRow.test.tsx.snap @@ -30,14 +30,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 1 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -51,12 +51,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 1 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -68,18 +62,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 1 1`] = ` class="c0" data-testid="Ethereum-selector" > - Ethereum + + + Ethereum logo + + + + + ethereum.svg + +
Ethereum
- Görli + + + Görli logo + + + + + ethereum.svg + +
Görli
@@ -216,14 +254,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 10 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -237,12 +275,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 10 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -254,18 +286,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 10 1`] = ` class="c0" data-testid="Optimism-selector" > - Optimism + + + Optimism logo + + + + + optimism.svg + +
Optimism
@@ -301,14 +358,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 56 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -322,12 +379,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 56 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -339,18 +390,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 56 1`] = ` class="c0" data-testid="BNB Chain-selector" > - BNB Chain + + + BNB Chain logo + + + + + bnb.svg + +
BNB Chain
@@ -386,14 +462,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 137 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -407,12 +483,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 137 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -424,18 +494,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 137 1`] = ` class="c0" data-testid="Polygon-selector" > - Polygon + + + Polygon logo + + + + + polygon.svg + +
Polygon
@@ -471,14 +566,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 420 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -492,12 +587,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 420 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -509,18 +598,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 420 1`] = ` class="c0" data-testid="Optimism Görli-selector" > - Optimism Görli + + + Optimism Görli logo + + + + + optimism.svg + +
Optimism Görli
@@ -556,14 +670,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 8453 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -577,12 +691,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 8453 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -594,18 +702,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 8453 1`] = ` class="c0" data-testid="Base-selector" > - Base + + + Base logo + + + + + base.svg + +
Base
@@ -641,14 +774,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 42161 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -662,12 +795,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 42161 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -679,18 +806,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 42161 1`] = ` class="c0" data-testid="Arbitrum-selector" > - Arbitrum + + + Arbitrum logo + + + + + arbitrum.svg + +
Arbitrum
@@ -726,14 +878,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 42220 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -747,12 +899,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 42220 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -764,18 +910,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 42220 1`] = ` class="c0" data-testid="Celo-selector" > - Celo + + + Celo logo + + + + + celo_light.svg + +
Celo
@@ -811,14 +982,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 43114 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -832,12 +1003,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 43114 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -849,18 +1014,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 43114 1`] = ` class="c0" data-testid="Avalanche-selector" > - Avalanche + + + Avalanche logo + + + + + avax.svg + +
Avalanche
@@ -896,14 +1086,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 44787 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -917,12 +1107,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 44787 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -934,18 +1118,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 44787 1`] = ` class="c0" data-testid="Celo Alfajores-selector" > - Celo Alfajores + + + Celo Alfajores logo + + + + + celo_light.svg + +
Celo Alfajores
@@ -981,14 +1190,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 80001 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -1002,12 +1211,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 80001 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -1019,18 +1222,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 80001 1`] = ` class="c0" data-testid="Polygon Mumbai-selector" > - Polygon Mumbai + + + Polygon Mumbai logo + + + + + polygon.svg + +
Polygon Mumbai
@@ -1128,14 +1356,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 421613 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -1149,12 +1377,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 421613 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -1166,18 +1388,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 421613 1`] = ` class="c0" data-testid="Arbitrum Goerli-selector" > - Arbitrum Goerli + + + Arbitrum Goerli logo + + + + + arbitrum.svg + +
Arbitrum Goerli
@@ -1213,14 +1460,14 @@ exports[`ChainSelectorRow should match snapshot for chainId 11155111 1`] = ` background-color: #22222212; } -.c2 { +.c1 { grid-column: 2; grid-row: 1; font-size: 16px; font-weight: 485; } -.c3 { +.c2 { grid-column: 3; grid-row: 1; display: -webkit-box; @@ -1234,12 +1481,6 @@ exports[`ChainSelectorRow should match snapshot for chainId 11155111 1`] = ` width: 20px; } -.c1 { - height: 20px; - width: 20px; - margin-right: 12px; -} - @media only screen and (max-width:640px) { .c0 { width: 100%; @@ -1251,18 +1492,43 @@ exports[`ChainSelectorRow should match snapshot for chainId 11155111 1`] = ` class="c0" data-testid="Sepolia-selector" > - Sepolia + + + Sepolia logo + + + + + ethereum.svg + +
Sepolia
diff --git a/src/components/NetworkAlert/NetworkAlert.tsx b/src/components/NetworkAlert/NetworkAlert.tsx index e2a7cfe9313..dca61014a42 100644 --- a/src/components/NetworkAlert/NetworkAlert.tsx +++ b/src/components/NetworkAlert/NetworkAlert.tsx @@ -1,200 +1,75 @@ import { Trans } from '@lingui/macro' -import { ChainId } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' +import { getChainUI } from 'components/Logo/ChainLogo' +import { RowBetween } from 'components/Row' import { getChainInfo } from 'constants/chainInfo' +import { isSupportedChain } from 'constants/chains' import { ArrowUpRight } from 'react-feather' import styled from 'styled-components' -import { colors } from 'theme/colors' -import { ExternalLink, HideSmall } from 'theme/components' -import { useDarkModeManager } from 'theme/components/ThemeToggle' +import { ExternalLink, HideSmall, ThemedText } from 'theme/components' +import { useIsDarkMode } from 'theme/components/ThemeToggle' import Column from '../Column' -const L2Icon = styled.img` - width: 24px; - height: 24px; - margin-right: 16px; -` - -const BodyText = styled.div` +const BridgeLink = styled(ExternalLink)<{ bgColor: string }>` color: ${({ color }) => color}; - display: flex; + background: ${({ bgColor }) => bgColor}; align-items: center; - justify-content: flex-start; - margin: 8px; - font-size: 14px; - line-height: 20px; -` -const RootWrapper = styled.div` - margin-top: 16px; -` - -const SHOULD_SHOW_ALERT = { - [ChainId.OPTIMISM]: true, - [ChainId.OPTIMISM_GOERLI]: true, - [ChainId.ARBITRUM_ONE]: true, - [ChainId.ARBITRUM_GOERLI]: true, - [ChainId.POLYGON]: true, - [ChainId.POLYGON_MUMBAI]: true, - [ChainId.CELO]: true, - [ChainId.CELO_ALFAJORES]: true, - [ChainId.BNB]: true, - [ChainId.AVALANCHE]: true, - [ChainId.BASE]: true, -} - -type NetworkAlertChains = keyof typeof SHOULD_SHOW_ALERT - -const BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID: { - [darkMode in 'dark' | 'light']: { [chainId in NetworkAlertChains]: string } -} = { - dark: { - [ChainId.POLYGON]: - 'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)', - [ChainId.POLYGON_MUMBAI]: - 'radial-gradient(100% 93.36% at 0% 6.64%, rgba(160, 108, 247, 0.1) 0%, rgba(82, 32, 166, 0.1) 100%)', - [ChainId.CELO]: - 'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)', - [ChainId.CELO_ALFAJORES]: - 'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(90, 190, 170, 0.15) 0%, rgba(80, 160, 40, 0.15) 100%)', - [ChainId.BNB]: - 'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(240, 185, 11, 0.16) 0%, rgba(255, 168, 0, 0.16) 100%)', - [ChainId.OPTIMISM]: - '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.OPTIMISM_GOERLI]: - 'radial-gradient(948% 292% at 42% 0%, rgba(255, 58, 212, 0.04) 0%, rgba(255, 255, 255, 0.04) 100%),radial-gradient(98% 96% at 2% 0%, rgba(255, 39, 39, 0.04) 0%, rgba(235, 0, 255, 0.01 96%)', - [ChainId.ARBITRUM_ONE]: - 'radial-gradient(285% 8200% at 30% 50%, rgba(40, 160, 240, 0.01) 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.05) 100%), hsla(0, 0%, 100%, 0.05)', - [ChainId.ARBITRUM_GOERLI]: - '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);', - }, - light: { - [ChainId.POLYGON]: - 'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)', - [ChainId.POLYGON_MUMBAI]: - 'radial-gradient(182.71% 205.59% at 2.81% 7.69%, rgba(130, 71, 229, 0.2) 0%, rgba(167, 202, 255, 0.2) 100%)', - [ChainId.CELO]: - 'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)', - [ChainId.CELO_ALFAJORES]: - 'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(63, 208, 137, 0.15) 0%, rgba(49, 205, 50, 0.15) 100%)', - [ChainId.BNB]: - 'radial-gradient(182.71% 150.59% at 2.81% 7.69%, rgba(240, 185, 11, 0.16) 0%, rgba(255, 168, 0, 0.16) 100%)', - [ChainId.OPTIMISM]: - '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.OPTIMISM_GOERLI]: - '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.ARBITRUM_ONE]: - '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.ARBITRUM_GOERLI]: - '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)', - }, -} + border-radius: 8px; + color: white; + display: flex; + font-size: 16px; + justify-content: space-between; + padding: 12px 18px 12px 12px; + text-decoration: none !important; + width: 100%; -const ContentWrapper = styled.div<{ chainId: NetworkAlertChains; darkMode: boolean; logoUrl: string }>` - background: ${({ chainId, darkMode }) => BG_COLORS_BY_DARK_MODE_AND_CHAIN_ID[darkMode ? 'dark' : 'light'][chainId]}; border-radius: 20px; display: flex; flex-direction: row; + gap: 12px; overflow: hidden; position: relative; width: 100%; - :before { - background-image: url(${({ logoUrl }) => logoUrl}); - background-repeat: no-repeat; - background-size: 300px; - content: ''; - height: 300px; - opacity: 0.1; - position: absolute; - transform: rotate(25deg) translate(-90px, -40px); - width: 300px; - pointer-events: none; - } -` -const Header = styled.h2` - font-weight: 535; - font-size: 16px; - margin: 0; + margin-top: 16px; ` -const LinkOutToBridge = styled(ExternalLink)` - align-items: center; - border-radius: 8px; - color: white; - display: flex; - font-size: 16px; - justify-content: space-between; - padding: 6px 8px; - text-decoration: none !important; - width: 100%; +const TitleText = styled(ThemedText.BodyPrimary)<{ $color: string }>` + font-weight: 535; + color: ${({ $color }) => $color}; ` -const StyledArrowUpRight = styled(ArrowUpRight)` - margin-left: 12px; - width: 24px; - height: 24px; +const SubtitleText = styled(ThemedText.BodySmall)<{ $color: string }>` + line-height: 20px; + color: ${({ $color }) => $color}; ` -const TEXT_COLORS: { [chainId in NetworkAlertChains]: string } = { - [ChainId.POLYGON]: 'rgba(130, 71, 229)', - [ChainId.POLYGON_MUMBAI]: 'rgba(130, 71, 229)', - [ChainId.CELO]: 'rgba(53, 178, 97)', - [ChainId.CELO_ALFAJORES]: 'rgba(53, 178, 97)', - [ChainId.OPTIMISM]: '#ff3856', - [ChainId.OPTIMISM_GOERLI]: '#ff3856', - [ChainId.ARBITRUM_ONE]: '#0490ed', - [ChainId.BNB]: colors.gold400, - [ChainId.ARBITRUM_GOERLI]: '#0490ed', - [ChainId.AVALANCHE]: '#ff3856', - [ChainId.BASE]: colors.networkBase, -} - -function shouldShowAlert(chainId: number | undefined): chainId is NetworkAlertChains { - return Boolean(chainId && SHOULD_SHOW_ALERT[chainId as unknown as NetworkAlertChains]) -} - export function NetworkAlert() { const { chainId } = useWeb3React() - const [darkMode] = useDarkModeManager() - - if (!shouldShowAlert(chainId)) { - return null - } - - const chainInfo = getChainInfo(chainId) + const darkMode = useIsDarkMode() - if (!chainInfo) return null + if (!chainId || !isSupportedChain(chainId)) return null - const { label, logoUrl, bridge } = chainInfo - const textColor = TEXT_COLORS[chainId] + const { Symbol: ChainSymbol, bgColor, textColor } = getChainUI(chainId, darkMode) + const { label, bridge } = getChainInfo(chainId) return bridge ? ( - - - - - - -
- {label} token bridge -
- - Deposit tokens to the {label} network. - -
-
- -
-
-
+ + + + + + {label} token bridge + + + + Deposit tokens to the {label} network. + + + + + + ) : null } diff --git a/src/components/Tokens/TokenDetails/BalanceSummary.tsx b/src/components/Tokens/TokenDetails/BalanceSummary.tsx index 649427aa58b..55895773c7d 100644 --- a/src/components/Tokens/TokenDetails/BalanceSummary.tsx +++ b/src/components/Tokens/TokenDetails/BalanceSummary.tsx @@ -1,11 +1,12 @@ import { Trans } from '@lingui/macro' import { ChainId, Currency } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' -import CurrencyLogo from 'components/Logo/CurrencyLogo' +import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' import { getChainInfo } from 'constants/chainInfo' import { asSupportedChain } from 'constants/chains' import { useStablecoinValue } from 'hooks/useStablecoinPrice' import useCurrencyBalance from 'lib/hooks/useCurrencyBalance' +import { useMemo } from 'react' import styled, { useTheme } from 'styled-components' import { ThemedText } from 'theme/components' import { NumberType, useFormatter } from 'utils/formatNumbers' @@ -75,6 +76,8 @@ export default function BalanceSummary({ token }: { token: Currency }) { type: NumberType.FiatTokenStats, }) + const currencies = useMemo(() => [token], [token]) + if (!account || !balance) { return null } @@ -85,7 +88,7 @@ export default function BalanceSummary({ token }: { token: Currency }) { Your balance on {label} - + diff --git a/src/components/Tokens/TokenDetails/index.tsx b/src/components/Tokens/TokenDetails/index.tsx index c5b73daf7d0..11c8dd983c5 100644 --- a/src/components/Tokens/TokenDetails/index.tsx +++ b/src/components/Tokens/TokenDetails/index.tsx @@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro' import { InterfacePageName } from '@uniswap/analytics-events' import { useWeb3React } from '@web3-react/core' import { Trace } from 'analytics' -import CurrencyLogo from 'components/Logo/CurrencyLogo' +import { PortfolioLogo } from 'components/AccountDrawer/MiniPortfolio/PortfolioLogo' import { AboutSection } from 'components/Tokens/TokenDetails/About' import AddressSection from 'components/Tokens/TokenDetails/AddressSection' import BalanceSummary from 'components/Tokens/TokenDetails/BalanceSummary' @@ -212,7 +212,7 @@ export default function TokenDetails({ - + {detailedToken.name ?? Name not found} {detailedToken.symbol ?? Symbol not found} diff --git a/src/components/Tokens/TokenTable/NetworkFilter.tsx b/src/components/Tokens/TokenTable/NetworkFilter.tsx index 502f2f9c937..3a9b39adf51 100644 --- a/src/components/Tokens/TokenTable/NetworkFilter.tsx +++ b/src/components/Tokens/TokenTable/NetworkFilter.tsx @@ -1,4 +1,5 @@ import Badge from 'components/Badge' +import { ChainLogo } from 'components/Logo/ChainLogo' import { getChainInfo } from 'constants/chainInfo' import { BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS, @@ -91,10 +92,6 @@ const NetworkLabel = styled.div` gap: 8px; align-items: center; ` -const Logo = styled.img` - height: 20px; - width: 20px; -` const CheckContainer = styled.div` display: flex; flex-direction: flex-end; @@ -118,10 +115,10 @@ export default function NetworkFilter() { useOnClickOutside(node, open ? toggleMenu : undefined) const navigate = useNavigate() - const { chainName } = useParams<{ chainName?: string }>() - const currentChainName = validateUrlChainParam(chainName) + const currentChainName = validateUrlChainParam(useParams().chainName) + const chainId = supportedChainIdFromGQLChain(currentChainName) - const chainInfo = getChainInfo(supportedChainIdFromGQLChain(currentChainName)) + const chainInfo = getChainInfo(chainId) return ( @@ -133,7 +130,7 @@ export default function NetworkFilter() { > - {chainInfo.label} + {chainInfo.label} {open ? ( @@ -147,7 +144,8 @@ export default function NetworkFilter() { {open && ( {BACKEND_SUPPORTED_CHAINS.map((network) => { - const chainInfo = getChainInfo(supportedChainIdFromGQLChain(network)) + const chainId = supportedChainIdFromGQLChain(network) + const chainInfo = getChainInfo(chainId) return ( - - {chainInfo.label} + {chainInfo.label} {network === currentChainName && ( @@ -178,8 +175,7 @@ export default function NetworkFilter() { disabled > - - {chainInfo.label} + {chainInfo.label} Coming soon diff --git a/src/components/Tokens/TokenTable/TokenRow.tsx b/src/components/Tokens/TokenTable/TokenRow.tsx index 88706f1dece..0f5c9a18396 100644 --- a/src/components/Tokens/TokenTable/TokenRow.tsx +++ b/src/components/Tokens/TokenTable/TokenRow.tsx @@ -113,7 +113,7 @@ const ClickableContent = styled.div<{ gap?: number }>` cursor: pointer; ` const ClickableName = styled(ClickableContent)` - gap: 8px; + gap: 12px; max-width: 100%; ` const StyledHeaderRow = styled(StyledTokenRow)` @@ -480,7 +480,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef - + {token.name} {token.symbol} diff --git a/src/components/Tokens/TokenTable/__snapshots__/TokenRow.test.tsx.snap b/src/components/Tokens/TokenTable/__snapshots__/TokenRow.test.tsx.snap index c739db76a43..9f6bf952449 100644 --- a/src/components/Tokens/TokenTable/__snapshots__/TokenRow.test.tsx.snap +++ b/src/components/Tokens/TokenTable/__snapshots__/TokenRow.test.tsx.snap @@ -2,51 +2,23 @@ exports[`LoadedRow.tsx renders a row 1`] = ` - .c9 { - opacity: 0; - -webkit-transition: opacity 250ms ease-in; - transition: opacity 250ms ease-in; - width: 24px; - height: 24px; - border-radius: 50%; + .c7 { + position: relative; + top: 0; + left: 0; } .c8 { - width: 24px; - height: 24px; - background: #22222212; - -webkit-transition: background-color 250ms ease-in; - transition: background-color 250ms ease-in; - box-shadow: 0 0 1px white; + width: 32px; + height: 32px; border-radius: 50%; } -.c7 { - position: relative; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.c10 { - --size: calc(24px / 2); - width: var(--size); - height: var(--size); - position: absolute; - left: 50%; - bottom: 0; - background: url(); - background-repeat: no-repeat; - background-size: calc(24px / 2) calc(24px / 2); - display: none; -} - -.c18 { +.c16 { color: #40B66B; } -.c19 { +.c17 { color: #40B66B; } @@ -105,7 +77,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` cursor: pointer; } -.c21 { +.c19 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -122,7 +94,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } .c6 { - gap: 8px; + gap: 12px; max-width: 100%; } @@ -132,7 +104,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` font-size: 14px; } -.c14 { +.c12 { -webkit-box-pack: end; -webkit-justify-content: flex-end; -ms-flex-pack: end; @@ -146,7 +118,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` transition: background-color 250ms ease; } -.c22 { +.c20 { padding-right: 8px; } @@ -160,19 +132,19 @@ exports[`LoadedRow.tsx renders a row 1`] = ` gap: 8px; } -.c15 { +.c13 { padding-right: 8px; } -.c20 { +.c18 { padding-right: 8px; } -.c17 { +.c15 { display: none; } -.c16 { +.c14 { -webkit-box-pack: end; -webkit-justify-content: flex-end; -ms-flex-pack: end; @@ -182,12 +154,12 @@ exports[`LoadedRow.tsx renders a row 1`] = ` flex: 1; } -.c24 { +.c22 { padding: 0px 24px; min-width: 120px; } -.c25 { +.c23 { width: 124px; height: 42px; } @@ -197,7 +169,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` text-decoration: none; } -.c11 { +.c9 { gap: 8px; line-height: 24px; font-size: 16px; @@ -207,19 +179,19 @@ exports[`LoadedRow.tsx renders a row 1`] = ` white-space: nowrap; } -.c12 { +.c10 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; } -.c13 { +.c11 { color: #7D7D7D; text-transform: uppercase; } -.c23 { +.c21 { padding-right: 8px; } @@ -260,7 +232,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } @media only screen and (max-width:720px) { - .c22 { + .c20 { display: none; } } @@ -272,13 +244,13 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } @media only screen and (max-width:540px) { - .c20 { + .c18 { display: none; } } @media only screen and (max-width:540px) { - .c17 { + .c15 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -295,7 +267,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } @media only screen and (max-width:540px) { - .c16 { + .c14 { -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; @@ -307,13 +279,13 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } @media only screen and (max-width:1200px) { - .c24 { + .c22 { display: none; } } @media only screen and (max-width:540px) { - .c11 { + .c9 { -webkit-box-pack: start; -webkit-justify-content: flex-start; -ms-flex-pack: start; @@ -330,7 +302,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } @media only screen and (max-width:540px) { - .c13 { + .c11 { font-size: 12px; height: 16px; -webkit-box-pack: start; @@ -342,7 +314,7 @@ exports[`LoadedRow.tsx renders a row 1`] = ` } @media only screen and (max-width:840px) { - .c23 { + .c21 { display: none; } } @@ -371,32 +343,23 @@ exports[`LoadedRow.tsx renders a row 1`] = ` >
-
- USDC logo -
-
USD Coin
USDC
@@ -404,22 +367,22 @@ exports[`LoadedRow.tsx renders a row 1`] = `
$1.00
0.00% @@ -441,15 +404,15 @@ exports[`LoadedRow.tsx renders a row 1`] = `
0.00%
` padding: ${({ inline }) => (inline ? '20px 0' : '32px 0;')}; ` -const StyledLogo = styled.img` - height: 16px; - width: 16px; - margin-left: 6px; -` - const ConfirmationModalContentWrapper = styled(AutoColumn)` padding-bottom: 12px; ` @@ -231,8 +226,8 @@ function L2Content({ {!inline && ( - - + + {info.label} diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx index d8ab028cbfb..98796538d2d 100644 --- a/src/components/swap/ConfirmSwapModal.tsx +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -9,6 +9,7 @@ import { Currency, Percent } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, Trace, useTrace } from 'analytics' import Badge from 'components/Badge' +import { ChainLogo } from 'components/Logo/ChainLogo' import Modal, { MODAL_TRANSITION_DURATION } from 'components/Modal' import { RowFixed } from 'components/Row' import { getChainInfo } from 'constants/chainInfo' @@ -55,11 +56,6 @@ const StyledL2Badge = styled(Badge)` padding: 6px 8px; ` -const StyledL2Logo = styled.img` - height: 16px; - width: 16px; -` - function isInApprovalPhase(confirmModalState: ConfirmModalState) { return ( confirmModalState === ConfirmModalState.RESETTING_TOKEN_ALLOWANCE || @@ -412,7 +408,7 @@ export default function ConfirmSwapModal({ return ( - + {info.label} diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index 397975b91a1..a4ad6400a5d 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -1,10 +1,10 @@ import { t, Trans } from '@lingui/macro' import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core' import { LoadingRow } from 'components/Loader/styled' +import { ChainLogo } from 'components/Logo/ChainLogo' import RouterLabel from 'components/RouterLabel' import Row, { RowBetween } from 'components/Row' import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' -import { getChainInfo } from 'constants/chainInfo' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import useHoverProps from 'hooks/useHoverProps' import { useIsMobile } from 'nft/hooks' @@ -128,7 +128,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { if (isPreview) return return ( - gas cost icon + {formatNumber({ input: trade.totalGasUseEstimateUSD, type: NumberType.FiatGasPrice })} ) diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index 0391cdb5f22..c838b3cdeed 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -399,12 +399,35 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $1.00
diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 05b2fcf01b1..c1f7d1105b8 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -198,12 +198,35 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $0.00
@@ -1199,12 +1222,35 @@ exports[`SwapLineItem.tsx exact input 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $1.00
@@ -2629,12 +2675,35 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $1.00
@@ -4059,12 +4128,35 @@ exports[`SwapLineItem.tsx exact output 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + -
@@ -5489,12 +5581,35 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $1.00
@@ -7125,12 +7240,35 @@ exports[`SwapLineItem.tsx fee on sell 1`] = `
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $1.00
diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index 66278adacc9..093c9e8efb1 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -281,12 +281,35 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
- gas cost icon + > + + Ethereum logo + + + + + ethereum.svg + + $1.00
diff --git a/src/constants/chainInfo.ts b/src/constants/chainInfo.ts index 9fe2a175e51..6166348dfcd 100644 --- a/src/constants/chainInfo.ts +++ b/src/constants/chainInfo.ts @@ -1,20 +1,4 @@ import { ChainId } from '@uniswap/sdk-core' -import bnbCircleLogoUrl from 'assets/images/bnbCircle.svg' -import ethereumLogoUrl from 'assets/images/ethereum-logo.png' -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' -import celoSquareLogoUrl from 'assets/svg/celo_square_logo.svg' -import optimismSquareLogoUrl from 'assets/svg/optimism_square_logo.svg' -import optimismLogoUrl from 'assets/svg/optimistic_ethereum.svg' -import polygonSquareLogoUrl from 'assets/svg/polygon_square_logo.svg' -import polygonMaticLogo from 'assets/svg/polygon-matic-logo.svg' import ms from 'ms' import { darkTheme } from 'theme/colors' @@ -46,9 +30,6 @@ interface BaseChainInfo { readonly bridge?: string readonly explorer: string readonly infoLink: string - readonly logoUrl: string - readonly circleLogoUrl?: string - readonly squareLogoUrl?: string readonly label: string readonly helpCenterUrl?: string readonly nativeCurrency: { @@ -83,7 +64,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://etherscan.io/', infoLink: 'https://info.uniswap.org/#/', label: 'Ethereum', - logoUrl: ethereumLogoUrl, nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, color: darkTheme.chain_1, }, @@ -93,7 +73,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://goerli.etherscan.io/', infoLink: 'https://info.uniswap.org/#/', label: 'Görli', - logoUrl: ethereumLogoUrl, nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 }, color: darkTheme.chain_5, }, @@ -103,7 +82,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://sepolia.etherscan.io/', infoLink: 'https://info.uniswap.org/#/', label: 'Sepolia', - logoUrl: ethereumLogoUrl, nativeCurrency: { name: 'Sepolia Ether', symbol: 'SepoliaETH', decimals: 18 }, color: darkTheme.chain_5, }, @@ -116,10 +94,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://optimistic.etherscan.io/', infoLink: 'https://info.uniswap.org/#/optimism/', label: 'Optimism', - logoUrl: optimismLogoUrl, - // Optimism perfers same icon for both - circleLogoUrl: optimismLogoUrl, - squareLogoUrl: optimismSquareLogoUrl, statusPage: 'https://optimism.io/status', helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, @@ -135,7 +109,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://goerli-optimism.etherscan.io/', infoLink: 'https://info.uniswap.org/#/optimism/', label: 'Optimism Görli', - logoUrl: optimismLogoUrl, statusPage: 'https://optimism.io/status', helpCenterUrl: 'https://help.uniswap.org/en/collections/3137778-uniswap-on-optimistic-ethereum-oξ', nativeCurrency: { name: 'Optimism Goerli Ether', symbol: 'görOpETH', decimals: 18 }, @@ -149,8 +122,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://arbiscan.io/', infoLink: 'https://info.uniswap.org/#/arbitrum', label: 'Arbitrum', - logoUrl: arbitrumLogoUrl, - circleLogoUrl: arbitrumCircleLogoUrl, defaultListUrl: ARBITRUM_LIST, helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, @@ -165,7 +136,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://goerli.arbiscan.io/', infoLink: 'https://info.uniswap.org/#/arbitrum/', label: 'Arbitrum Goerli', - logoUrl: arbitrumLogoUrl, defaultListUrl: ARBITRUM_LIST, // TODO: use arbitrum goerli token list helpCenterUrl: 'https://help.uniswap.org/en/collections/3137787-uniswap-on-arbitrum', nativeCurrency: { name: 'Goerli Arbitrum Ether', symbol: 'goerliArbETH', decimals: 18 }, @@ -179,9 +149,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://polygonscan.com/', infoLink: 'https://info.uniswap.org/#/polygon/', label: 'Polygon', - logoUrl: polygonMaticLogo, - circleLogoUrl: polygonCircleLogoUrl, - squareLogoUrl: polygonSquareLogoUrl, nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 }, color: darkTheme.chain_137, backgroundColor: darkTheme.chain_137_background, @@ -194,7 +161,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://mumbai.polygonscan.com/', infoLink: 'https://info.uniswap.org/#/polygon/', label: 'Polygon Mumbai', - logoUrl: polygonMaticLogo, nativeCurrency: { name: 'Polygon Mumbai Matic', symbol: 'mMATIC', decimals: 18 }, }, [ChainId.CELO]: { @@ -205,9 +171,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://celoscan.io/', infoLink: 'https://info.uniswap.org/#/celo/', label: 'Celo', - logoUrl: celoLogo, - circleLogoUrl: celoLogo, - squareLogoUrl: celoSquareLogoUrl, nativeCurrency: { name: 'Celo', symbol: 'CELO', decimals: 18 }, defaultListUrl: CELO_LIST, }, @@ -219,7 +182,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://alfajores-blockscout.celo-testnet.org/', infoLink: 'https://info.uniswap.org/#/celo/', label: 'Celo Alfajores', - logoUrl: celoLogo, nativeCurrency: { name: 'Celo', symbol: 'CELO', decimals: 18 }, defaultListUrl: CELO_LIST, }, @@ -231,9 +193,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://bscscan.com/', infoLink: 'https://info.uniswap.org/#/bnb/', label: 'BNB Chain', - logoUrl: bnbLogo, - circleLogoUrl: bnbCircleLogoUrl, - squareLogoUrl: bnbSquareLogoUrl, nativeCurrency: { name: 'BNB', symbol: 'BNB', decimals: 18 }, defaultListUrl: PLASMA_BNB_LIST, color: darkTheme.chain_56, @@ -247,9 +206,6 @@ const CHAIN_INFO: ChainInfoMap = { explorer: 'https://snowtrace.io/', infoLink: 'https://info.uniswap.org/#/avax/', // TODO(WEB-2336): Add avax support to info site label: 'Avalanche', - logoUrl: avaxLogo, - circleLogoUrl: avaxLogo, - squareLogoUrl: avaxSquareLogo, nativeCurrency: { name: 'AVAX', symbol: 'AVAX', decimals: 18 }, defaultListUrl: AVALANCHE_LIST, color: darkTheme.chain_43114, @@ -264,10 +220,7 @@ const CHAIN_INFO: ChainInfoMap = { 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, }, diff --git a/src/graphql/data/util.tsx b/src/graphql/data/util.tsx index 65ce6f2d8ba..d5088efbecb 100644 --- a/src/graphql/data/util.tsx +++ b/src/graphql/data/util.tsx @@ -117,7 +117,8 @@ export function gqlToCurrency(token: { }): Currency | undefined { const chainId = supportedChainIdFromGQLChain(token.chain) if (!chainId) return undefined - if (token.standard === TokenStandard.Native || !token.address) return nativeOnChain(chainId) + if (token.standard === TokenStandard.Native || token.address === 'NATIVE' || !token.address) + return nativeOnChain(chainId) else return new Token(chainId, token.address, token.decimals ?? 18, token.symbol, token.name) } diff --git a/src/pages/PoolDetails/PoolDetailsHeader.tsx b/src/pages/PoolDetails/PoolDetailsHeader.tsx index 158134ad53f..0588490b17a 100644 --- a/src/pages/PoolDetails/PoolDetailsHeader.tsx +++ b/src/pages/PoolDetails/PoolDetailsHeader.tsx @@ -2,8 +2,8 @@ import { Trans } from '@lingui/macro' import { ChainId, Currency } from '@uniswap/sdk-core' import blankTokenUrl from 'assets/svg/blank_token.svg' import Column from 'components/Column' +import { ChainLogo } from 'components/Logo/ChainLogo' import Row from 'components/Row' -import { getChainInfo } from 'constants/chainInfo' import { chainIdToBackendName } from 'graphql/data/util' import { useCurrency } from 'hooks/Tokens' import useTokenLogoSource from 'hooks/useAssetLogoSource' @@ -116,8 +116,8 @@ function DoubleCurrencyAndChainLogo({ ) } -const L2LogoContainer = styled.div<{ hasSquareLogo?: boolean }>` - background-color: ${({ theme, hasSquareLogo }) => (hasSquareLogo ? theme.surface2 : theme.neutral1)}; +const L2LogoContainer = styled.div` + background-color: ${({ theme }) => theme.surface2}; border-radius: 2px; height: 12px; left: 60%; @@ -130,29 +130,12 @@ const L2LogoContainer = styled.div<{ hasSquareLogo?: boolean }>` justify-content: center; ` -const StyledChainLogo = styled.img` - height: 12px; - width: 12px; -` - -const SquareChainLogo = styled.img` - height: 100%; - width: 100%; -` - function SquareL2Logo({ chainId }: { chainId: ChainId }) { if (chainId === ChainId.MAINNET) return null - const { squareLogoUrl, logoUrl } = getChainInfo(chainId) - - const chainLogo = squareLogoUrl ?? logoUrl return ( - - {squareLogoUrl ? ( - - ) : ( - - )} + + ) } From f09ded1a3f4798d62f6801311c98885bed705cf3 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:51:26 -0700 Subject: [PATCH 26/61] fix: user prop updater staging (#7443) fix: move user prop updater into statsigProvider --- src/pages/App.tsx | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 89648990c7d..3f15ce0356a 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -97,12 +97,10 @@ export default function App() { const location = useLocation() const { pathname } = location const currentPage = getCurrentPageFromLocation(pathname) - const isDarkMode = useIsDarkMode() - const [routerPreference] = useRouterPreference() + const [scrollY, setScrollY] = useState(0) const scrolledState = scrollY > 0 - const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() - const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() + const routerConfig = useRouterConfig() const originCountry = useAppSelector((state: AppState) => state.user.originCountry) @@ -122,53 +120,6 @@ export default function App() { } }, [searchParams, setShouldDisableNFTRoutes]) - useEffect(() => { - // User properties *must* be set before sending corresponding event properties, - // so that the event contains the correct and up-to-date user properties. - user.set(CustomUserProperties.USER_AGENT, navigator.userAgent) - user.set(CustomUserProperties.BROWSER, getBrowser()) - user.set(CustomUserProperties.SCREEN_RESOLUTION_HEIGHT, window.screen.height) - user.set(CustomUserProperties.SCREEN_RESOLUTION_WIDTH, window.screen.width) - user.set(CustomUserProperties.GIT_COMMIT_HASH, process.env.REACT_APP_GIT_COMMIT_HASH ?? 'unknown') - - // Service Worker analytics - const isServiceWorkerInstalled = Boolean(window.navigator.serviceWorker?.controller) - const isServiceWorkerHit = Boolean((window as any).__isDocumentCached) - const serviceWorkerProperty = isServiceWorkerInstalled ? (isServiceWorkerHit ? 'hit' : 'miss') : 'uninstalled' - - const pageLoadProperties = { service_worker: serviceWorkerProperty } - sendInitializationEvent(SharedEventName.APP_LOADED, pageLoadProperties) - const sendWebVital = - (metric: string) => - ({ delta }: Metric) => - sendAnalyticsEvent(SharedEventName.WEB_VITALS, { ...pageLoadProperties, [metric]: delta }) - getCLS(sendWebVital('cumulative_layout_shift')) - getFCP(sendWebVital('first_contentful_paint_ms')) - getFID(sendWebVital('first_input_delay_ms')) - getLCP(sendWebVital('largest_contentful_paint_ms')) - }, []) - - useEffect(() => { - user.set(CustomUserProperties.DARK_MODE, isDarkMode) - }, [isDarkMode]) - - useEffect(() => { - // If we're not in the transition period to UniswapX opt-out, set the router preference to whatever is specified. - if (!isUniswapXDefaultEnabled) { - user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) - return - } - - // In the transition period, override the stored API preference to UniswapX if the user hasn't opted out. - if (routerPreference === RouterPreference.API && !userOptedOutOfUniswapX) { - user.set(CustomUserProperties.ROUTER_PREFERENCE, RouterPreference.X) - return - } - - // Otherwise, the user has opted out or their preference is UniswapX/client, so set the preference to whatever is specified. - user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) - }, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX]) - useEffect(() => { const scrollListener = () => { setScrollY(window.scrollY) @@ -221,6 +172,7 @@ export default function App() { api: process.env.REACT_APP_STATSIG_PROXY_URL, }} > + {renderUkBannner && } @@ -255,3 +207,59 @@ export default function App() { ) } + +function UserPropertyUpdater() { + const isDarkMode = useIsDarkMode() + + const [routerPreference] = useRouterPreference() + const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() + const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() + + useEffect(() => { + // User properties *must* be set before sending corresponding event properties, + // so that the event contains the correct and up-to-date user properties. + user.set(CustomUserProperties.USER_AGENT, navigator.userAgent) + user.set(CustomUserProperties.BROWSER, getBrowser()) + user.set(CustomUserProperties.SCREEN_RESOLUTION_HEIGHT, window.screen.height) + user.set(CustomUserProperties.SCREEN_RESOLUTION_WIDTH, window.screen.width) + user.set(CustomUserProperties.GIT_COMMIT_HASH, process.env.REACT_APP_GIT_COMMIT_HASH ?? 'unknown') + + // Service Worker analytics + const isServiceWorkerInstalled = Boolean(window.navigator.serviceWorker?.controller) + const isServiceWorkerHit = Boolean((window as any).__isDocumentCached) + const serviceWorkerProperty = isServiceWorkerInstalled ? (isServiceWorkerHit ? 'hit' : 'miss') : 'uninstalled' + + const pageLoadProperties = { service_worker: serviceWorkerProperty } + sendInitializationEvent(SharedEventName.APP_LOADED, pageLoadProperties) + const sendWebVital = + (metric: string) => + ({ delta }: Metric) => + sendAnalyticsEvent(SharedEventName.WEB_VITALS, { ...pageLoadProperties, [metric]: delta }) + getCLS(sendWebVital('cumulative_layout_shift')) + getFCP(sendWebVital('first_contentful_paint_ms')) + getFID(sendWebVital('first_input_delay_ms')) + getLCP(sendWebVital('largest_contentful_paint_ms')) + }, []) + + useEffect(() => { + user.set(CustomUserProperties.DARK_MODE, isDarkMode) + }, [isDarkMode]) + + useEffect(() => { + // If we're not in the transition period to UniswapX opt-out, set the router preference to whatever is specified. + if (!isUniswapXDefaultEnabled) { + user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) + return + } + + // In the transition period, override the stored API preference to UniswapX if the user hasn't opted out. + if (routerPreference === RouterPreference.API && !userOptedOutOfUniswapX) { + user.set(CustomUserProperties.ROUTER_PREFERENCE, RouterPreference.X) + return + } + + // Otherwise, the user has opted out or their preference is UniswapX/client, so set the preference to whatever is specified. + user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) + }, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX]) + return null +} From 48855f487f3e504a06b20b773ddacf455ba43f75 Mon Sep 17 00:00:00 2001 From: cartcrom <39385577+cartcrom@users.noreply.github.com> Date: Wed, 11 Oct 2023 09:23:28 -0400 Subject: [PATCH 27/61] feat: animated review expando (#7440) * feat: animated review expando * test: update snapshots * fix: pr comments + spacing --- src/assets/svg/expando-icon-closed.svg | 3 + src/assets/svg/expando-icon-opened.svg | 3 + src/components/AnimatedDropdown/index.tsx | 7 +- .../TransactionConfirmationModal/index.tsx | 2 +- src/components/swap/SwapLineItem.tsx | 22 +- src/components/swap/SwapModalFooter.tsx | 98 +- src/components/swap/SwapModalHeader.tsx | 15 +- src/components/swap/SwapModalHeaderAmount.tsx | 2 +- .../SwapDetailsDropdown.test.tsx.snap | 190 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 4002 +++++++++-------- .../SwapModalFooter.test.tsx.snap | 636 ++- .../SwapModalHeader.test.tsx.snap | 84 +- 12 files changed, 2694 insertions(+), 2370 deletions(-) create mode 100644 src/assets/svg/expando-icon-closed.svg create mode 100644 src/assets/svg/expando-icon-opened.svg diff --git a/src/assets/svg/expando-icon-closed.svg b/src/assets/svg/expando-icon-closed.svg new file mode 100644 index 00000000000..63311a80e3e --- /dev/null +++ b/src/assets/svg/expando-icon-closed.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/expando-icon-opened.svg b/src/assets/svg/expando-icon-opened.svg new file mode 100644 index 00000000000..4ea0cc9be84 --- /dev/null +++ b/src/assets/svg/expando-icon-opened.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/AnimatedDropdown/index.tsx b/src/components/AnimatedDropdown/index.tsx index a2492556030..02bc23b8bd8 100644 --- a/src/components/AnimatedDropdown/index.tsx +++ b/src/components/AnimatedDropdown/index.tsx @@ -1,11 +1,13 @@ -import { animated, useSpring } from 'react-spring' +import { animated, useSpring, UseSpringProps } from 'react-spring' import useResizeObserver from 'use-resize-observer' +type AnimatedDropdownProps = React.PropsWithChildren<{ open: boolean; springProps?: UseSpringProps }> /** * @param open conditional to show content or hide + * @param springProps additional props to include in spring animation * @returns Wrapper to smoothly hide and expand content */ -export default function AnimatedDropdown({ open, children }: React.PropsWithChildren<{ open: boolean }>) { +export default function AnimatedDropdown({ open, springProps, children }: AnimatedDropdownProps) { const { ref, height } = useResizeObserver() const props = useSpring({ @@ -20,6 +22,7 @@ export default function AnimatedDropdown({ open, children }: React.PropsWithChil clamp: true, velocity: 0.01, }, + ...springProps, }) return ( {topContent()} - {bottomContent && {bottomContent()}} + {bottomContent && {bottomContent()}} ) } diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index a4ad6400a5d..a1a6637e632 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -9,6 +9,7 @@ import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import useHoverProps from 'hooks/useHoverProps' import { useIsMobile } from 'nft/hooks' import React, { PropsWithChildren, useEffect, useState } from 'react' +import { animated, SpringValue } from 'react-spring' import { InterfaceTrade, TradeFillType } from 'state/routing/types' import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils' import { useUserSlippageTolerance } from 'state/user/hooks' @@ -238,11 +239,12 @@ function ValueWrapper({ children, lineItem, labelHovered, syncing }: ValueWrappe ) } -interface SwapLineItemProps { +export interface SwapLineItemProps { trade: InterfaceTrade syncing: boolean allowedSlippage: Percent type: SwapLineItemType + animatedOpacity?: SpringValue } function SwapLineItem(props: SwapLineItemProps) { @@ -252,14 +254,16 @@ function SwapLineItem(props: SwapLineItemProps) { if (!LineItem) return null return ( - - - - - - - - + + + + + + + + + + ) } diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 86f5155c9df..e323e0d961f 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -2,28 +2,33 @@ import { Trans } from '@lingui/macro' import { BrowserEvent, InterfaceElementName, SwapEventName } from '@uniswap/analytics-events' import { Percent } from '@uniswap/sdk-core' import { TraceEvent } from 'analytics' +import AnimatedDropdown from 'components/AnimatedDropdown' import Column from 'components/Column' import SpinningLoader from 'components/Loader/SpinningLoader' import { SwapResult } from 'hooks/useSwapCallback' import useTransactionDeadline from 'hooks/useTransactionDeadline' -import { ReactNode } from 'react' +import ms from 'ms' +import { ReactNode, useState } from 'react' import { AlertTriangle } from 'react-feather' +import { easings, useSpring } from 'react-spring' import { InterfaceTrade, RouterPreference } from 'state/routing/types' import { isClassicTrade } from 'state/routing/utils' import { useRouterPreference, useUserSlippageTolerance } from 'state/user/hooks' import styled, { useTheme } from 'styled-components' -import { ThemedText } from 'theme/components' +import { Separator, ThemedText } from 'theme/components' import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries' import { formatSwapButtonClickEventProperties } from 'utils/loggingFormatters' +import { ReactComponent as ExpandoIconClosed } from '../../assets/svg/expando-icon-closed.svg' +import { ReactComponent as ExpandoIconOpened } from '../../assets/svg/expando-icon-opened.svg' import { ButtonError, SmallButtonPrimary } from '../Button' import Row, { AutoRow, RowBetween, RowFixed } from '../Row' import { SwapCallbackError, SwapShowAcceptChanges } from './styled' -import { SwapLineItemType } from './SwapLineItem' +import { SwapLineItemProps, SwapLineItemType } from './SwapLineItem' import SwapLineItem from './SwapLineItem' const DetailsContainer = styled(Column)` - padding: 0 8px; + padding-bottom: 8px; ` const StyledAlertTriangle = styled(AlertTriangle)` @@ -33,9 +38,45 @@ const StyledAlertTriangle = styled(AlertTriangle)` const ConfirmButton = styled(ButtonError)` height: 56px; - margin-top: 10px; ` +const DropdownControllerWrapper = styled.div` + display: flex; + align-items: center; + margin-right: -6px; + + padding: 0 16px; + min-width: fit-content; + white-space: nowrap; +` + +const DropdownButton = styled.button` + padding: 0; + margin-top: 16px; + height: 28px; + text-decoration: none; + display: flex; + background: none; + border: none; + align-items: center; + cursor: pointer; +` + +function DropdownController({ open, onClick }: { open: boolean; onClick: () => void }) { + return ( + + + + + {open ? Show less : Show more} + + {open ? : } + + + + ) +} + export default function SwapModalFooter({ trade, allowedSlippage, @@ -66,17 +107,16 @@ export default function SwapModalFooter({ const [routerPreference] = useRouterPreference() const routes = isClassicTrade(trade) ? getRoutingDiagramEntries(trade) : undefined const theme = useTheme() + const [showMore, setShowMore] = useState(false) const lineItemProps = { trade, allowedSlippage, syncing: false } return ( <> + setShowMore(!showMore)} /> - - - - + @@ -141,3 +181,43 @@ export default function SwapModalFooter({ ) } + +function AnimatedLineItem(props: SwapLineItemProps & { open: boolean; delay: number }) { + const { open, delay } = props + + const animatedProps = useSpring({ + animatedOpacity: open ? 1 : 0, + config: { duration: ms('300ms'), easing: easings.easeOutSine }, + delay, + }) + + return +} + +function ExpandableLineItems(props: { trade: InterfaceTrade; allowedSlippage: Percent; open: boolean }) { + const { open, trade, allowedSlippage } = props + + if (!trade) return null + + const lineItemProps = { trade, allowedSlippage, syncing: false, open } + + return ( + + + + + + + + + ) +} diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 2bd7c8e351a..084fedfa999 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -6,14 +6,10 @@ import { InterfaceTrade } from 'state/routing/types' import { isPreviewTrade } from 'state/routing/utils' import { Field } from 'state/swap/actions' import styled from 'styled-components' -import { Divider, ThemedText } from 'theme/components' +import { ThemedText } from 'theme/components' import { SwapModalHeaderAmount } from './SwapModalHeaderAmount' -const Rule = styled(Divider)` - margin: 16px 2px 24px 2px; -` - const HeaderContainer = styled(AutoColumn)` margin-top: 16px; ` @@ -50,7 +46,7 @@ export default function SwapModalHeader({ isLoading={isPreviewTrade(trade) && trade.tradeType === TradeType.EXACT_INPUT} tooltipText={ trade.tradeType === TradeType.EXACT_INPUT ? ( - + Output is estimated. You will receive at least{' '} @@ -58,9 +54,9 @@ export default function SwapModalHeader({ {' '} or the transaction will revert. - + ) : ( - + Input is estimated. You will sell at most{' '} @@ -68,12 +64,11 @@ export default function SwapModalHeader({ {' '} or the transaction will revert. - + ) } />
- ) } diff --git a/src/components/swap/SwapModalHeaderAmount.tsx b/src/components/swap/SwapModalHeaderAmount.tsx index 7c57c857b42..33513fb1858 100644 --- a/src/components/swap/SwapModalHeaderAmount.tsx +++ b/src/components/swap/SwapModalHeaderAmount.tsx @@ -61,7 +61,7 @@ export function SwapModalHeaderAmount({ - + diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index c838b3cdeed..7422fdaeca8 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -327,108 +327,114 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
-
-
- Price impact -
+
-
-
- + Price impact +
+
+
+
- -105566.373% - + + -105566.373% + +
-
-
- Max. slippage -
+
-
-
+
+ Max. slippage +
+
+
- 2% + class="c2 c21" + > +
+ 2% +
-
-
- Network cost -
+
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $1.00 + $1.00 +
@@ -437,26 +443,28 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
-
-
- Order routing -
+
-
-
+
+ Order routing +
+
+
- Uniswap Client +
+ Uniswap Client +
diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index c1f7d1105b8..8c4cb6343ec 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -179,88 +179,90 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` } -
+
- Network cost -
-
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $0.00 + $0.00 +
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -473,81 +475,83 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` } -
-
- Max. slippage -
+
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Receive at least +
+ Receive at least +
+
+ 0.0000000000000009 DEF +
- 0.0000000000000009 DEF + class="c13" + /> +
+ If the price moves so that you will receive less than 0.0000000000000009 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more +
-
-
- If the price moves so that you will receive less than 0.0000000000000009 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
+
-
.c0 { @@ -690,43 +694,45 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` } -
-
- Receive at least -
+
-
-
- 0.0000000000000009 DEF +
+ Receive at least +
+
+
+
+ 0.0000000000000009 DEF +
-
-
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -925,104 +931,106 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` } } -
+
- Order routing -
-
-
-
+
+ Order routing +
+
+
- - - - - - - - -
+ + + + + + + + +
- Uniswap X +
+ Uniswap X +
-
-
- UniswapX +
+ UniswapX +
+ aggregates liquidity sources for better prices and gas free swaps. + + Learn more +
- aggregates liquidity sources for better prices and gas free swaps. - - Learn more -
+
-
@@ -1203,88 +1211,90 @@ exports[`SwapLineItem.tsx exact input 1`] = ` color: #7D7D7D; } -
+
- Network cost -
-
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $1.00 + $1.00 +
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -1423,47 +1433,49 @@ exports[`SwapLineItem.tsx exact input 1`] = ` color: #7D7D7D; } -
-
- Price impact -
+
-
-
- + Price impact +
+
+
+
- -105566.373% - + + -105566.373% + +
-
-
- The impact your trade has on the market price of this pool. +
+ The impact your trade has on the market price of this pool. +
+
-
.c0 { @@ -1672,81 +1684,83 @@ exports[`SwapLineItem.tsx exact input 1`] = ` content: 'Auto'; } -
+
- Max. slippage -
-
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Receive at least +
+ Receive at least +
+
+ 0.00000000000000098 DEF +
- 0.00000000000000098 DEF + class="c13" + /> +
+ If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more +
-
-
- If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
+
-
.c0 { @@ -1885,43 +1899,45 @@ exports[`SwapLineItem.tsx exact input 1`] = ` color: #7D7D7D; } -
+
- Receive at least -
-
-
-
- 0.00000000000000098 DEF +
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
-
-
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -2280,202 +2296,204 @@ exports[`SwapLineItem.tsx exact input 1`] = ` color: #7D7D7D; } -
-
- Order routing -
+
-
-
+
+ Order routing +
+
+
- Uniswap Client +
+ Uniswap Client +
-
-
-
- ABC logo -
-
-
- - dot_line.svg - + ABC logo +
-
- V3 -
+ dot_line.svg +
- 100% +
+
+ V3 +
+
+
+ 100% +
-
-
-
-
+
+
- DEF logo +
+ DEF logo +
-
-
- ABC logo +
+ ABC logo +
-
-
- 1% +
+ 1% +
-
-
- ABC/DEF 1% pool +
+ ABC/DEF 1% pool +
+
-
-
-
- DEF logo +
+ DEF logo +
-
-
- Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+ Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+
-
@@ -2656,88 +2674,90 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` color: #7D7D7D; } -
-
- Network cost -
+
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $1.00 + $1.00 +
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -2876,47 +2896,49 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` color: #7D7D7D; } -
+
- Price impact -
-
-
-
- + Price impact +
+
+
+
- -105566.373% - + + -105566.373% + +
-
-
- The impact your trade has on the market price of this pool. +
+ The impact your trade has on the market price of this pool. +
+
-
.c0 { @@ -3125,81 +3147,83 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` content: 'Auto'; } -
-
- Max. slippage -
+
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Receive at least +
+ Receive at least +
+
+ 0.00000000000000098 DEF +
- 0.00000000000000098 DEF + class="c13" + /> +
+ If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more +
-
-
- If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
+
-
.c0 { @@ -3338,43 +3362,45 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` color: #7D7D7D; } -
-
- Receive at least -
+
-
-
- 0.00000000000000098 DEF +
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
-
-
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -3733,202 +3759,204 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` color: #7D7D7D; } -
-
- Order routing -
+
-
-
+
+ Order routing +
+
+
- Uniswap API +
+ Uniswap API +
-
-
-
- ABC logo -
-
-
- - dot_line.svg - + ABC logo +
-
- V3 -
+ dot_line.svg +
- 100% -
-
-
-
-
+
+ V3 +
+
+
+ 100% +
+
+
+
+
- DEF logo +
+ DEF logo +
-
-
- ABC logo +
+ ABC logo +
-
-
- 1% +
+ 1% +
-
-
- ABC/DEF 1% pool +
+ ABC/DEF 1% pool +
+
-
-
-
- DEF logo +
+ DEF logo +
-
-
- Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+ Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+
-
@@ -4109,88 +4137,90 @@ exports[`SwapLineItem.tsx exact output 1`] = ` color: #7D7D7D; } -
+
- Network cost -
-
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - - + - +
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -4329,47 +4359,49 @@ exports[`SwapLineItem.tsx exact output 1`] = ` color: #7D7D7D; } -
-
- Price impact -
+
-
-
- + Price impact +
+
+
+
- -105566.373% - + + -105566.373% + +
-
-
- The impact your trade has on the market price of this pool. +
+ The impact your trade has on the market price of this pool. +
+
-
.c0 { @@ -4578,81 +4610,83 @@ exports[`SwapLineItem.tsx exact output 1`] = ` content: 'Auto'; } -
+
- Max. slippage -
-
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Pay at most +
+ Pay at most +
+
+ 0.00000000000000102 ABC +
- 0.00000000000000102 ABC + class="c13" + /> +
+ If the price moves so that you will pay more than 0.00000000000000102 ABC, your transaction will be reverted. This is the maximum amount you are guaranteed to pay. + + Learn more +
-
-
- If the price moves so that you will pay more than 0.00000000000000102 ABC, your transaction will be reverted. This is the maximum amount you are guaranteed to pay. - - Learn more - -
+
-
.c0 { @@ -4791,43 +4825,45 @@ exports[`SwapLineItem.tsx exact output 1`] = ` color: #7D7D7D; } -
+
- Pay at most -
-
-
-
- 0.00000000000000102 ABC +
+ Pay at most +
+
+
+
+ 0.00000000000000102 ABC +
-
-
- The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert. +
+ The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -5186,202 +5222,204 @@ exports[`SwapLineItem.tsx exact output 1`] = ` color: #7D7D7D; } -
-
- Order routing -
+
-
-
+
+ Order routing +
+
+
- Uniswap Client +
+ Uniswap Client +
-
-
-
- ABC logo -
-
-
- - dot_line.svg - + ABC logo +
-
- V3 -
+ dot_line.svg +
- 100% +
+
+ V3 +
+
+
+ 100% +
-
-
-
-
+
+
- GHI logo +
+ GHI logo +
-
-
- ABC logo +
+ ABC logo +
-
-
- 0.3% +
+ 0.3% +
-
-
- ABC/GHI 0.3% pool +
+ ABC/GHI 0.3% pool +
+
-
-
-
- GHI logo +
+ GHI logo +
-
-
- This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+ This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+
-
@@ -5562,88 +5600,90 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } -
-
- Network cost -
+
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $1.00 + $1.00 +
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -5801,55 +5841,57 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } -
+
- DEF fee -
-
-
-
- + DEF fee +
+
+
+
- 3% - + + 3% + +
-
-
- Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees. - - Learn more - + Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees. + + Learn more + +
+
-
.c0 { @@ -5988,47 +6030,49 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } -
+
- Price impact -
-
-
-
- + Price impact +
+
+
+
- -105566.373% - + + -105566.373% + +
-
-
- The impact your trade has on the market price of this pool. +
+ The impact your trade has on the market price of this pool. +
+
-
.c0 { @@ -6237,81 +6281,83 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` content: 'Auto'; } -
-
- Max. slippage -
+
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Receive at least +
+ Receive at least +
+
+ 0.000000000000000952 DEF +
- 0.000000000000000952 DEF + class="c13" + /> +
+ If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more +
-
-
- If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
+
-
.c0 { @@ -6450,43 +6496,45 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } -
-
- Receive at least -
+
-
-
- 0.000000000000000952 DEF +
+ Receive at least +
+
+
+
+ 0.000000000000000952 DEF +
-
-
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -6845,202 +6893,204 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } -
-
- Order routing -
+
-
-
+
+ Order routing +
+
+
- Uniswap API +
+ Uniswap API +
-
-
-
- ABC logo -
-
-
- - dot_line.svg - + ABC logo +
-
- V3 -
+ dot_line.svg +
- 100% +
+
+ V3 +
+
+
+ 100% +
-
-
-
-
+
+
- DEF logo +
+ DEF logo +
-
-
- ABC logo +
+ ABC logo +
-
-
- 1% +
+ 1% +
-
-
- ABC/DEF 1% pool +
+ ABC/DEF 1% pool +
+
-
-
-
- DEF logo +
+ DEF logo +
-
-
- Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+ Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+
-
@@ -7221,88 +7271,90 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -
-
- Network cost -
+
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $1.00 + $1.00 +
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -7460,55 +7512,57 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -
-
- ABC fee -
+
-
-
- + ABC fee +
+
+
+
- 3% - + + 3% + +
-
-
- Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees. - - Learn more - + Some tokens take a fee when they are bought or sold, which is set by the token issuer. Uniswap does not receive any of these fees. + + Learn more + +
+
-
.c0 { @@ -7647,47 +7701,49 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -
-
- Price impact -
+
-
-
- + Price impact +
+
+
+
- -105566.373% - + + -105566.373% + +
-
-
- The impact your trade has on the market price of this pool. +
+ The impact your trade has on the market price of this pool. +
+
-
.c0 { @@ -7896,81 +7952,83 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` content: 'Auto'; } -
+
- Max. slippage -
-
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Receive at least +
+ Receive at least +
+
+ 0.000000000000000952 DEF +
- 0.000000000000000952 DEF + class="c13" + /> +
+ If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more +
-
-
- If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
+
-
.c0 { @@ -8109,43 +8167,45 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -
-
- Receive at least -
+
-
-
- 0.000000000000000952 DEF +
+ Receive at least +
+
+
+
+ 0.000000000000000952 DEF +
-
-
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -8504,202 +8564,204 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -
+
- Order routing -
-
-
-
+
+ Order routing +
+
+
- Uniswap API +
+ Uniswap API +
-
-
- ABC logo -
-
-
-
- - dot_line.svg - + ABC logo +
-
- V3 -
+ dot_line.svg +
- 100% +
+
+ V3 +
+
+
+ 100% +
-
-
-
-
+
+
- DEF logo +
+ DEF logo +
-
-
- ABC logo +
+ ABC logo +
-
-
- 1% +
+ 1% +
-
-
- ABC/DEF 1% pool +
+ ABC/DEF 1% pool +
+
-
-
-
- DEF logo +
+ DEF logo +
-
-
- Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+ Best price route costs ~$1.00 in gas. This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each step. +
+
-
@@ -8875,60 +8937,62 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` color: #7D7D7D; } -
+
- Network cost -
-
-
-
+
+ Network cost +
+
+
+ class="c3 c6 css-142zc9n" + > +
+
-
-
- The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. - - Learn more - + The fee paid to the Ethereum network to process your transaction. This must be paid in ETH. + + Learn more + +
+
-
.c0 { @@ -9080,48 +9144,50 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` color: #7D7D7D; } -
+
- Price impact -
-
-
-
+
+ Price impact +
+
+
+ class="c3 c6 css-142zc9n" + > +
+
-
-
- The impact your trade has on the market price of this pool. +
+ The impact your trade has on the market price of this pool. +
+
-
.c0 { @@ -9330,81 +9396,83 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` content: 'Auto'; } -
-
- Max. slippage -
+
-
-
+
+ Max. slippage +
+
+
- 2% + class="c0 c7" + > +
+ 2% +
-
-
- Receive at least +
+ Receive at least +
+
+ 0.00000000000000098 DEF +
- 0.00000000000000098 DEF + class="c13" + /> +
+ If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more +
-
-
- If the price moves so that you will receive less than 0.00000000000000098 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
+
-
.c0 { @@ -9543,43 +9611,45 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` color: #7D7D7D; } -
-
- Receive at least -
+
-
-
- 0.00000000000000098 DEF +
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
-
-
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
-
.c0 { @@ -9639,24 +9709,26 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` color: #7D7D7D; } -
-
- Order routing -
+
+ class="c3 c4 css-142zc9n" + data-testid="swap-li-label" + > + Order routing +
+
+
+
@@ -9716,21 +9788,23 @@ exports[`SwapLineItem.tsx syncing 1`] = ` color: #7D7D7D; } -
+
- Network cost +
+ Network cost +
+
-
.c0 { box-sizing: border-box; @@ -9784,21 +9858,23 @@ exports[`SwapLineItem.tsx syncing 1`] = ` color: #7D7D7D; } -
+
- Price impact +
+ Price impact +
+
-
.c0 { box-sizing: border-box; @@ -9852,21 +9928,23 @@ exports[`SwapLineItem.tsx syncing 1`] = ` color: #7D7D7D; } -
+
- Max. slippage +
+ Max. slippage +
+
-
.c0 { box-sizing: border-box; @@ -9920,21 +9998,23 @@ exports[`SwapLineItem.tsx syncing 1`] = ` color: #7D7D7D; } -
+
- Receive at least +
+ Receive at least +
+
-
.c0 { box-sizing: border-box; @@ -9988,21 +10068,23 @@ exports[`SwapLineItem.tsx syncing 1`] = ` color: #7D7D7D; } -
+
- Order routing +
+ Order routing +
+
-
`; diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index 093c9e8efb1..e319920c388 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -2,6 +2,74 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = ` + .c3 { + color: #7D7D7D; +} + +.c1 { + width: 100%; + height: 1px; + background-color: #22222212; +} + +.c2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-right: -6px; + padding: 0 16px; + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + white-space: nowrap; +} + +.c0 { + padding: 0; + margin-top: 16px; + height: 28px; + -webkit-text-decoration: none; + text-decoration: none; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + background: none; + border: none; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + cursor: pointer; +} + + .c2 { box-sizing: border-box; margin: 0; @@ -68,14 +136,14 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = justify-content: space-between; } -.c5 { - color: #222222; -} - .c11 { color: #7D7D7D; } +.c5 { + color: #222222; +} + .c0 { display: -webkit-box; display: -webkit-flex; @@ -151,166 +219,192 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = } .c1 { - padding: 0 8px; + padding-bottom: 8px; }
-
+
- Rate -
-
-
-
-
+
+
+
-
- 1 DEF = 1.00 ABC -
- - +
+ 1 DEF = 1.00 ABC +
+ + +
-
- Price impact -
-
-
+
+
- - -105566.373% - +
+ Price impact +
+
+
+
+ + -105566.373% + +
+
+
+
-
-
-
-
-
- Max. slippage -
-
-
- 2% + class="c5 c6 css-142zc9n" + data-testid="swap-li-label" + > + Max. slippage +
+
+
+
+
+
+ 2% +
+
+
+
-
-
-
-
-
- Receive at least -
-
-
- 0.00000000000000098 DEF +
+
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
+
+
+
-
+
- Network cost -
-
-
-
+
+ Network cost +
+
+
- - - Ethereum logo - - - - ethereum.svg + + Ethereum logo + + + + + ethereum.svg + - - $1.00 + $1.00 +
@@ -467,7 +561,6 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = .c6 { height: 56px; - margin-top: 10px; }
+ .c3 { + color: #7D7D7D; +} + +.c1 { + width: 100%; + height: 1px; + background-color: #22222212; +} + +.c2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-right: -6px; + padding: 0 16px; + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + white-space: nowrap; +} + +.c0 { + padding: 0; + margin-top: 16px; + height: 28px; + -webkit-text-decoration: none; + text-decoration: none; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + background: none; + border: none; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + cursor: pointer; +} + +@media (max-width:960px) { + +} + + .c2 { box-sizing: border-box; margin: 0; @@ -538,14 +703,14 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission justify-content: space-between; } -.c5 { - color: #222222; -} - .c13 { color: #7D7D7D; } +.c5 { + color: #222222; +} + .c0 { display: -webkit-box; display: -webkit-flex; @@ -639,7 +804,7 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission } .c1 { - padding: 0 8px; + padding-bottom: 8px; } @media (max-width:960px) { @@ -649,128 +814,154 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
-
+
- Rate -
-
-
- - +
+ 1 DEF = 1.00 ABC +
+ + +
-
- Price impact -
-
-
+
+
+ class="c2 c3 c4" + > +
+ Price impact +
+
+
+
+
+
+
+
+
-
-
-
-
-
- Max. slippage -
-
-
- 2% + class="c5 c9 css-142zc9n" + data-testid="swap-li-label" + > + Max. slippage +
+
+
+
+
+
+ 2% +
+
+
+
-
-
-
-
-
- Receive at least -
-
-
- 0.00000000000000098 DEF +
+
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
+
+
+
-
+
- Network cost -
-
-
-
+
+ Network cost +
+
+
+ class="c5 c7 css-142zc9n" + > +
+
@@ -977,7 +1168,6 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission .c6 { height: 56px; - margin-top: 10px; } @media (max-width:960px) { diff --git a/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap index bccad92baac..b0cf7d1fbe7 100644 --- a/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap @@ -34,14 +34,6 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = color: #222222; } -.c13 { - width: 100%; - height: 1px; - border-width: 0; - margin: 0; - background-color: #22222212; -} - .c2 { display: -webkit-box; display: -webkit-flex; @@ -111,13 +103,14 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = } .c9 { - cursor: help; color: #7D7D7D; margin-right: 8px; } -.c14 { - margin: 16px 2px 24px 2px; +.c13 { + cursor: help; + color: #7D7D7D; + margin-right: 8px; } .c1 { @@ -145,7 +138,6 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] =
You pay
@@ -192,7 +184,7 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = >
You receive @@ -227,9 +219,6 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] =
-
`; @@ -268,14 +257,6 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s color: #222222; } -.c13 { - width: 100%; - height: 1px; - border-width: 0; - margin: 0; - background-color: #22222212; -} - .c2 { display: -webkit-box; display: -webkit-flex; @@ -345,13 +326,14 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s } .c9 { - cursor: help; color: #7D7D7D; margin-right: 8px; } -.c14 { - margin: 16px 2px 24px 2px; +.c13 { + cursor: help; + color: #7D7D7D; + margin-right: 8px; } .c1 { @@ -379,7 +361,6 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s
You pay
@@ -426,7 +407,7 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s >
You receive @@ -461,9 +442,6 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s
-
`; @@ -502,14 +480,6 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = ` color: #222222; } -.c13 { - width: 100%; - height: 1px; - border-width: 0; - margin: 0; - background-color: #22222212; -} - .c2 { display: -webkit-box; display: -webkit-flex; @@ -579,13 +549,14 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = ` } .c9 { - cursor: help; color: #7D7D7D; margin-right: 8px; } -.c14 { - margin: 16px 2px 24px 2px; +.c13 { + cursor: help; + color: #7D7D7D; + margin-right: 8px; } .c1 { @@ -613,7 +584,6 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = `
You pay
@@ -660,7 +630,7 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = ` >
You receive @@ -695,9 +665,6 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = `
-
`; @@ -736,14 +703,6 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = ` color: #222222; } -.c13 { - width: 100%; - height: 1px; - border-width: 0; - margin: 0; - background-color: #22222212; -} - .c2 { display: -webkit-box; display: -webkit-flex; @@ -813,13 +772,14 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = ` } .c9 { - cursor: help; color: #7D7D7D; margin-right: 8px; } -.c14 { - margin: 16px 2px 24px 2px; +.c13 { + cursor: help; + color: #7D7D7D; + margin-right: 8px; } .c1 { @@ -847,7 +807,6 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = `
You pay
@@ -894,7 +853,7 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = ` >
You receive @@ -929,9 +888,6 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = `
-
`; From 9f44e48cf1323738b6bf641db1ba525b8cc16abc Mon Sep 17 00:00:00 2001 From: Thomas Thachil Date: Wed, 11 Oct 2023 12:17:45 -0400 Subject: [PATCH 28/61] chore(): update deeplink package names (#7399) --- public/.well-known/assetlinks.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/.well-known/assetlinks.json b/public/.well-known/assetlinks.json index ff3eaba193f..dfcfe7ccd5e 100644 --- a/public/.well-known/assetlinks.json +++ b/public/.well-known/assetlinks.json @@ -3,27 +3,27 @@ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", - "package_name": "com.uniswap", + "package_name": "com.uniswap.mobile", "sha256_cert_fingerprints": - ["97:A5:81:51:DA:AF:8F:6E:65:3A:90:1E:82:12:6C:FB:61:2D:36:C7:CF:20:61:6B:A3:4C:52:CA:BC:58:43:8E", "F9:E9:E3:F0:04:28:66:62:81:44:50:7E:D6:A9:5F:B9:65:39:02:70:1D:13:74:15:D3:E1:A3:1B:D4:38:3A:1F"] + ["49:D9:3D:5D:FB:AA:64:A4:64:80:85:0F:39:A8:C1:D9:25:D3:D4:BC:8E:6B:1F:45:0C:EA:AF:B1:0C:27:DF:B8", "F9:E9:E3:F0:04:28:66:62:81:44:50:7E:D6:A9:5F:B9:65:39:02:70:1D:13:74:15:D3:E1:A3:1B:D4:38:3A:1F"] } }, { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", - "package_name": "com.uniswap.beta", + "package_name": "com.uniswap.mobile.beta", "sha256_cert_fingerprints": - ["E5:39:87:DC:4D:FD:4C:1B:A6:74:36:7D:3A:3B:6B:ED:9E:B3:66:89:92:8A:1B:B8:FC:1B:22:56:56:B4:46:A3", "54:4B:62:33:17:9B:5F:A8:E6:5D:D3:A6:E5:9D:80:5F:A5:02:7F:E2:14:B8:C1:7A:AC:4B:8D:E0:65:49:87:41"] + ["75:41:9C:2D:01:4A:88:4E:8D:C6:EF:E5:51:54:28:6B:99:05:31:43:AD:84:B4:EB:39:28:B8:C3:C4:CE:48:E3", "54:4B:62:33:17:9B:5F:A8:E6:5D:D3:A6:E5:9D:80:5F:A5:02:7F:E2:14:B8:C1:7A:AC:4B:8D:E0:65:49:87:41"] } }, { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", - "package_name": "com.uniswap.dev", + "package_name": "com.uniswap.mobile.dev", "sha256_cert_fingerprints": - ["5A:6D:23:50:2F:1E:0D:01:DC:96:65:F3:3A:18:4C:4C:8C:67:E0:09:99:9B:B1:9B:BF:44:99:D0:D1:D0:FC:5E", "02:E6:1C:76:8C:75:C3:78:C8:8C:FE:7B:2E:8F:4B:E1:FA:47:F2:F6:1A:DB:57:69:4A:41:99:C6:71:2C:AB:E3", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"] + ["45:F8:15:02:C5:4F:AD:82:E7:51:F0:9C:D1:CA:77:C8:C9:BF:06:A6:D9:5A:55:4F:9E:B8:5F:81:33:2B:D0:DB", "02:E6:1C:76:8C:75:C3:78:C8:8C:FE:7B:2E:8F:4B:E1:FA:47:F2:F6:1A:DB:57:69:4A:41:99:C6:71:2C:AB:E3", "FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"] } } ] \ No newline at end of file From b75438bc8b4037a0ddd93101e7930da6d01bc356 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Wed, 11 Oct 2023 12:53:01 -0400 Subject: [PATCH 29/61] fix: fix sitemaps routes test (#7441) * fix: fix sitemaps routes test * avoid checking all top-tokens paths --- src/pages/routes.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/routes.test.ts b/src/pages/routes.test.ts index 9920a9fef1a..5022f261fd2 100644 --- a/src/pages/routes.test.ts +++ b/src/pages/routes.test.ts @@ -9,12 +9,12 @@ describe('Routes', () => { const contents = fs.readFileSync('./public/sitemap.xml', 'utf8') const sitemap = await parseStringPromise(contents) - const sitemapPaths = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname) + const sitemapPaths: string[] = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname) - pathNames - .filter((p) => !p.includes(':') && !p.includes('*') && !p.includes('not-found')) + sitemapPaths + .filter((p) => !p.includes('/0x')) .forEach((path: string) => { - expect(sitemapPaths).toContain(path) + expect(pathNames).toContain(path) }) }) From d56030a92084ea080eb383fdb83fd322ab2ea479 Mon Sep 17 00:00:00 2001 From: gnewfield <18626088+gnewfield@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:53:10 -0400 Subject: [PATCH 30/61] feat: add progress_indicator_v2 flag (#7445) * feat: add progress_indicator_v2 flag * rename flag hook --- src/components/FeatureFlagModal/FeatureFlagModal.tsx | 7 +++++++ src/featureFlags/flags/progressIndicatorV2.ts | 5 +++++ src/featureFlags/index.tsx | 1 + 3 files changed, 13 insertions(+) create mode 100644 src/featureFlags/flags/progressIndicatorV2.ts diff --git a/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/src/components/FeatureFlagModal/FeatureFlagModal.tsx index caeb77d2c97..f141a3a839c 100644 --- a/src/components/FeatureFlagModal/FeatureFlagModal.tsx +++ b/src/components/FeatureFlagModal/FeatureFlagModal.tsx @@ -11,6 +11,7 @@ import { useInfoLiveViewsFlag } from 'featureFlags/flags/infoLiveViews' import { useInfoPoolPageFlag } from 'featureFlags/flags/infoPoolPage' import { useInfoTDPFlag } from 'featureFlags/flags/infoTDP' import { useMultichainUXFlag } from 'featureFlags/flags/multichainUx' +import { useProgressIndicatorV2Flag } from 'featureFlags/flags/progressIndicatorV2' import { useQuickRouteMainnetFlag } from 'featureFlags/flags/quickRouteMainnet' import { TraceJsonRpcVariant, useTraceJsonRpcFlag } from 'featureFlags/flags/traceJsonRpc' import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefault' @@ -290,6 +291,12 @@ export default function FeatureFlagModal() { featureFlag={FeatureFlag.fotAdjustedmentsEnabled} label="Enable fee-on-transfer UI and slippage adjustments" /> + Date: Wed, 11 Oct 2023 10:21:43 -0700 Subject: [PATCH 31/61] fix: hide slippage warning when trade is x (#7439) --- src/components/Settings/MenuButton/index.tsx | 10 +++++++--- src/components/Settings/index.tsx | 7 ++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/Settings/MenuButton/index.tsx b/src/components/Settings/MenuButton/index.tsx index 25f82e7992c..4a7a4c17261 100644 --- a/src/components/Settings/MenuButton/index.tsx +++ b/src/components/Settings/MenuButton/index.tsx @@ -1,6 +1,8 @@ import { t, Trans } from '@lingui/macro' import { Settings } from 'components/Icons/Settings' import Row from 'components/Row' +import { InterfaceTrade } from 'state/routing/types' +import { isUniswapXTrade } from 'state/routing/utils' import { useUserSlippageTolerance } from 'state/user/hooks' import { SlippageTolerance } from 'state/user/types' import styled from 'styled-components' @@ -45,11 +47,11 @@ const IconContainerWithSlippage = styled(IconContainer)<{ displayWarning?: boole displayWarning ? theme.deprecated_accentWarningSoft : theme.surface2}; ` -const ButtonContent = () => { +const ButtonContent = ({ trade }: { trade?: InterfaceTrade }) => { const [userSlippageTolerance] = useUserSlippageTolerance() const { formatSlippage } = useFormatter() - if (userSlippageTolerance === SlippageTolerance.Auto) { + if (userSlippageTolerance === SlippageTolerance.Auto || isUniswapXTrade(trade)) { return ( @@ -73,10 +75,12 @@ export default function MenuButton({ disabled, onClick, isActive, + trade, }: { disabled: boolean onClick: () => void isActive: boolean + trade?: InterfaceTrade }) { return ( ) } diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx index 94c3c785d30..95d2da9b5ee 100644 --- a/src/components/Settings/index.tsx +++ b/src/components/Settings/index.tsx @@ -152,7 +152,12 @@ export default function SettingsTab({ return ( - + {isOpenDesktop && {Settings}} {isOpenMobile && ( From 82a194987a89732f585a4e7e5c841ddb2b15ad34 Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Wed, 11 Oct 2023 10:32:33 -0700 Subject: [PATCH 32/61] feat: [info] Add Token Description Component (#7400) * working token details section * update decription styling * different chain explorers * remove wrap check for color extraction * move token description to its own component, add copy, make simple project query * rename styled components and add tests * remove old comment * await test fragment * fix: update description truncation from TokenDescription (#7413) * fix: update description truncation from TokenDescription * fix: use better name * fix: test if description is hidden or not (#7422) --------- Co-authored-by: Charles Bachmeier * make darker or lighter * showCopy default false * update test * remove unused styles --------- Co-authored-by: eddie <66155195+just-toby@users.noreply.github.com> --- src/components/Icons/Etherscan.tsx | 22 + src/components/Icons/Globe.tsx | 18 + src/components/Icons/TwitterX.tsx | 18 + src/components/Tokens/TokenDetails/About.tsx | 33 +- .../TokenDetails/TokenDescription.test.tsx | 85 ++ .../Tokens/TokenDetails/TokenDescription.tsx | 170 +++ .../TokenDescription.test.tsx.snap | 1287 +++++++++++++++++ src/components/Tokens/TokenDetails/shared.ts | 34 + src/graphql/data/Token.ts | 26 + src/hooks/useColor.ts | 34 +- .../PoolDetails/PoolDetailsStats.test.tsx | 20 +- src/pages/PoolDetails/PoolDetailsStats.tsx | 4 +- .../PoolDetailsStats.test.tsx.snap | 4 +- .../__snapshots__/index.test.tsx.snap | 303 ++++ src/pages/PoolDetails/index.tsx | 34 + src/test-utils/pools/fixtures.ts | 47 +- 16 files changed, 2090 insertions(+), 49 deletions(-) create mode 100644 src/components/Icons/Etherscan.tsx create mode 100644 src/components/Icons/Globe.tsx create mode 100644 src/components/Icons/TwitterX.tsx create mode 100644 src/components/Tokens/TokenDetails/TokenDescription.test.tsx create mode 100644 src/components/Tokens/TokenDetails/TokenDescription.tsx create mode 100644 src/components/Tokens/TokenDetails/__snapshots__/TokenDescription.test.tsx.snap create mode 100644 src/components/Tokens/TokenDetails/shared.ts diff --git a/src/components/Icons/Etherscan.tsx b/src/components/Icons/Etherscan.tsx new file mode 100644 index 00000000000..f6d7136a2ef --- /dev/null +++ b/src/components/Icons/Etherscan.tsx @@ -0,0 +1,22 @@ +import { ComponentProps } from 'react' + +export const EtherscanLogo = (props: ComponentProps<'svg'>) => ( + + + + +) diff --git a/src/components/Icons/Globe.tsx b/src/components/Icons/Globe.tsx new file mode 100644 index 00000000000..b17778aa558 --- /dev/null +++ b/src/components/Icons/Globe.tsx @@ -0,0 +1,18 @@ +import { ComponentProps } from 'react' + +export const Globe = (props: ComponentProps<'svg'>) => ( + + + +) diff --git a/src/components/Icons/TwitterX.tsx b/src/components/Icons/TwitterX.tsx new file mode 100644 index 00000000000..c87b09f9256 --- /dev/null +++ b/src/components/Icons/TwitterX.tsx @@ -0,0 +1,18 @@ +import { ComponentProps } from 'react' + +export const TwitterXLogo = (props: ComponentProps<'svg'>) => ( + + + +) diff --git a/src/components/Tokens/TokenDetails/About.tsx b/src/components/Tokens/TokenDetails/About.tsx index 270afd4b092..9cfdd0430fa 100644 --- a/src/components/Tokens/TokenDetails/About.tsx +++ b/src/components/Tokens/TokenDetails/About.tsx @@ -1,19 +1,14 @@ import { Trans } from '@lingui/macro' import { ChainId } from '@uniswap/sdk-core' import { getChainInfo } from 'constants/chainInfo' -import { darken } from 'polished' import { useState } from 'react' import styled from 'styled-components' import { ThemedText } from 'theme/components' import { textFadeIn } from 'theme/styles' import Resource from './Resource' +import { NoInfoAvailable, TRUNCATE_CHARACTER_COUNT, truncateDescription, TruncateDescriptionButton } from './shared' -const NoInfoAvailable = styled.span` - color: ${({ theme }) => theme.neutral3}; - font-weight: 485; - font-size: 16px; -` const TokenDescriptionContainer = styled.div` overflow: hidden; text-overflow: ellipsis; @@ -24,32 +19,6 @@ const TokenDescriptionContainer = styled.div` white-space: pre-wrap; ` -const TruncateDescriptionButton = styled.div` - color: ${({ theme }) => theme.neutral2}; - font-weight: 485; - font-size: 0.85em; - padding-top: 0.5em; - - &:hover, - &:focus { - color: ${({ theme }) => darken(0.1, theme.neutral2)}; - cursor: pointer; - } -` - -const truncateDescription = (desc: string) => { - //trim the string to the maximum length - let tokenDescriptionTruncated = desc.slice(0, TRUNCATE_CHARACTER_COUNT) - //re-trim if we are in the middle of a word - tokenDescriptionTruncated = `${tokenDescriptionTruncated.slice( - 0, - Math.min(tokenDescriptionTruncated.length, tokenDescriptionTruncated.lastIndexOf(' ')) - )}...` - return tokenDescriptionTruncated -} - -const TRUNCATE_CHARACTER_COUNT = 400 - export const AboutContainer = styled.div` gap: 16px; padding: 24px 0px; diff --git a/src/components/Tokens/TokenDetails/TokenDescription.test.tsx b/src/components/Tokens/TokenDetails/TokenDescription.test.tsx new file mode 100644 index 00000000000..c858e533d76 --- /dev/null +++ b/src/components/Tokens/TokenDetails/TokenDescription.test.tsx @@ -0,0 +1,85 @@ +import { QueryResult } from '@apollo/client' +import userEvent from '@testing-library/user-event' +import { USDC_MAINNET } from 'constants/tokens' +import { Chain, Exact, TokenProjectQuery, useTokenProjectQuery } from 'graphql/data/__generated__/types-and-hooks' +import { useCurrency } from 'hooks/Tokens' +import { mocked } from 'test-utils/mocked' +import { validTokenProjectResponse, validUSDCCurrency } from 'test-utils/pools/fixtures' +import { act, render, screen } from 'test-utils/render' + +import { TokenDescription } from './TokenDescription' + +jest.mock('graphql/data/__generated__/types-and-hooks', () => { + const originalModule = jest.requireActual('graphql/data/__generated__/types-and-hooks') + return { + ...originalModule, + useTokenProjectQuery: jest.fn(), + } +}) + +jest.mock('hooks/Tokens', () => { + const originalModule = jest.requireActual('hooks/Tokens') + return { + ...originalModule, + useCurrency: jest.fn(), + } +}) + +const tokenAddress = USDC_MAINNET.address + +describe('TokenDescription', () => { + beforeEach(() => { + mocked(useTokenProjectQuery).mockReturnValue(validTokenProjectResponse) + mocked(useCurrency).mockReturnValue(validUSDCCurrency) + }) + + it('renders token information correctly with defaults', () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.getByText('USDC')).toBeVisible() + expect(screen.getByText('USDCoin')).toBeVisible() + expect(screen.getByText('Website')).toBeVisible() + expect(screen.getByText('Twitter')).toBeVisible() + expect(screen.getByText('Etherscan')).toBeVisible() + expect(screen.getByText('0xA0b8...eB48')).toBeVisible() + }) + + it('truncates description and shows more', async () => { + const { asFragment } = render() + + expect(asFragment()).toMatchSnapshot() + const truncatedDescription = screen.getByTestId('token-description-truncated') + const fullDescription = screen.getByTestId('token-description-full') + + expect(truncatedDescription).toHaveStyleRule('display', 'inline') + expect(fullDescription).toHaveStyleRule('display', 'none') + + await act(() => userEvent.click(screen.getByText('Show more'))) + expect(truncatedDescription).toHaveStyleRule('display', 'none') + expect(fullDescription).toHaveStyleRule('display', 'inline') + expect(screen.getByText('Hide')).toBeVisible() + }) + + it('copy address button hidden when flagged', async () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.queryByText('0xA0b8...eB48')).toBeNull() + }) + + it('no description or social buttons shown when not available', async () => { + mocked(useTokenProjectQuery).mockReturnValue({ data: undefined } as unknown as QueryResult< + TokenProjectQuery, + Exact<{ chain: Chain; address?: string }> + >) + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.getByText('No token information available')).toBeVisible() + expect(screen.queryByText('Website')).toBeNull() + expect(screen.queryByText('Twitter')).toBeNull() + expect(screen.getByText('Etherscan')).toBeVisible() + expect(screen.getByText('0xA0b8...eB48')).toBeVisible() + }) +}) diff --git a/src/components/Tokens/TokenDetails/TokenDescription.tsx b/src/components/Tokens/TokenDetails/TokenDescription.tsx new file mode 100644 index 00000000000..362ccf8830a --- /dev/null +++ b/src/components/Tokens/TokenDetails/TokenDescription.tsx @@ -0,0 +1,170 @@ +import { Trans } from '@lingui/macro' +import { ChainId } from '@uniswap/sdk-core' +import Column from 'components/Column' +import { EtherscanLogo } from 'components/Icons/Etherscan' +import { Globe } from 'components/Icons/Globe' +import { TwitterXLogo } from 'components/Icons/TwitterX' +import CurrencyLogo from 'components/Logo/CurrencyLogo' +import Row from 'components/Row' +import { NoInfoAvailable, truncateDescription, TruncateDescriptionButton } from 'components/Tokens/TokenDetails/shared' +import { useTokenProjectQuery } from 'graphql/data/__generated__/types-and-hooks' +import { chainIdToBackendName } from 'graphql/data/util' +import { useCurrency } from 'hooks/Tokens' +import { useColor } from 'hooks/useColor' +import useCopyClipboard from 'hooks/useCopyClipboard' +import { useCallback, useReducer } from 'react' +import { Copy } from 'react-feather' +import styled, { useTheme } from 'styled-components' +import { BREAKPOINTS } from 'theme' +import { ClickableStyle, EllipsisStyle, ExternalLink, ThemedText } from 'theme/components' +import { opacify } from 'theme/utils' +import { shortenAddress } from 'utils' +import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' + +const TokenInfoSection = styled(Column)` + gap: 12px; + width: 100%; + + @media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.sm}px) { + max-width: 45%; + } +` + +const TokenNameRow = styled(Row)` + gap: 8px; + width: 100%; +` + +const TokenName = styled(ThemedText.BodyPrimary)` + ${EllipsisStyle} +` + +const TokenButtonRow = styled(TokenNameRow)` + flex-wrap: wrap; +` + +const TokenInfoButton = styled(Row)<{ tokenColor: string }>` + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: ${({ tokenColor }) => tokenColor}; + background-color: ${({ tokenColor }) => opacify(12, tokenColor)}; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: max-content; + ${ClickableStyle} +` + +const TokenDescriptionContainer = styled(ThemedText.BodyPrimary)` + ${EllipsisStyle} + max-width: 100%; + // max-height: fit-content; + line-height: 24px; + white-space: pre-wrap; +` + +const DescriptionVisibilityWrapper = styled.span<{ $visible: boolean }>` + display: ${({ $visible }) => ($visible ? 'inline' : 'none')}; +` + +const TRUNCATE_CHARACTER_COUNT = 75 + +export function TokenDescription({ + tokenAddress, + chainId = ChainId.MAINNET, + showCopy = false, +}: { + tokenAddress: string + chainId?: number + showCopy?: boolean +}) { + const currency = useCurrency(tokenAddress, chainId) + const theme = useTheme() + const color = useColor(currency?.wrapped, theme.surface1, theme.darkMode) + const { data: tokenQuery } = useTokenProjectQuery({ + variables: { + address: tokenAddress, + chain: chainIdToBackendName(chainId), + }, + errorPolicy: 'all', + }) + const tokenProject = tokenQuery?.token?.project + const description = tokenProject?.description + const explorerUrl = getExplorerLink(chainId, tokenAddress, ExplorerDataType.TOKEN) + + const [, setCopied] = useCopyClipboard() + const copy = useCallback(() => { + setCopied(tokenAddress) + }, [tokenAddress, setCopied]) + + const [isDescriptionTruncated, toggleIsDescriptionTruncated] = useReducer((x) => !x, true) + const truncatedDescription = truncateDescription(description ?? '', TRUNCATE_CHARACTER_COUNT) + const shouldTruncate = !!description && description.length > TRUNCATE_CHARACTER_COUNT + const showTruncatedDescription = shouldTruncate && isDescriptionTruncated + + return ( + + + + {currency?.name} + {currency?.symbol} + + + {showCopy && ( + + + {shortenAddress(tokenAddress)} + + )} + + + + {chainId === ChainId.MAINNET ? Etherscan : Explorer} + + + {!!tokenProject?.homepageUrl && ( + + + + Website + + + )} + {!!tokenProject?.twitterName && ( + + + + Twitter + + + )} + + + {!description && ( + + No token information available + + )} + {description && ( + <> + + {description} + + + {truncatedDescription} + + + )} + {shouldTruncate && ( + + {isDescriptionTruncated ? Show more : Hide} + + )} + + + ) +} diff --git a/src/components/Tokens/TokenDetails/__snapshots__/TokenDescription.test.tsx.snap b/src/components/Tokens/TokenDetails/__snapshots__/TokenDescription.test.tsx.snap new file mode 100644 index 00000000000..23fa7293085 --- /dev/null +++ b/src/components/Tokens/TokenDetails/__snapshots__/TokenDescription.test.tsx.snap @@ -0,0 +1,1287 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TokenDescription copy address button hidden when flagged 1`] = ` + + .c2 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c3 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c7 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c11 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c11:hover { + opacity: 0.6; +} + +.c11:active { + opacity: 0.4; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c6 { + --size: 20px; + border-radius: 100px; + color: #222222; + background-color: #22222212; + font-size: calc(var(--size) / 3); + font-weight: 535; + height: 20px; + line-height: 20px; + text-align: center; + width: 20px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.c5 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c16 { + color: #7D7D7D; + font-weight: 485; + font-size: 0.85em; + padding-top: 0.5em; +} + +.c16:hover, +.c16:focus { + color: #636363; + cursor: pointer; +} + +.c1 { + gap: 12px; + width: 100%; +} + +.c4 { + gap: 8px; + width: 100%; +} + +.c8 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c10 { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.c12 { + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: #FC72FF; + background-color: #FC72FF1f; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; +} + +.c12:hover { + opacity: 0.6; +} + +.c12:active { + opacity: 0.4; +} + +.c13 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + line-height: 24px; + white-space: pre-wrap; +} + +.c14 { + display: none; +} + +.c15 { + display: inline; +} + +@media (max-width:1023px) and (min-width:640px) { + .c1 { + max-width: 45%; + } +} + +
+
+
+
+ USD +
+
+
+ USDCoin +
+
+ USDC +
+
+ +
+ + USDC is a fully collateralized US dollar stablecoin. USDC is the bridge between dollars and trading on cryptocurrency exchanges. The technology behind CENTRE makes it possible to exchange value between people, businesses and financial institutions just like email between mail services and texts between SMS providers. We believe by removing artificial economic borders, we can create a more inclusive global economy. + + + USDC is a fully collateralized US dollar stablecoin. USDC is the bridge... + +
+ Show more +
+
+
+
+`; + +exports[`TokenDescription no description or social buttons shown when not available 1`] = ` + + .c2 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c3 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c7 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c12 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c12:hover { + opacity: 0.6; +} + +.c12:active { + opacity: 0.4; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c6 { + --size: 20px; + border-radius: 100px; + color: #222222; + background-color: #22222212; + font-size: calc(var(--size) / 3); + font-weight: 535; + height: 20px; + line-height: 20px; + text-align: center; + width: 20px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.c5 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c14 { + color: #CECECE; + font-weight: 485; + font-size: 16px; +} + +.c1 { + gap: 12px; + width: 100%; +} + +.c4 { + gap: 8px; + width: 100%; +} + +.c8 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c10 { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.c11 { + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: #FC72FF; + background-color: #FC72FF1f; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; +} + +.c11:hover { + opacity: 0.6; +} + +.c11:active { + opacity: 0.4; +} + +.c13 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + line-height: 24px; + white-space: pre-wrap; +} + +@media (max-width:1023px) and (min-width:640px) { + .c1 { + max-width: 45%; + } +} + +
+
+
+
+ USD +
+
+
+ USDCoin +
+
+ USDC +
+
+
+
+ + + + + 0xA0b8...eB48 +
+ +
+ + + + + Etherscan +
+
+
+
+ + No token information available + +
+
+
+`; + +exports[`TokenDescription renders token information correctly with defaults 1`] = ` + + .c2 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c3 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c7 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c12 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c12:hover { + opacity: 0.6; +} + +.c12:active { + opacity: 0.4; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c6 { + --size: 20px; + border-radius: 100px; + color: #222222; + background-color: #22222212; + font-size: calc(var(--size) / 3); + font-weight: 535; + height: 20px; + line-height: 20px; + text-align: center; + width: 20px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.c5 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c16 { + color: #7D7D7D; + font-weight: 485; + font-size: 0.85em; + padding-top: 0.5em; +} + +.c16:hover, +.c16:focus { + color: #636363; + cursor: pointer; +} + +.c1 { + gap: 12px; + width: 100%; +} + +.c4 { + gap: 8px; + width: 100%; +} + +.c8 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c10 { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.c11 { + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: #FC72FF; + background-color: #FC72FF1f; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; +} + +.c11:hover { + opacity: 0.6; +} + +.c11:active { + opacity: 0.4; +} + +.c13 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + line-height: 24px; + white-space: pre-wrap; +} + +.c14 { + display: none; +} + +.c15 { + display: inline; +} + +@media (max-width:1023px) and (min-width:640px) { + .c1 { + max-width: 45%; + } +} + +
+
+
+
+ USD +
+
+
+ USDCoin +
+
+ USDC +
+
+ +
+ + USDC is a fully collateralized US dollar stablecoin. USDC is the bridge between dollars and trading on cryptocurrency exchanges. The technology behind CENTRE makes it possible to exchange value between people, businesses and financial institutions just like email between mail services and texts between SMS providers. We believe by removing artificial economic borders, we can create a more inclusive global economy. + + + USDC is a fully collateralized US dollar stablecoin. USDC is the bridge... + +
+ Show more +
+
+
+
+`; + +exports[`TokenDescription truncates description and shows more 1`] = ` + + .c2 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c3 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c7 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c12 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c12:hover { + opacity: 0.6; +} + +.c12:active { + opacity: 0.4; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c6 { + --size: 20px; + border-radius: 100px; + color: #222222; + background-color: #22222212; + font-size: calc(var(--size) / 3); + font-weight: 535; + height: 20px; + line-height: 20px; + text-align: center; + width: 20px; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; +} + +.c5 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c16 { + color: #7D7D7D; + font-weight: 485; + font-size: 0.85em; + padding-top: 0.5em; +} + +.c16:hover, +.c16:focus { + color: #636363; + cursor: pointer; +} + +.c1 { + gap: 12px; + width: 100%; +} + +.c4 { + gap: 8px; + width: 100%; +} + +.c8 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c10 { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.c11 { + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: #FC72FF; + background-color: #FC72FF1f; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; +} + +.c11:hover { + opacity: 0.6; +} + +.c11:active { + opacity: 0.4; +} + +.c13 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + line-height: 24px; + white-space: pre-wrap; +} + +.c14 { + display: none; +} + +.c15 { + display: inline; +} + +@media (max-width:1023px) and (min-width:640px) { + .c1 { + max-width: 45%; + } +} + +
+
+
+
+ USD +
+
+
+ USDCoin +
+
+ USDC +
+
+ +
+ + USDC is a fully collateralized US dollar stablecoin. USDC is the bridge between dollars and trading on cryptocurrency exchanges. The technology behind CENTRE makes it possible to exchange value between people, businesses and financial institutions just like email between mail services and texts between SMS providers. We believe by removing artificial economic borders, we can create a more inclusive global economy. + + + USDC is a fully collateralized US dollar stablecoin. USDC is the bridge... + +
+ Show more +
+
+
+
+`; diff --git a/src/components/Tokens/TokenDetails/shared.ts b/src/components/Tokens/TokenDetails/shared.ts new file mode 100644 index 00000000000..b06b7aae7dc --- /dev/null +++ b/src/components/Tokens/TokenDetails/shared.ts @@ -0,0 +1,34 @@ +import { darken } from 'polished' +import styled from 'styled-components' + +export const NoInfoAvailable = styled.span` + color: ${({ theme }) => theme.neutral3}; + font-weight: 485; + font-size: 16px; +` + +export const TruncateDescriptionButton = styled.div` + color: ${({ theme }) => theme.neutral2}; + font-weight: 485; + font-size: 0.85em; + padding-top: 0.5em; + + &:hover, + &:focus { + color: ${({ theme }) => darken(0.1, theme.neutral2)}; + cursor: pointer; + } +` + +export const truncateDescription = (desc: string, maxCharacterCount = TRUNCATE_CHARACTER_COUNT) => { + //trim the string to the maximum length + let tokenDescriptionTruncated = desc.slice(0, maxCharacterCount) + //re-trim if we are in the middle of a word + tokenDescriptionTruncated = `${tokenDescriptionTruncated.slice( + 0, + Math.min(tokenDescriptionTruncated.length, tokenDescriptionTruncated.lastIndexOf(' ')) + )}...` + return tokenDescriptionTruncated +} + +export const TRUNCATE_CHARACTER_COUNT = 400 diff --git a/src/graphql/data/Token.ts b/src/graphql/data/Token.ts index d5d000d4900..3cc5789f8b2 100644 --- a/src/graphql/data/Token.ts +++ b/src/graphql/data/Token.ts @@ -63,6 +63,32 @@ gql` } ` +gql` + query TokenProject($chain: Chain!, $address: String = null) { + token(chain: $chain, address: $address) { + id + decimals + name + chain + address + symbol + standard + project { + id + description + homepageUrl + twitterName + logoUrl + tokens { + id + chain + address + } + } + } + } +` + export type { Chain, TokenQuery } from './__generated__/types-and-hooks' export type TokenQueryData = TokenQuery['token'] diff --git a/src/hooks/useColor.ts b/src/hooks/useColor.ts index 14ecaa9d738..4fb20ef1e5f 100644 --- a/src/hooks/useColor.ts +++ b/src/hooks/useColor.ts @@ -1,11 +1,26 @@ import { ChainId, Token } from '@uniswap/sdk-core' import { DEFAULT_COLOR } from 'constants/tokenColors' import useTokenLogoSource from 'hooks/useAssetLogoSource' -import { rgb } from 'polished' +import { darken, lighten, rgb } from 'polished' import { useEffect, useState } from 'react' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { useTheme } from 'styled-components' import { getColor } from 'utils/getColor' +import { hex } from 'wcag-contrast' + +// The WCAG AA standard color contrast threshold +const MIN_COLOR_CONTRAST_THRESHOLD = 3 + +/** + * Compares a given color against the background color to determine if it passes the minimum contrast threshold. + * @param color The hex value of the extracted color + * @param backgroundColor The hex value of the background color to check contrast against + * @returns either 'sporeWhite' or 'sporeBlack' + */ +function passesContrast(color: string, backgroundColor: string): boolean { + const contrast = hex(color, backgroundColor) + return contrast >= MIN_COLOR_CONTRAST_THRESHOLD +} function URIForEthToken(address: string) { return `https://raw.githubusercontent.com/uniswap/assets/master/blockchains/ethereum/assets/${address}/logo.png` @@ -20,10 +35,6 @@ function URIForEthToken(address: string) { * @returns {Promise< | null>} A promise that resolves to a color string or null if color cannot be determined. */ async function getColorFromToken(token: Token, primarySrc?: string): Promise { - if (!(token instanceof WrappedTokenInfo)) { - return null - } - const wrappedToken = token as WrappedTokenInfo let color: string | null = null @@ -33,7 +44,7 @@ async function getColorFromToken(token: Token, primarySrc?: string): Promise { if (!stale && tokenColor !== null) { + if (backgroundColor) { + let increment = 0.1 + while (!passesContrast(tokenColor, backgroundColor)) { + tokenColor = makeLighter ? lighten(increment, tokenColor) : darken(increment, tokenColor) + increment += 0.1 + } + } setColor(tokenColor) } }) @@ -74,7 +92,7 @@ export function useColor(token?: Token) { stale = true setColor(theme.accent1) } - }, [src, theme.accent1, token]) + }, [backgroundColor, makeLighter, src, theme.accent1, token]) return color } diff --git a/src/pages/PoolDetails/PoolDetailsStats.test.tsx b/src/pages/PoolDetails/PoolDetailsStats.test.tsx index e8b5e798501..dd374433f4c 100644 --- a/src/pages/PoolDetails/PoolDetailsStats.test.tsx +++ b/src/pages/PoolDetails/PoolDetailsStats.test.tsx @@ -1,5 +1,6 @@ +import { enableNetConnect } from 'nock' import { validPoolDataResponse } from 'test-utils/pools/fixtures' -import { render, screen } from 'test-utils/render' +import { act, render, screen } from 'test-utils/render' import { BREAKPOINTS } from 'theme' import { PoolDetailsStats } from './PoolDetailsStats' @@ -11,8 +12,18 @@ describe('PoolDetailsStats', () => { chainId: 1, } - it('renders stats text correctly', () => { + beforeEach(() => { + // Enable network connections for retrieving token logos + enableNetConnect() + }) + + it('renders stats text correctly', async () => { const { asFragment } = render() + // After the first render, the extracted color is updated to an a11y compliant color + // This is why we need to wrap the fragment in act(...) + await act(async () => { + await asFragment + }) expect(asFragment()).toMatchSnapshot() expect(screen.getByText(/Stats/i)).toBeInTheDocument() @@ -23,13 +34,16 @@ describe('PoolDetailsStats', () => { expect(screen.getByTestId('pool-balance-chart')).toBeInTheDocument() }) - it('pool balance chart not visible on mobile', () => { + it('pool balance chart not visible on mobile', async () => { Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: BREAKPOINTS.md, }) const { asFragment } = render() + await act(async () => { + await asFragment + }) expect(asFragment()).toMatchSnapshot() expect(screen.queryByTestId('pool-balance-chart')).toBeNull() diff --git a/src/pages/PoolDetails/PoolDetailsStats.tsx b/src/pages/PoolDetails/PoolDetailsStats.tsx index 9d98d4cf0f5..a72ebf15a27 100644 --- a/src/pages/PoolDetails/PoolDetailsStats.tsx +++ b/src/pages/PoolDetails/PoolDetailsStats.tsx @@ -105,8 +105,8 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS const currency0 = useCurrency(poolData?.token0?.id, chainId) ?? undefined const currency1 = useCurrency(poolData?.token1?.id, chainId) ?? undefined - const color0 = useColor(currency0?.wrapped) - let color1 = useColor(currency1?.wrapped) + const color0 = useColor(currency0?.wrapped, theme.surface2, theme.darkMode) + let color1 = useColor(currency1?.wrapped, theme.surface2, theme.darkMode) if (color0 === color1 && color0 === theme.accent1) { color1 = colors.blue400 } diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap index 08cbabaab88..03b49fca7ba 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap @@ -506,7 +506,7 @@ exports[`PoolDetailsStats renders stats text correctly 1`] = ` .c9 { height: 8px; width: 40.698463777008904%; - background: #FC72FF; + background: #0066d9; border-top-left-radius: 5px; border-bottom-left-radius: 5px; border-right: 1px solid #F9F9F9; @@ -515,7 +515,7 @@ exports[`PoolDetailsStats renders stats text correctly 1`] = ` .c10 { height: 8px; width: 59.3015362229911%; - background: #4C82FB; + background: #FC72FF; border-top-right-radius: 5px; border-bottom-right-radius: 5px; border-left: 1px solid #F9F9F9; diff --git a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap index 42eb6bc4df6..1aeec655d20 100644 --- a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap @@ -110,6 +110,25 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the color: #222222; } +.c43 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c43:hover { + opacity: 0.6; +} + +.c43:active { + opacity: 0.4; +} + .c3 { display: -webkit-box; display: -webkit-flex; @@ -124,6 +143,97 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the justify-content: flex-start; } +.c40 { + opacity: 0; + -webkit-transition: opacity 250ms ease-in; + transition: opacity 250ms ease-in; + width: 20px; + height: 20px; + border-radius: 50%; +} + +.c39 { + width: 20px; + height: 20px; + background: #22222212; + -webkit-transition: background-color 250ms ease-in; + transition: background-color 250ms ease-in; + box-shadow: 0 0 1px white; + border-radius: 50%; +} + +.c38 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c46 { + color: #CECECE; + font-weight: 485; + font-size: 16px; +} + +.c36 { + gap: 12px; + width: 100%; +} + +.c37 { + gap: 8px; + width: 100%; +} + +.c41 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c42 { + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; +} + +.c44 { + gap: 8px; + padding: 8px 12px; + border-radius: 20px; + color: #FC72FF; + background-color: #FC72FF1f; + font-size: 14px; + font-weight: 535; + line-height: 16px; + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; +} + +.c44:hover { + opacity: 0.6; +} + +.c44:active { + opacity: 0.4; +} + +.c45 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + line-height: 24px; + white-space: pre-wrap; +} + .c21 { background-color: transparent; bottom: 0; @@ -381,6 +491,24 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the min-width: 360px; } +.c34 { + gap: 24px; + padding: 20px; +} + +.c35 { + width: 100%; + font-size: 24px; + font-weight: 485; + line-height: 32px; +} + +@media (max-width:1023px) and (min-width:640px) { + .c36 { + max-width: 45%; + } +} + @media (max-width:1023px) { .c23 { width: 100%; @@ -474,6 +602,24 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } } +@media (max-width:1023px) and (min-width:640px) { + .c34 { + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: unset; + } +} + +@media (max-width:639px) { + .c34 { + padding: unset; + } +} +
@@ -741,6 +887,163 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
+
+
+ Info +
+
+
+
+
+ UNKNOWN logo +
+
+
+ Unknown Token +
+
+ UNKNOWN +
+
+ +
+ + No token information available + +
+
+
+
+
+
+ WETH logo +
+
+
+ Wrapped Ether +
+
+ WETH +
+
+ +
+ + No token information available + +
+
+
diff --git a/src/pages/PoolDetails/index.tsx b/src/pages/PoolDetails/index.tsx index 427a63e1a25..971e42be178 100644 --- a/src/pages/PoolDetails/index.tsx +++ b/src/pages/PoolDetails/index.tsx @@ -1,10 +1,13 @@ +import { Trans } from '@lingui/macro' import Column from 'components/Column' import Row from 'components/Row' +import { TokenDescription } from 'components/Tokens/TokenDetails/TokenDescription' import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util' import { usePoolData } from 'graphql/thegraph/PoolData' import NotFound from 'pages/NotFound' import { useReducer } from 'react' import { useParams } from 'react-router-dom' +import { Text } from 'rebass' import styled from 'styled-components' import { BREAKPOINTS } from 'theme' import { isAddress } from 'utils' @@ -40,6 +43,28 @@ const RightColumn = styled(Column)` } ` +const TokenDetailsWrapper = styled(Column)` + gap: 24px; + padding: 20px; + + @media (max-width: ${BREAKPOINTS.lg - 1}px) and (min-width: ${BREAKPOINTS.sm}px) { + flex-direction: row; + flex-wrap: wrap; + padding: unset; + } + + @media (max-width: ${BREAKPOINTS.sm - 1}px) { + padding: unset; + } +` + +const TokenDetailsHeader = styled(Text)` + width: 100%; + font-size: 24px; + font-weight: 485; + line-height: 32px; +` + export default function PoolDetailsPage() { const { poolAddress, chainName } = useParams<{ poolAddress: string @@ -70,6 +95,15 @@ export default function PoolDetailsPage() { {poolData && } + {(token0 || token1) && ( + + + Info + + {token0 && } + {token1 && } + + )} ) diff --git a/src/test-utils/pools/fixtures.ts b/src/test-utils/pools/fixtures.ts index d070cd15b42..de6cef27277 100644 --- a/src/test-utils/pools/fixtures.ts +++ b/src/test-utils/pools/fixtures.ts @@ -1,8 +1,11 @@ +import { QueryResult } from '@apollo/client' import { BigNumber } from '@ethersproject/bignumber' -import { ChainId, WETH9 } from '@uniswap/sdk-core' +import { ChainId, Currency, WETH9 } from '@uniswap/sdk-core' import { FeeAmount, Pool, Position } from '@uniswap/v3-sdk' import { USDC_MAINNET } from 'constants/tokens' +import { Chain, Exact, TokenProjectQuery } from 'graphql/data/__generated__/types-and-hooks' import { Token } from 'graphql/thegraph/__generated__/types-and-hooks' +import { PoolData } from 'graphql/thegraph/PoolData' export const validParams = { poolAddress: '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640', chainName: 'ethereum' } @@ -15,6 +18,21 @@ export const validPoolToken0 = { __typename: 'Token', } as Token +export const validUSDCCurrency = { + isNative: false, + isToken: true, + name: 'USDCoin', + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + symbol: 'USDC', + decimals: 6, + chainId: 1, + logoURI: + 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png', + _checksummedAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + _tags: null, + wrapped: validPoolToken0, +} as unknown as Currency + export const validPoolToken1 = { id: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', symbol: 'WETH', @@ -97,7 +115,32 @@ export const validPoolDataResponse = { tvlUSDChange: -0.3657085465786977, tvlToken0: 90930713.7356909, tvlToken1: 82526.48678530742, - }, + } as PoolData, loading: false, error: false, } + +export const validTokenProjectResponse = { + data: { + token: { + id: 'VG9rZW46RVRIRVJFVU1fMHhBMGI4Njk5MWM2MjE4YjM2YzFkMTlENGEyZTlFYjBjRTM2MDZlQjQ4', + decimals: 6, + name: 'USD Coin', + chain: 'ETHEREUM', + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + symbol: 'USDC', + standard: 'ERC20', + project: { + id: 'VG9rZW5Qcm9qZWN0OkVUSEVSRVVNXzB4YTBiODY5OTFjNjIxOGIzNmMxZDE5ZDRhMmU5ZWIwY2UzNjA2ZWI0OF9VU0RD', + description: + 'USDC is a fully collateralized US dollar stablecoin. USDC is the bridge between dollars and trading on cryptocurrency exchanges. The technology behind CENTRE makes it possible to exchange value between people, businesses and financial institutions just like email between mail services and texts between SMS providers. We believe by removing artificial economic borders, we can create a more inclusive global economy.', + homepageUrl: 'https://www.circle.com/en/usdc', + twitterName: 'circle', + logoUrl: + 'https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png', + __typename: 'TokenProject', + }, + __typename: 'Token', + }, + }, +} as unknown as QueryResult> From aee4df10a87c4d2bba2a21161ee595b47303ce6b Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:15:51 -0700 Subject: [PATCH 33/61] fix: change default tx deadline to 10m (#7451) * fix: change deadline to 10m * test: add unit tests * fix: improve unit tests --- src/constants/misc.ts | 4 +- src/state/migrations.test.ts | 2 +- src/state/migrations.ts | 6 ++- src/state/migrations/1.test.ts | 85 ++++++++++++++++++++++++++++++++++ src/state/migrations/1.ts | 28 +++++++++++ src/state/reducer.ts | 2 +- 6 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 src/state/migrations/1.test.ts create mode 100644 src/state/migrations/1.ts diff --git a/src/constants/misc.ts b/src/constants/misc.ts index 06d2003db7c..14b4a25763d 100644 --- a/src/constants/misc.ts +++ b/src/constants/misc.ts @@ -5,8 +5,8 @@ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' // TODO(WEB-1984): Convert the deadline to minutes and remove unecessary conversions from // seconds to minutes in the codebase. -// 30 minutes, denominated in seconds -export const DEFAULT_DEADLINE_FROM_NOW = 60 * 30 +// 10 minutes, denominated in seconds +export const DEFAULT_DEADLINE_FROM_NOW = 60 * 10 export const L2_DEADLINE_FROM_NOW = 60 * 5 // transaction popup dismisal amounts diff --git a/src/state/migrations.test.ts b/src/state/migrations.test.ts index a23723e183c..7c99f600061 100644 --- a/src/state/migrations.test.ts +++ b/src/state/migrations.test.ts @@ -13,7 +13,7 @@ const defaultState = { user: {}, _persist: { rehydrated: true, - version: 0, + version: 1, }, application: { chainId: null, diff --git a/src/state/migrations.ts b/src/state/migrations.ts index f7d78e46a15..143c0e6f193 100644 --- a/src/state/migrations.ts +++ b/src/state/migrations.ts @@ -2,19 +2,21 @@ import { createMigrate, MigrationManifest, PersistedState, PersistMigrate } from import { MigrationConfig } from 'redux-persist/es/createMigrate' import { migration0 } from './migrations/0' +import { migration1 } from './migrations/1' import { legacyLocalStorageMigration } from './migrations/legacy' /** * These run once per state re-hydration when a version mismatch is detected. * Keep them as lightweight as possible. * - * Migration functions should not assume that any value exists in localStorage previously, - * because a user may be visiting the site for the first time or have cleared their localStorage. + * Migration functions should not assume that any value exists in the persisted data previously, + * because a user may be visiting the site for the first time or have cleared their data. */ // The target version number is the key export const migrations: MigrationManifest = { 0: migration0, + 1: migration1, } // We use a custom migration function for the initial state, because redux-persist diff --git a/src/state/migrations/1.test.ts b/src/state/migrations/1.test.ts new file mode 100644 index 00000000000..30c77054f46 --- /dev/null +++ b/src/state/migrations/1.test.ts @@ -0,0 +1,85 @@ +import { createMigrate } from 'redux-persist' +import { RouterPreference } from 'state/routing/types' +import { SlippageTolerance } from 'state/user/types' + +import { migration1, PersistAppStateV1 } from './1' + +const previousState: PersistAppStateV1 = { + user: { + userLocale: null, + userRouterPreference: RouterPreference.API, + userHideClosedPositions: false, + userSlippageTolerance: SlippageTolerance.Auto, + userSlippageToleranceHasBeenMigratedToAuto: true, + userDeadline: 1800, + tokens: {}, + pairs: {}, + timestamp: Date.now(), + hideBaseWalletBanner: false, + }, + _persist: { + version: 0, + rehydrated: true, + }, +} + +describe('migration to v1', () => { + it('should migrate the default deadline', async () => { + const migrator = createMigrate( + { + 1: migration1, + }, + { debug: false } + ) + const result: any = await migrator(previousState, 1) + expect(result?.user?.userDeadline).toEqual(600) + expect(result?._persist.version).toEqual(1) + + expect(result?.user?.userLocale).toEqual(null) + expect(result?.user?.userRouterPreference).toEqual(RouterPreference.API) + expect(result?.user?.userHideClosedPositions).toEqual(false) + expect(result?.user?.userSlippageTolerance).toEqual(SlippageTolerance.Auto) + expect(result?.user?.userSlippageToleranceHasBeenMigratedToAuto).toEqual(true) + expect(result?.user?.tokens).toEqual({}) + expect(result?.user?.pairs).toEqual({}) + expect(result?.user?.timestamp).toEqual(previousState.user?.timestamp) + expect(result?.user?.hideBaseWalletBanner).toEqual(false) + }) + + it('should not migrate a non-default value', async () => { + const migrator = createMigrate( + { + 1: migration1, + }, + { debug: false } + ) + const result: any = await migrator( + { + ...previousState, + user: { + ...previousState.user, + userDeadline: 300, + }, + } as PersistAppStateV1, + 1 + ) + expect(result?.user?.userDeadline).toEqual(300) + }) + + it('should not migrate if user state is not set', async () => { + const migrator = createMigrate( + { + 1: migration1, + }, + { debug: false } + ) + const result: any = await migrator( + { + ...previousState, + user: undefined, + } as PersistAppStateV1, + 1 + ) + expect(result?.user?.userDeadline).toBeUndefined() + }) +}) diff --git a/src/state/migrations/1.ts b/src/state/migrations/1.ts new file mode 100644 index 00000000000..a0a8f493abe --- /dev/null +++ b/src/state/migrations/1.ts @@ -0,0 +1,28 @@ +import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc' +import { PersistState } from 'redux-persist' +import { UserState } from 'state/user/reducer' + +export type PersistAppStateV1 = { + _persist: PersistState +} & { user?: UserState } + +/** + * Migration to change the default user deadline from 30 minutes to 10 minutes. + * We only migrate if the saved deadline is the old default. + */ +export const migration1 = (state: PersistAppStateV1 | undefined) => { + if (state?.user && state.user?.userDeadline === 1800) { + return { + ...state, + user: { + ...state.user, + userDeadline: DEFAULT_DEADLINE_FROM_NOW, + }, + _persist: { + ...state._persist, + version: 1, + }, + } + } + return state +} diff --git a/src/state/reducer.ts b/src/state/reducer.ts index 963ad4f74f8..d884eb11cde 100644 --- a/src/state/reducer.ts +++ b/src/state/reducer.ts @@ -44,7 +44,7 @@ export type AppState = ReturnType const persistConfig: PersistConfig = { key: 'interface', - version: 0, // see migrations.ts for more details about this version + version: 1, // see migrations.ts for more details about this version storage: localForage.createInstance({ name: 'redux', }), From 40b1e40721d066406510982ab3830df75143e971 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:16:15 -0700 Subject: [PATCH 34/61] fix: remove bridge usdc arbitrum (#7446) --- .../AccountDrawer/MiniPortfolio/PortfolioLogo.test.tsx | 4 ++-- .../__snapshots__/PortfolioLogo.test.tsx.snap | 2 +- src/components/FiatOnrampModal/utils.ts | 2 -- src/constants/tokens.ts | 9 +-------- src/hooks/useStablecoinPrice.ts | 4 ++-- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.test.tsx b/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.test.tsx index edc9bfc0a95..b9c3bd8a85c 100644 --- a/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.test.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/PortfolioLogo.test.tsx @@ -1,5 +1,5 @@ import { ChainId } from '@uniswap/sdk-core' -import { BRIDGED_USDC_ARBITRUM, DAI, DAI_ARBITRUM_ONE, USDC_MAINNET } from 'constants/tokens' +import { DAI, DAI_ARBITRUM_ONE, USDC_ARBITRUM, USDC_MAINNET } from 'constants/tokens' import { render } from 'test-utils/render' import { PortfolioLogo } from './PortfolioLogo' @@ -12,7 +12,7 @@ describe('PortfolioLogo', () => { it('renders with L2 icon', () => { const { container } = render( - + ) expect(container).toMatchSnapshot() }) diff --git a/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap b/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap index 4f273f7e347..1673da678fd 100644 --- a/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap +++ b/src/components/AccountDrawer/MiniPortfolio/__snapshots__/PortfolioLogo.test.tsx.snap @@ -78,7 +78,7 @@ exports[`PortfolioLogo renders with L2 icon 1`] = ` />
} = { [ChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC_MAINNET, 100_000e6), - [ChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(BRIDGED_USDC_ARBITRUM, 10_000e6), + [ChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6), [ChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18), [ChainId.POLYGON]: CurrencyAmount.fromRawAmount(USDC_POLYGON, 10_000e6), [ChainId.CELO]: CurrencyAmount.fromRawAmount(CUSD_CELO, 10_000e18), From 5fee3c6fdd4332a236b151494cbb4d26ba47273b Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:23:15 -0700 Subject: [PATCH 35/61] fix: sitemap format (#7453) --- public/sitemap.xml | 1780 ++++++++++++++++++++++++++++------- scripts/generate-sitemap.js | 14 +- src/pages/routes.test.ts | 2 +- 3 files changed, 1429 insertions(+), 367 deletions(-) diff --git a/public/sitemap.xml b/public/sitemap.xml index ac5e016f090..b632b7693f6 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,361 +1,1425 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + https://app.uniswap.org/ + 2023-10-11T19:57:27.976Z + weekly + 1.0 + + + https://app.uniswap.org/tokens + 2023-10-11T19:57:27.976Z + weekly + 0.8 + + + https://app.uniswap.org/send + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/swap + 2023-10-11T19:57:27.976Z + weekly + 0.9 + + + https://app.uniswap.org/pool/v2/find + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pool/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pool + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pools/v2/find + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pools/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pools + 2023-10-11T19:57:27.976Z + weekly + 0.7 + + + https://app.uniswap.org/add/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/add + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/increase + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/migrate/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/nfts + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/nfts/profile + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/create-proposal + 2023-10-11T19:57:27.976Z + weekly + 0.5 + + + https://app.uniswap.org/tokens/ethereum/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xdac17f958d2ee523a2206206994597c13d831ec7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6b175474e89094c44da98b954eedeac495271d0f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x514910771af9ca656af840dff83e8264ecf986ca + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xae78736cd615f374d3085123a210448e74fc6393 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x853d955acef822db058eb8505911ed77f175b99e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb62e45c3df611dce236a6ddc7a493d79f9dfadef + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x046eee2cc3188071c02bfc1745a6b17c656e3f3d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x20561172f791f915323241e885b4f7d5187c36e1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x1a7e4e63778b4f12a199c062f3efdd288afcbce8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5a98fcbea516cf06857215779fd812ca3bef1b32 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5f98805a4e8be255a32880fdec7f6728c6568ba0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd0d56273290d339aaf1417d9bfa1bb8cfe8a0933 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x1abaea1f7c830bd89acc67ec4af516284b1bc33c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x72e4f9f808c49a2a61de9c5896298920dc4eeea9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x92d6c1e31e14520e676a687f0a93788b716beff5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x423f4e6138e475d85cf7ea071ac92097ed631eea + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6c3ea9036406852006290770bedfcaba0e23a0e8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb504035a11e672e12a099f32b1672b9c4a78b22f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x183015a9ba6ff60230fdeadc3f43b3d788b13e21 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9813037ee2218799597d83d4a5b6f3b6778218d9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd33526068d116ce69f19a9ee46f0bd304f21a51f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4d224452801aced8b2f0aebe155379bb5d594381 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf951e335afb289353dc249e82926178eac7ded78 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x78a0a62fba6fb21a83fe8a3433d44c73a4017a6f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf411903cbc70a74d22900a5de66a2dda66507255 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb23d80f5fefcddaa212212f028021b41ded428cf + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xbe9895146f7af43049ca1c1ae358b0541ea49704 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5283d291dbcf85356a21ba090e6db59121208b44 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd1d2eb1b1e90b638588728b4130137d262c87cae + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf57e7e7c23978c3caec3c3548e3d615c346e79ff + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9d65ff81a3c488d585bbfb0bfe3c7707c7917f54 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xe28b3b32b6c345a34ff64674606124dd5aceca30 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x68749665ff8d2d112fa859aa293f07a622782f38 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd533a949740bb3306d119cc777fa900ba034cd52 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x163f8c2467924be0ae7b5347228cabf260318753 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9bf1d7d63dd7a4ce167cf4866388226eeefa702e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4a220e6096b25eadb88358cb44068a3248254675 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x8207c1ffc5b6804f6024322ccf34f29c3541ae26 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x2b591e99afe9f32eaa6214f7b7629768c40eeb39 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x06450dee7fd2fb8e39061434babcfc05599a6fb8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0ab87046fbb341d058f17cbc4c1133f25a20a52f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa0ef786bf476fe0810408caba05e536ac800ff86 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5faa989af96af85384b8a938c2ede4a7378d9875 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4fabb145d64652a948d72533023f6e7a623c7c53 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf4d2888d29d722226fafa5d9b24f9164c092421e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x49d72e3973900a195a155a46441f0c08179fdb64 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc221b7e65ffc80de234bbb6667abdd46593d34f0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6368e1e18c4c419ddfc608a0bed1ccb87b9250fc + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xed328e9c1179a30ddc1e7595e036aed8760c22af + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf21661d0d1d76d3ecb8e1b9f1c923dbfffae4097 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xbe042e9d09cb588331ff911c2b46fd833a3e5bd6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xe41d2489571d322189246dafa5ebde1f4699f498 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3c3a81e81dc49a522a592e7622a7e711c06bf354 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x8f8221afbb33998d8584a2b05749ba73c37a938a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x820802fa8a99901f52e39acd21177b0be6ee2974 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x808507121b80c02388fad14726482e061b8da827 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc08512927d12348f6620a698105e1baac6ecd911 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6810e776880c02933d47db1b9fc05908e5386b96 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x582d872a1b094fc48f5de31d3b73f2d9be47def1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc00e94cb662c3520282e6f5717214004a7f26888 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0b7f0e51cd1739d6c96982d55ad8fa634dd43a9c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xaaa9214f675316182eaa21c85f0ca99160cc3aaa + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xaea46a60368a7bd060eec7df8cba43b7ef41ad85 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x967da4048cd07ab37855c090aaf366e4ce1b9f48 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x75c97384ca209f915381755c582ec0e2ce88c1ba + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa8b919680258d369114910511cc87595aec0be6d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x037a54aab062628c9bbae1fdb1583c195585fe41 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3ef3b555842cdaff0f4f0b79c9dd65096d60ba63 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa36fdbbae3c9d55a1d67ee5821d53b50b63a1ab9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc944e90c64b2c07662a292be6244bdf05cda44a7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xaa6e8127831c9de45ae56bb1b0d4d4da6e5665bd + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x18084fba666a33d37592fa2633fd49a74dd93a88 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x33349b282065b0284d756f0577fb39c158f935e6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x470c8950c0c3aa4b09654bc73b004615119a44b5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xff836a5821e69066c87e268bc51b849fab94240c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xba5bde662c17e2adff1075610382b9b691296350 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9aab071b4129b083b01cb5a0cb513ce7eca26fa5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf939e0a03fb07f59a73314e73794be0e57ac1b4e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x112b08621e27e10773ec95d250604a041f36c582 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x82af49447d8a07e3bd95bd0d56f35241523fbab1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xff970a61a04b1ca14834a43f5de4533ebddb5cc8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xaf88d065e77c8cc2239327c5edb3a432268e5831 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x912ce59144191c1204e64559fe8253a0e49e6548 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x5979d7b546e38e414f7e9822514be443a4800529 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x0c880f6761f1af8d9aa9c466984b80dab9a8c9e8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3082cc23568ea640225c2467653db90e9250aaa0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xf97f4df75117a78c1a5a0dbb814af92458539fb4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x539bde0d7dbd336b79148aa742883198bbf60342 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x18c11fd286c5ec11c3b683caa813b77f5163a122 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x772598e9e62155d7fdfe65fdf01eb5a53a8465be + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x2297aebd383787a160dd0d9f71508148769342e3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x6694340fc020c5e6b96567843da2df01b2ce1eb6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfa5ed56a203466cbbc2430a43c66b9d8723528e7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x1f52145666c862ed3e2f1da213d479e61b2892af + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x83d6c8c06ac276465e4c92e7ac8c23740f435140 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x6fd58f5a2f3468e35feb098b5f59f04157002407 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x32eb7902d4134bf98a28b963d26de779af92a212 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x0341c0c0ec423328621788d4854119b97f44e391 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x93d504070ab0eede5449c89c5ea0f5e34d8103f8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xd77b108d4f6cefaa0cae9506a934e825becca46e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x4e352cf164e64adcbad318c3a1e222e9eba4ce42 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x13ad51ed4f1b7e9dc168d8a00cb3f4ddd85efa60 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x9ed7e4b1bff939ad473da5e7a218c771d1569456 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x289ba1701c2f088cf0faf8b3705246331cb8a839 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x93b346b6bc2548da6a1e7d98e9a421b42541425b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x17fc002b466eec40dae837fc4be5c67993ddbd6f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xd74f5255d557944cf7dd0e45ff521520002d5748 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x51f9f9ff6cb2266d68c04ec289c7aba81378a383 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3a33473d7990a605a88ac72a78ad4efc40a54adb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x9d2f299715d94d8a7e6f5eaa8e654e8c74a988a7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x7c8a1a80fdd00c9cccd6ebd573e9ecb49bfa2a59 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3404149e9ee6f17fb41db1ce593ee48fbdcd9506 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x8d9ba570d6cb60c7e3e0f31343efe75ab8e65fb1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3a18dcc9745edcd1ef33ecb93b0b6eba5671e7ca + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x9623063377ad1b27544c965ccd7342f7ea7e88c7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x088cd8f5ef3652623c22d48b1605dcfe860cd704 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfc77b86f3ade71793e1eec1e7944db074922856e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xcf985aba4647a432e60efceeb8054bbd64244305 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x655a6beebf2361a19549a99486ff65f709bd2646 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x371c7ec6d8039ff7933a2aa28eb827ffe1f52f07 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x7dd747d63b094971e6638313a6a2685e80c7fb2e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xc47d9753f3b32aa9548a7c3f30b6aec3b2d2798c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xe85b662fe97e8562f4099d8a1d5a92d4b453bf30 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfea7a6a0b346362bf88a9e4a88416b77a57d6c2a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x431402e8b9de9aa016c743880e04e517074d8cec + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xd67a097dce9d4474737e6871684ae3c05460f571 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x51fc0f6660482ea73330e414efd7808811a57fa2 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x7f5c764cbc14f9669b88837ca1490cca17c31607 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x4200000000000000000000000000000000000006 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x4200000000000000000000000000000000000042 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x94b008aa00579c1307b0ef2c499ad98a8ce58e58 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x68f180fcce6836688e9084f035309e29bf0a2095 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x0b2c639c533813f4aa9d7837caf62653d097ff85 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xdc6ff44d5d932cbd77b52e5612ba0529dc6226f1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x1f32b1c2345538c0c6f582fcb022739c4a194ebb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x8700daec35af8ff88c16bdf0418774cb3d7599b4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x9e1028f5f1d5ede59748ffcee5532509976840e0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xc5b001dc33727f8f26880b184090d3e252470d45 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x920cf626a271321c151d027030d5d08af699456b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xc40f949f8a4e094d1b49a23ea9241d289b7b2819 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x9560e827af36c94d2ac33a39bce1fe78631088db + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x296f55f8fb28e498b858d0bcda06d955b2cb3f97 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x350a791bfc2c21f9ed5d10980dad2e2638ffa7f6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x217d47011b23bb961eb6d93ca9945b7501a5bb11 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xf98dcd95217e15e05d8638da4c91125e59590b07 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x73cb180bf0521828d8849bc8cf2b920918e23032 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x9bcef72be871e61ed4fbbc7630889bee758eb81d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xc03b43d492d904406db2d7d57e67c7e8234ba752 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xfdb794692724153d1488ccdbe0c56c252596735f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x50bce64397c75488465253c0a034b8097fea6578 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x14778860e937f509e651192a90589de711fb88a9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2791bca1f2de4661ed88a30c99a7a9449aa84174 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x7ceb23fd6bc0add59e62ac25578270cff1b9f619 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc2132d05d31c914a87c6611c10748aeb04b58e8f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x53e0bca35ec356bd5dddfebbd1fc0fd03fabad39 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x77a6f2e9a9e44fd5d5c3f9be9e52831fc1c3c0a0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe0b52e49357fd4daf2c15e02058dce6bc0057db4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xdc3326e71d45186f113a2f448984ca0e8d201995 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x49e6a20f1bbdfeec2a8222e052000bbb14ee6007 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x430ef9263e76dae63c84292c3409d61c598e9682 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xf88332547c680f755481bf489d890426248bb275 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc3c7d422809852031b44ab29eec9f1eff2a58756 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xd6df932a45c0f255f85145f286ea0b292b21c90b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x172370d5cd63279efa6d502dab29171933a610af + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x45c32fa6df82ead1e2ef74d17b76547eddfaff89 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xd0258a3fd00f38aa8090dfee343f10a9d4d30d3f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb33eaad8d922b1083446dc23f610c2567fb5180f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe5417af564e4bfda1c483642db72007871397896 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x311434160d7537be358930def317afb606c0d737 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x61299774020da444af134c82fa83e3810b309991 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa3fa99a148fa48d14ed51d610c367c61876997f1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x381caf412b45dac0f62fbeec89de306d3eabe384 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x431d5dff03120afa4bdf332c61a6e1766ef37bdb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe2aa7db6da1dae97c5f5c6914d285fbfcc32a128 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb6a5ae40e79891e4deadad06c8a7ca47396df21c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xbbba073c31bf03b8acf7c28ef0738decf3695683 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa486c6bc102f409180ccb8a94ba045d39f8fc7cb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0308a3a9c433256ad7ef24dbef9c49c8cb01300a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe631dabef60c37a37d70d3b4f812871df663226f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x8a16d4bf8a0a716017e8d2262c4ac32927797a2f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x553d3d295e0f695b9228246232edf400ed3560b5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe238ecb42c424e877652ad82d8a939183a04c35f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe261d618a959afffd53168cd07d12e37b26761db + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x15e99d827c1d2fc2b9b5312d1e71713c88110bdb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0b3f868e0be5597d5db7feb59e1cadbb0fdda50a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe111178a87a3bff0c8d18decba5798827539ae99 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x5fe2b58c013d7601147dcdd68c143a77499f5531 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2ab0e9e4ee70fff1fb9d67031e44f6410170d00e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xac0f66379a6d7801d7726d5a943356a172549adb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x71eeba415a523f5c952cc2f06361d5443545ad28 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xf2ae0038696774d65e67892c9d301c5f2cbbda58 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x50b728d8d964fd00c2d0aad81718b71311fef68a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x235737dbb56e8517391473f7c964db31fa6ef280 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x820802fa8a99901f52e39acd21177b0be6ee2974 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xfa68fb4628dff1028cfec22b4162fccd0d45efb6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x18ec0a6e18e5bc3784fdd3a3634b31245ab704f6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2f6f07cdcf3588944bf4c42ac74ff24bf56e7590 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x5a7bb7b8eff493625a2bb855445911e63a490e42 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc708d6f2153933daa50b2d0758955be0a93a8fec + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x11cd37bb86f65419713f30673a480ea33c826872 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc59132fbdf8de8fbe510f568a5d831c991b4fc38 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa1c57f48f0deb89f569dfbe6e2b7f46d33606fd4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xed755dba6ec1eb520076cec051a582a6d81a8253 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x3b7e1ce09afe2bb3a23919afb65a38e627cfbe97 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x4200000000000000000000000000000000000006 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x236aa50979d5f3de3bd1eeb40e81137f22ab794b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x55d398326f99059ff775485246999027b3197955 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x2170ed0880ac9a755fd29b2688956bd959f933f8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xe9e7cea3dedca5984780bafc599bd69add087d56 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xd691d9a68c887bdf34da8c36f63487333acfd103 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x031b41e504677879370e9dbcf937283a8691fa7f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xb0d502e938ed5f4df2e681fe6e419ff29631d62b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xa2b726b1145a4773f68593cf171187d8ebe4d495 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x66803fb87abd4aac3cbb3fad7c3aa01f6f3fb207 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x471ece3750da237f93b8e339c536989b8978a438 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x765de816845861e75a25fca122bb6898b8b1282a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x37f750b7cc259a2f741af45294f6a16572cf5cad + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0xd71ffd0940c920786ec4dbb5a12306669b5b81ef + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x62b8b11039fcfe5ab0c56e502b1c372a3d2a9c7a + 2023-10-11T19:57:27.976Z + 0.8 + \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js index a1c395d6f13..94b0f3cde54 100644 --- a/scripts/generate-sitemap.js +++ b/scripts/generate-sitemap.js @@ -21,11 +21,11 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { const sitemap = await parseStringPromise(data) if (sitemap.urlset.url) { sitemap.urlset.url.forEach((url) => { - const lastMod = new Date(url['$'].lastmod).getTime() + const lastMod = new Date(url.lastmod).getTime() if (lastMod < Date.now() - weekMs) { - url['$'].lastmod = nowISO + url.lastmod = nowISO } - sitemapURLs[url['$']['loc']] = true + sitemapURLs[url.loc] = true }) } @@ -45,11 +45,9 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { const tokenURL = `https://app.uniswap.org/tokens/${chainName.toLowerCase()}/${address}` if (!(tokenURL in sitemapURLs)) { sitemap.urlset.url.push({ - $: { - loc: [tokenURL], - lastmod: [nowISO], - priority: [0.8], - }, + loc: [tokenURL], + lastmod: [nowISO], + priority: [0.8], }) } }) diff --git a/src/pages/routes.test.ts b/src/pages/routes.test.ts index 5022f261fd2..cb89cc77f46 100644 --- a/src/pages/routes.test.ts +++ b/src/pages/routes.test.ts @@ -9,7 +9,7 @@ describe('Routes', () => { const contents = fs.readFileSync('./public/sitemap.xml', 'utf8') const sitemap = await parseStringPromise(contents) - const sitemapPaths: string[] = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname) + const sitemapPaths: string[] = sitemap.urlset.url.map((url: any) => new URL(url.loc).pathname) sitemapPaths .filter((p) => !p.includes('/0x')) From 7001452f89dcc4722255f4ecfcd632ac24286868 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:30:02 -0700 Subject: [PATCH 36/61] fix: move user prop updater into statsigProvider (#7442) From ad1e2c60a1d6c295a408b149ea660a3b6caa7afb Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 12 Oct 2023 15:42:57 -0700 Subject: [PATCH 37/61] fix: delay setting user.router_preference until statsig and redux initialize (#7458) --- src/pages/App.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 3f15ce0356a..f844b5bcc9b 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -5,7 +5,7 @@ import ErrorBoundary from 'components/ErrorBoundary' import Loader from 'components/Icons/LoadingSpinner' import NavBar, { PageTabs } from 'components/NavBar' import { UK_BANNER_HEIGHT, UK_BANNER_HEIGHT_MD, UK_BANNER_HEIGHT_SM, UkBanner } from 'components/NavBar/UkBanner' -import { useFeatureFlagsIsLoaded } from 'featureFlags' +import { FeatureFlag, useFeatureFlagsIsLoaded } from 'featureFlags' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useAtom } from 'jotai' import { useBag } from 'nft/hooks/useBag' @@ -16,7 +16,7 @@ import { useAppSelector } from 'state/hooks' import { AppState } from 'state/reducer' import { RouterPreference } from 'state/routing/types' import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' -import { StatsigProvider, StatsigUser } from 'statsig-react' +import { StatsigProvider, StatsigUser, useGate } from 'statsig-react' import styled from 'styled-components' import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader' import { useIsDarkMode } from 'theme/components/ThemeToggle' @@ -214,6 +214,8 @@ function UserPropertyUpdater() { const [routerPreference] = useRouterPreference() const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() + const { isLoading: isUniswapXDefaultLoading } = useGate(FeatureFlag.uniswapXDefaultEnabled) + const rehydrated = useAppSelector((state) => state._persist.rehydrated) useEffect(() => { // User properties *must* be set before sending corresponding event properties, @@ -246,6 +248,8 @@ function UserPropertyUpdater() { }, [isDarkMode]) useEffect(() => { + if (isUniswapXDefaultLoading || !rehydrated) return + // If we're not in the transition period to UniswapX opt-out, set the router preference to whatever is specified. if (!isUniswapXDefaultEnabled) { user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) @@ -260,6 +264,6 @@ function UserPropertyUpdater() { // Otherwise, the user has opted out or their preference is UniswapX/client, so set the preference to whatever is specified. user.set(CustomUserProperties.ROUTER_PREFERENCE, routerPreference) - }, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX]) + }, [routerPreference, isUniswapXDefaultEnabled, userOptedOutOfUniswapX, isUniswapXDefaultLoading, rehydrated]) return null } From b553a6fcd86b49f5984beb97c0d22b1da8902adf Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:17:57 -0700 Subject: [PATCH 38/61] feat: nfts sitemap (#7456) * feat: add nft collections to sitemap * feat: size check on sitemap * fix: update generate-sitemap script --- public/sitemap.xml | 550 ++++++++++++++++++++++++++++++++++++ scripts/generate-sitemap.js | 53 +++- 2 files changed, 598 insertions(+), 5 deletions(-) diff --git a/public/sitemap.xml b/public/sitemap.xml index b632b7693f6..f07cb711f7e 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1422,4 +1422,554 @@ 2023-10-11T19:57:27.976Z 0.8 + + https://app.uniswap.org/tokens/ethereum/0x5de8ab7e27f6e7a1fff3e5b337584aa43961beef + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x940a2db1b7008b6c776d4faaca729d6d4a4aa551 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x80f0c1c49891dcfdd40b6e0f960f84e6042bcb6f + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x1622bf67e6e5747b81866fe0b85178a93c7f86e3 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x031d35296154279dc1984dcd93e392b1f946737b + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xdfa46478f9e5ea86d57387849598dbfb2e964b02 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x104592a158490a9228070e0a8e5343b499e125d0 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x4e3decbb3645551b8a19f0ea1678079fcb33fb4c + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x02de4766c272abc10bc88c220d214a26960a7e92 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x60e4d786628fea6478f785a6d7e704777c86a7c6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x99a9b7c1116f9ceeb1652de04d5969cce509b069 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb7f7f6c52f2e2fdb1963eab30438024864c313f6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x23581767a106ae21c074b2276d25e5c3e136a68b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xba30e5f9bb24caa003e9f2f0497ad287fdf95623 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xbd3531da5cf5857e7cfaa92426877b022e612cf8 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x306b1ea3ecdf94ab739f1910bbda052ed4a9f949 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x1a92f7381b9f03921564a437210bb9396471050c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5cc5b05a8a13e3fbdb0bb9fccd98d38e50f90c38 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5af0d9827e0c53e4799bb226655a1de152a425a5 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x3bf2922f4520a8ba0c2efc3d2a1539678dad5e9d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xe785e82358879f061bc3dcac6f0444462d4b5330 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x76be3b62873462d2142405439777e971754e8e77 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xfd43af6d3fe1b916c026f6ac35b3ede068d1ca01 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x1cb1a5e65610aeff2551a50f76a87a7d3fb649c6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x6339e5e072086621540d0362c4e3cea0d643e114 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb932a70a57673d89f4acffbe830e8ed7f75fb9e0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x79fcdef22feed20eddacbb2587640e45491b757f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xa3aee8bce55beea1951ef834b99f3ac60d1abeeb + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x769272677fab02575e84945f03eca517acc544cc + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4db1f25d3d98600140dfc18deb7515be5bd293af + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x34eebee6942d8def3c125458d1a86e0a897fd6f9 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x59468516a8259058bad1ca5f8f4bff190d30e066 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x394e3d3044fc89fcdd966d3cb35ac0b32b0cda91 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x28472a58a490c5e09a238847f66a68a47cc76f0f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x341a1c534248966c4b6afad165b98daed4b964ef + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xccc441ac31f02cd96c153db6fd5fe0a2f4e6a68d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x764aeebcf425d56800ef2c84f2578689415a2daa + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x160c404b2b49cbc3240055ceaee026df1e8497a0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd2f668a8461d6761115daf8aeb3cdf5f40c532c6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x39ee2c7b3cb80254225884ca001f57118c8f21b6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd774557b647330c91bf44cfeab205095f7e6c367 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x1792a96e5668ad7c167ab804a100ce42395ce54d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x04afa589e2b933f9463c5639f412b183ec062505 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xe75512aa3bec8f00434bbd6ad8b0a3fbff100ad6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x348fc118bcc65a92dc033a951af153d14d945312 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x892848074ddea461a15f337250da3ce55580ca85 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4b15a9c28034dc83db40cd810001427d3bd7163d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7ea3cca10668b8346aec0bf1844a49e995527c8b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb852c6b5892256c264cc2c888ea462189154d8d7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x9378368ba6b85c1fba5b131b530f5f5bedf21a18 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x2acab3dea77832c09420663b0e1cb386031ba17b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x0c2e57efddba8c768147d1fdf9176a0a6ebd5d83 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x08d7c0242953446436f34b4c78fe9da38c73668d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x8943c7bac1914c9a7aba750bf2b6b09fd21037e0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x364c828ee171616a39897688a831c2499ad972ec + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7f36182dee28c45de6072a34d29855bae76dbe2f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf61f24c2d93bf2de187546b14425bf631f28d6dc + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x797a48c46be32aafcedcfd3d8992493d8a1f256b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x123b30e25973fecd8354dd5f41cc45a3065ef88c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x6632a9d63e142f17a668064d41a21193b49b41a0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf4ee95274741437636e748ddac70818b4ed7d043 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x57a204aa1042f6e66dd7730813f4024114d74f37 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd1258db6ac08eb0e625b75b371c023da478e94a9 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x75e95ba5997eb235f40ecf8347cdb11f18ff640b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd532b88607b1877fe20c181cba2550e3bbd6b31c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xa1d4657e0e6507d5a94d06da93e94dc7c8c44b51 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xedb61f74b0d09b2558f1eeb79b247c1f363ae452 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7d8820fa92eb1584636f4f5b8515b5476b75171a + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x231d3559aa848bf10366fb9868590f01d34bf240 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xad9fd7cb4fc7a0fbce08d64068f60cbde22ed34c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x0e9d6552b85be180d941f1ca73ae3e318d2d4f1f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb716600ed99b4710152582a124c697a7fe78adbf + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4e1f41613c9084fdb9e34e11fae9412427480e56 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x79986af15539de2db9a5086382daeda917a9cf0c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc99c679c50033bbc5321eb88752e89a93e9e83c5 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc36cf0cfcb5d905b8b513860db0cfe63f6cf9f5c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x3110ef5f612208724ca51f5761a69081809f03b7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x036721e5a769cc48b3189efbb9cce4471e8a48b1 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x524cab2ec69124574082676e6f654a18df49a048 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7ab2352b1d2e185560494d5e577f9d3c238b78c5 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x32973908faee0bf825a343000fe412ebe56f802a + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7daec605e9e2a1717326eedfd660601e2753a057 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x33fd426905f149f8376e227d0c9d3340aad17af1 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x466cfcd0525189b573e794f554b8a751279213ac + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x6be69b2a9b153737887cfcdca7781ed1511c7e36 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x80336ad7a747236ef41f47ed2c7641828a480baa + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x9401518f4ebba857baa879d9f76e1cc8b31ed197 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4b61413d4392c806e6d0ff5ee91e6073c21d6430 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x09233d553058c2f42ba751c87816a8e9fae7ef10 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x960b7a6bcd451c9968473f7bbfd9be826efd549a + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x36d30b3b85255473d27dd0f7fd8f35e36a9d6f06 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x698fbaaca64944376e2cdc4cad86eaa91362cf54 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x497a9a79e82e6fc0ff10a16f6f75e6fcd5ae65a8 + 2023-10-11T23:24:32.127Z + 0.7 + \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js index 94b0f3cde54..db6cbad8cd0 100644 --- a/scripts/generate-sitemap.js +++ b/scripts/generate-sitemap.js @@ -6,7 +6,7 @@ const { parseStringPromise, Builder } = require('xml2js') const weekMs = 7 * 24 * 60 * 60 * 1000 const nowISO = new Date().toISOString() -const getQuery = (chain) => ` +const getTopTokensQuery = (chain) => ` query { topTokens(pageSize: 100, page: 1, chain: ${chain}, orderBy: VOLUME) { address @@ -15,6 +15,20 @@ const getQuery = (chain) => ` ` const chains = ['ETHEREUM', 'ARBITRUM', 'OPTIMISM', 'POLYGON', 'BASE', 'BNB', 'CELO'] +const nftTopCollectionsQuery = ` + query { + topCollections(first: 100, duration: MAX) { + edges { + node { + nftContracts { + address + } + } + } + } + } +` + fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { const sitemapURLs = {} try { @@ -30,15 +44,15 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { } for (const chainName of chains) { - const response = await fetch('https://api.uniswap.org/v1/graphql', { + const tokensResponse = await fetch('https://api.uniswap.org/v1/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Origin: 'https://app.uniswap.org', }, - body: JSON.stringify({ query: getQuery(chainName) }), + body: JSON.stringify({ query: getTopTokensQuery(chainName) }), }) - const tokensJSON = await response.json() + const tokensJSON = await tokensResponse.json() const tokenAddresses = tokensJSON.data.topTokens.map((token) => token.address.toLowerCase()) tokenAddresses.forEach((address) => { @@ -53,10 +67,39 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { }) } + const nftResponse = await fetch('https://api.uniswap.org/v1/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Origin: 'https://app.uniswap.org', + }, + body: JSON.stringify({ query: nftTopCollectionsQuery }), + }) + const nftJSON = await nftResponse.json() + const collectionAddresses = nftJSON.data.topCollections.edges.map((edge) => edge.node.nftContracts[0].address) + collectionAddresses.forEach((address) => { + const collectionURL = `https://app.uniswap.org/nfts/collection/${address}` + if (!(collectionURL in sitemapURLs)) { + sitemap.urlset.url.push({ + loc: [collectionURL], + lastmod: [nowISO], + priority: [0.7], + }) + } + }) + const builder = new Builder() const xml = builder.buildObject(sitemap) - fs.writeFile('./public/sitemap.xml', xml, (error) => { + const path = './public/sitemap.xml' + fs.writeFile(path, xml, (error) => { if (error) throw error + const stats = fs.statSync(path) + const fileSizeBytes = stats.size + const fileSizeMegabytes = fileSizeBytes / (1024 * 1024) + + if (fileSizeMegabytes > 50) { + throw new Error('Generated sitemap file size exceeds 50MB') + } console.log('Sitemap updated') }) } catch (e) { From 749c9b40ea2a97b7352a0eccfbf44aca2d615061 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:27:15 -0700 Subject: [PATCH 39/61] fix: specify canonical URLs (#7455) * fix: app.uniswap.org canonicals * fix: deps test * fix: use window.location.origin --- package.json | 2 ++ src/index.tsx | 8 +++++++- yarn.lock | 30 ++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 86d377be8ae..7db671e70a0 100644 --- a/package.json +++ b/package.json @@ -191,6 +191,7 @@ "@sentry/react": "^7.45.0", "@sentry/tracing": "^7.45.0", "@sentry/types": "^7.45.0", + "@types/react-helmet": "^6.1.7", "@types/react-window-infinite-loader": "^1.0.6", "@uniswap/analytics": "1.5.0", "@uniswap/analytics-events": "^2.24.0", @@ -267,6 +268,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-feather": "^2.0.8", + "react-helmet": "^6.1.0", "react-infinite-scroll-component": "^6.1.0", "react-is": "^17.0.2", "react-markdown": "^4.3.1", diff --git a/src/index.tsx b/src/index.tsx index ead27ddc597..fbe006a1523 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,9 +11,10 @@ import { BlockNumberProvider } from 'lib/hooks/useBlockNumber' import { MulticallUpdater } from 'lib/state/multicall' import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { Helmet } from 'react-helmet' import { QueryClient, QueryClientProvider } from 'react-query' import { Provider } from 'react-redux' -import { BrowserRouter, HashRouter } from 'react-router-dom' +import { BrowserRouter, HashRouter, useLocation } from 'react-router-dom' import { SystemThemeUpdater, ThemeColorMetaUpdater } from 'theme/components/ThemeToggle' import { isBrowserRouterEnabled } from 'utils/env' @@ -35,8 +36,13 @@ if (window.ethereum) { } function Updaters() { + const location = useLocation() + const baseUrl = `${window.location.origin}${location.pathname}` return ( <> + + + diff --git a/yarn.lock b/yarn.lock index 5036ce978a6..50505ed1867 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5699,6 +5699,13 @@ dependencies: "@types/react" "*" +"@types/react-helmet@^6.1.7": + version "6.1.7" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.7.tgz#4cecc03165084727408d29d92d8fdd4a7e267403" + integrity sha512-mUFOrdR3AIvHE8BEaqzfPEnR62xq5PHQJehhgNtj78x0d5NOxUCQ0j+r9OZ4RvB+prNZx9wvQnVW8ApFBX+fig== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.24": version "7.1.24" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.24.tgz#6caaff1603aba17b27d20f8ad073e4c077e975c0" @@ -17488,10 +17495,10 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.0.1: - version "3.2.0" - resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz" - integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +react-fast-compare@^3.0.1, react-fast-compare@^3.1.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" + integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== react-feather@^2.0.8: version "2.0.9" @@ -17512,6 +17519,16 @@ react-focus-lock@^2.3.1: use-callback-ref "^1.2.1" use-sidecar "^1.0.1" +react-helmet@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" + react-infinite-scroll-component@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz#7e511e7aa0f728ac3e51f64a38a6079ac522407f" @@ -17671,6 +17688,11 @@ react-scripts@^5.0.1: optionalDependencies: fsevents "^2.3.2" +react-side-effect@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" + integrity sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw== + react-spring@^9.5.5: version "9.5.5" resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-9.5.5.tgz#314009a65efc04d0ef157d3d60590dbb9de65f3c" From e9fbf613758f31620bf4b71aa3b158c17eb7790e Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 13 Oct 2023 10:20:10 -0700 Subject: [PATCH 40/61] fix: position X opt in tooltip on TDP (#7465) --- src/pages/Swap/UniswapXOptIn.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/Swap/UniswapXOptIn.tsx b/src/pages/Swap/UniswapXOptIn.tsx index 4d2b548a05b..0ea44934d4a 100644 --- a/src/pages/Swap/UniswapXOptIn.tsx +++ b/src/pages/Swap/UniswapXOptIn.tsx @@ -17,6 +17,7 @@ import { import { formatCommonPropertiesForTrade } from 'lib/utils/analytics' import { PropsWithChildren, useEffect, useRef, useState } from 'react' import { X } from 'react-feather' +import { useLocation } from 'react-router-dom' import { Text } from 'rebass' import { useAppDispatch } from 'state/hooks' import { RouterPreference } from 'state/routing/types' @@ -73,6 +74,7 @@ const OptInContents = ({ const dispatch = useAppDispatch() const [showYoureIn, setShowYoureIn] = useState(false) const isVisible = isOnClassic + const location = useLocation() // adding this as we need to mount and then set shouldAnimate = true after it mounts to avoid a flicker on initial mount const [shouldAnimate, setShouldAnimate] = useState(false) @@ -115,7 +117,7 @@ const OptInContents = ({ const containerRef = useRef() - if (isSmall) { + if (isSmall || location.pathname.includes('/tokens/')) { return ( From cfaf5d79c1bba9623cd1dc7fa58fb8a67984ac08 Mon Sep 17 00:00:00 2001 From: Tina <59578595+tinaszheng@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:33:47 -0400 Subject: [PATCH 41/61] feat: Remove local routing setting (#7462) * remove client side router preference * update e2e test * fix comment --- cypress/e2e/swap/settings.test.ts | 2 - src/components/NavigationTabs/index.tsx | 2 +- src/components/RouterLabel/index.tsx | 4 +- .../RouterPreferenceSettings/index.test.tsx | 14 --- .../RouterPreferenceSettings/index.tsx | 105 ++++++----------- src/components/Settings/index.test.tsx | 27 +++-- src/components/Settings/index.tsx | 9 +- src/hooks/useDebouncedTrade.test.ts | 6 +- src/hooks/useDebouncedTrade.ts | 5 +- src/pages/MigrateV2/MigrateV2Pair.tsx | 6 +- src/state/migrations.test.ts | 2 +- src/state/migrations.ts | 2 + src/state/migrations/2.test.ts | 62 ++++++++++ src/state/migrations/2.ts | 29 +++++ src/state/reducer.ts | 2 +- src/state/routing/slice.ts | 106 +++++++++--------- src/state/routing/types.ts | 2 - src/state/routing/utils.ts | 4 - src/test-utils/constants.ts | 4 +- 19 files changed, 219 insertions(+), 174 deletions(-) create mode 100644 src/state/migrations/2.test.ts create mode 100644 src/state/migrations/2.ts diff --git a/cypress/e2e/swap/settings.test.ts b/cypress/e2e/swap/settings.test.ts index 9d878bbe7b4..0dbbe74d6fa 100644 --- a/cypress/e2e/swap/settings.test.ts +++ b/cypress/e2e/swap/settings.test.ts @@ -9,7 +9,6 @@ describe('Swap settings', () => { cy.contains('Max. slippage').should('exist') cy.contains('Transaction deadline').should('exist') cy.contains('UniswapX').should('exist') - cy.contains('Local routing').should('exist') cy.get(getTestSelector('open-settings-dialog-button')).click() cy.contains('Settings').should('not.exist') }) @@ -28,7 +27,6 @@ describe('Swap settings', () => { .within(() => { cy.contains('Max. slippage').should('exist') cy.contains('UniswapX').should('exist') - cy.contains('Local routing').should('exist') cy.contains('Transaction deadline').should('exist') cy.get(getTestSelector('mobile-settings-close')).click() }) diff --git a/src/components/NavigationTabs/index.tsx b/src/components/NavigationTabs/index.tsx index 3bab8f2037b..eefbf4aef1e 100644 --- a/src/components/NavigationTabs/index.tsx +++ b/src/components/NavigationTabs/index.tsx @@ -114,7 +114,7 @@ export function AddRemoveTabs({ )} {children && {children}} - + ) diff --git a/src/components/RouterLabel/index.tsx b/src/components/RouterLabel/index.tsx index 06ad4e0c48a..6339f9e7615 100644 --- a/src/components/RouterLabel/index.tsx +++ b/src/components/RouterLabel/index.tsx @@ -13,8 +13,10 @@ export default function RouterLabel({ trade, color }: { trade: SubmittableTrade; ) } - if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE || trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) { + + if (trade.quoteMethod === QuoteMethod.CLIENT_SIDE_FALLBACK) { return Uniswap Client } + return Uniswap API } diff --git a/src/components/Settings/RouterPreferenceSettings/index.test.tsx b/src/components/Settings/RouterPreferenceSettings/index.test.tsx index 6524e518a34..44a087579b7 100644 --- a/src/components/Settings/RouterPreferenceSettings/index.test.tsx +++ b/src/components/Settings/RouterPreferenceSettings/index.test.tsx @@ -24,18 +24,4 @@ describe('RouterPreferenceSettings', () => { expect(uniswapXToggle).toHaveAttribute('aria-selected', 'false') expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API) }) - it('toggles `Local Routing` router preference', () => { - render() - - const localRoutingToggle = screen.getByTestId('toggle-local-routing-button') - - fireEvent.click(localRoutingToggle) - expect(localRoutingToggle).toHaveAttribute('aria-selected', 'true') - expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.CLIENT) - - fireEvent.click(localRoutingToggle) - - expect(localRoutingToggle).toHaveAttribute('aria-selected', 'false') - expect(store.getState().user.userRouterPreference).toEqual(RouterPreference.API) - }) }) diff --git a/src/components/Settings/RouterPreferenceSettings/index.tsx b/src/components/Settings/RouterPreferenceSettings/index.tsx index 15aabc60698..7b721334742 100644 --- a/src/components/Settings/RouterPreferenceSettings/index.tsx +++ b/src/components/Settings/RouterPreferenceSettings/index.tsx @@ -1,17 +1,15 @@ import { Trans } from '@lingui/macro' -import { useWeb3React } from '@web3-react/core' import Column from 'components/Column' import UniswapXBrandMark from 'components/Logo/UniswapXBrandMark' import { RowBetween, RowFixed } from 'components/Row' import Toggle from 'components/Toggle' -import { isUniswapXSupportedChain } from 'constants/chains' import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useAppDispatch } from 'state/hooks' import { RouterPreference } from 'state/routing/types' import { useRouterPreference, useUserOptedOutOfUniswapX } from 'state/user/hooks' import { updateDisabledUniswapX, updateOptedOutOfUniswapX } from 'state/user/reducer' import styled from 'styled-components' -import { Divider, ExternalLink, ThemedText } from 'theme/components' +import { ExternalLink, ThemedText } from 'theme/components' const InlineLink = styled(ThemedText.BodySmall)` color: ${({ theme }) => theme.accent1}; @@ -23,81 +21,48 @@ const InlineLink = styled(ThemedText.BodySmall)` ` export default function RouterPreferenceSettings() { - const { chainId } = useWeb3React() const [routerPreference, setRouterPreference] = useRouterPreference() - const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId) const dispatch = useAppDispatch() const userOptedOutOfUniswapX = useUserOptedOutOfUniswapX() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() const isUniswapXOverrideEnabled = isUniswapXDefaultEnabled && !userOptedOutOfUniswapX - const uniswapXInEffect = - routerPreference === RouterPreference.X || - (routerPreference !== RouterPreference.CLIENT && isUniswapXOverrideEnabled) + const uniswapXInEffect = routerPreference === RouterPreference.X || isUniswapXOverrideEnabled return ( - <> - {uniswapXEnabled && ( - <> - - - - - - - - When available, aggregates liquidity sources for better prices and gas free swaps.{' '} - - Learn more - - - - - { - if (uniswapXInEffect) { - if (isUniswapXDefaultEnabled) { - // We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes. - dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true })) - } else { - // We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again. - dispatch(updateDisabledUniswapX({ disabledUniswapX: true })) - } - } - setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X) - }} - /> - - - - )} - - - - - Local routing - - - - - setRouterPreference( - routerPreference === RouterPreference.CLIENT - ? isUniswapXDefaultEnabled - ? RouterPreference.X - : RouterPreference.API - : RouterPreference.CLIENT - ) + + + + + + + + When available, aggregates liquidity sources for better prices and gas free swaps.{' '} + + Learn more + + + + + { + if (uniswapXInEffect) { + if (isUniswapXDefaultEnabled) { + // We need to remember if a opts out of UniswapX, so we don't request UniswapX quotes. + dispatch(updateOptedOutOfUniswapX({ optedOutOfUniswapX: true })) + } else { + // We need to remember if a user disables Uniswap X, so we don't show the opt-in flow again. + dispatch(updateDisabledUniswapX({ disabledUniswapX: true })) + } } - /> - - + setRouterPreference(uniswapXInEffect ? RouterPreference.API : RouterPreference.X) + }} + /> + ) } diff --git a/src/components/Settings/index.test.tsx b/src/components/Settings/index.test.tsx index eff42b85742..587384b9cdc 100644 --- a/src/components/Settings/index.test.tsx +++ b/src/components/Settings/index.test.tsx @@ -1,5 +1,5 @@ import { Percent } from '@uniswap/sdk-core' -import { isSupportedChain } from 'constants/chains' +import { isSupportedChain, isUniswapXSupportedChain } from 'constants/chains' import { mocked } from 'test-utils/mocked' import { fireEvent, render, screen, waitFor } from 'test-utils/render' @@ -14,25 +14,38 @@ describe('Settings Tab', () => { mocked(isSupportedChain).mockReturnValue(true) }) - it('renders routing settings when showRoutingSettings is true', async () => { - render() + it('renders routing settings when hideRoutingSettings is false', async () => { + mocked(isUniswapXSupportedChain).mockReturnValue(true) + render() const settingsButton = screen.getByTestId('open-settings-dialog-button') fireEvent.click(settingsButton) await waitFor(() => { - expect(screen.getByTestId('toggle-local-routing-button')).toBeInTheDocument() + expect(screen.getByTestId('toggle-uniswap-x-button')).toBeInTheDocument() }) }) - it('does not render routing settings when showRoutingSettings is false', async () => { - render() + it('does not render routing settings when hideRoutingSettings is true', async () => { + render() const settingsButton = screen.getByTestId('open-settings-dialog-button') fireEvent.click(settingsButton) await waitFor(() => { - expect(screen.queryByTestId('toggle-local-routing-button')).not.toBeInTheDocument() + expect(screen.queryByTestId('toggle-uniswap-x-button')).not.toBeInTheDocument() + }) + }) + + it('does not render routing settings when uniswapx is not enabled', async () => { + mocked(isUniswapXSupportedChain).mockReturnValue(false) + render() + + const settingsButton = screen.getByTestId('open-settings-dialog-button') + fireEvent.click(settingsButton) + + await waitFor(() => { + expect(screen.queryByTestId('toggle-uniswap-x-button')).not.toBeInTheDocument() }) }) }) diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx index 95d2da9b5ee..80075595ff3 100644 --- a/src/components/Settings/index.tsx +++ b/src/components/Settings/index.tsx @@ -5,7 +5,7 @@ import { Scrim } from 'components/AccountDrawer' import AnimatedDropdown from 'components/AnimatedDropdown' import Column, { AutoColumn } from 'components/Column' import Row from 'components/Row' -import { isSupportedChain, L2_CHAIN_IDS } from 'constants/chains' +import { isSupportedChain, isUniswapXSupportedChain, L2_CHAIN_IDS } from 'constants/chains' import useDisableScrolling from 'hooks/useDisableScrolling' import { useOnClickOutside } from 'hooks/useOnClickOutside' import { Portal } from 'nft/components/common/Portal' @@ -101,12 +101,12 @@ export default function SettingsTab({ autoSlippage, chainId, trade, - showRoutingSettings = true, + hideRoutingSettings = false, }: { autoSlippage: Percent chainId?: number trade?: InterfaceTrade - showRoutingSettings?: boolean + hideRoutingSettings?: boolean }) { const { chainId: connectedChainId } = useWeb3React() const showDeadlineSettings = Boolean(chainId && !L2_CHAIN_IDS.includes(chainId)) @@ -124,6 +124,9 @@ export default function SettingsTab({ useOnClickOutside(node, isOpenDesktop ? closeMenu : undefined) useDisableScrolling(isOpen) + const uniswapXEnabled = chainId && isUniswapXSupportedChain(chainId) + const showRoutingSettings = Boolean(uniswapXEnabled && !hideRoutingSettings) + const isChainSupported = isSupportedChain(chainId) const Settings = useMemo( () => ( diff --git a/src/hooks/useDebouncedTrade.test.ts b/src/hooks/useDebouncedTrade.test.ts index 7fa7cb3aa61..4f6c594a20e 100644 --- a/src/hooks/useDebouncedTrade.test.ts +++ b/src/hooks/useDebouncedTrade.test.ts @@ -34,7 +34,7 @@ beforeEach(() => { mocked(useIsWindowVisible).mockReturnValue(true) mocked(useAutoRouterSupported).mockReturnValue(true) - mocked(useRouterPreference).mockReturnValue([RouterPreference.CLIENT, () => undefined]) + mocked(useRouterPreference).mockReturnValue([RouterPreference.API, () => undefined]) }) describe('#useBestV3Trade ExactIn', () => { @@ -49,7 +49,7 @@ describe('#useBestV3Trade ExactIn', () => { TradeType.EXACT_INPUT, USDCAmount, DAI, - RouterPreference.CLIENT, + RouterPreference.API, /* account = */ undefined, /* inputTax = */ undefined, /* outputTax = */ undefined @@ -69,7 +69,7 @@ describe('#useDebouncedTrade ExactOut', () => { TradeType.EXACT_OUTPUT, DAIAmount, USDC_MAINNET, - RouterPreference.CLIENT, + RouterPreference.API, /* account = */ undefined, /* inputTax = */ undefined, /* outputTax = */ undefined diff --git a/src/hooks/useDebouncedTrade.ts b/src/hooks/useDebouncedTrade.ts index c37bf8c761b..46ae92ba820 100644 --- a/src/hooks/useDebouncedTrade.ts +++ b/src/hooks/useDebouncedTrade.ts @@ -37,7 +37,7 @@ export function useDebouncedTrade( tradeType: TradeType, amountSpecified?: CurrencyAmount, otherCurrency?: Currency, - routerPreferenceOverride?: RouterPreference.API | RouterPreference.CLIENT, + routerPreferenceOverride?: RouterPreference.API, account?: string, inputTax?: Percent, outputTax?: Percent @@ -97,8 +97,7 @@ export function useDebouncedTrade( const skipBothFetches = !autoRouterSupported || !isWindowVisible || isWrap const skipRoutingFetch = skipBothFetches || isDebouncing - const skipPreviewTradeFetch = - skipBothFetches || routerPreference === RouterPreference.CLIENT || isPreviewTradeDebouncing + const skipPreviewTradeFetch = skipBothFetches || isPreviewTradeDebouncing const previewTradeResult = usePreviewTrade( skipPreviewTradeFetch, diff --git a/src/pages/MigrateV2/MigrateV2Pair.tsx b/src/pages/MigrateV2/MigrateV2Pair.tsx index de4e5193f97..f2ac2717263 100644 --- a/src/pages/MigrateV2/MigrateV2Pair.tsx +++ b/src/pages/MigrateV2/MigrateV2Pair.tsx @@ -739,11 +739,7 @@ export default function MigrateV2Pair() { Migrate V2 liquidity - + {!account ? ( diff --git a/src/state/migrations.test.ts b/src/state/migrations.test.ts index 7c99f600061..56b9b8fbb39 100644 --- a/src/state/migrations.test.ts +++ b/src/state/migrations.test.ts @@ -13,7 +13,7 @@ const defaultState = { user: {}, _persist: { rehydrated: true, - version: 1, + version: 2, }, application: { chainId: null, diff --git a/src/state/migrations.ts b/src/state/migrations.ts index 143c0e6f193..6bb84163f7e 100644 --- a/src/state/migrations.ts +++ b/src/state/migrations.ts @@ -3,6 +3,7 @@ import { MigrationConfig } from 'redux-persist/es/createMigrate' import { migration0 } from './migrations/0' import { migration1 } from './migrations/1' +import { migration2 } from './migrations/2' import { legacyLocalStorageMigration } from './migrations/legacy' /** @@ -17,6 +18,7 @@ import { legacyLocalStorageMigration } from './migrations/legacy' export const migrations: MigrationManifest = { 0: migration0, 1: migration1, + 2: migration2, } // We use a custom migration function for the initial state, because redux-persist diff --git a/src/state/migrations/2.test.ts b/src/state/migrations/2.test.ts new file mode 100644 index 00000000000..4945c82402a --- /dev/null +++ b/src/state/migrations/2.test.ts @@ -0,0 +1,62 @@ +import { createMigrate } from 'redux-persist' +import { RouterPreference } from 'state/routing/types' +import { SlippageTolerance } from 'state/user/types' + +import { migration1 } from './1' +import { migration2, PersistAppStateV2 } from './2' + +const previousState: PersistAppStateV2 = { + user: { + userLocale: null, + // @ts-ignore this is intentionally a string and not the `RouterPreference` enum because `client` is a deprecated option + userRouterPreference: 'client', + userHideClosedPositions: false, + userSlippageTolerance: SlippageTolerance.Auto, + userSlippageToleranceHasBeenMigratedToAuto: true, + userDeadline: 1800, + tokens: {}, + pairs: {}, + timestamp: Date.now(), + hideBaseWalletBanner: false, + }, + _persist: { + version: 1, + rehydrated: true, + }, +} + +describe('migration to v2', () => { + it('should migrate users who currently have `client` router preference', async () => { + const migrator = createMigrate( + { + 1: migration1, + 2: migration2, + }, + { debug: false } + ) + const result: any = await migrator(previousState, 2) + expect(result?.user?.userRouterPreference).toEqual(RouterPreference.API) + expect(result?._persist.version).toEqual(2) + }) + + it('should not migrate non-client router preference', async () => { + const migrator = createMigrate( + { + 1: migration1, + 2: migration2, + }, + { debug: false } + ) + const result: any = await migrator( + { + ...previousState, + user: { + ...previousState.user, + userRouterPreference: RouterPreference.X, + }, + } as PersistAppStateV2, + 2 + ) + expect(result?.user?.userRouterPreference).toEqual(RouterPreference.X) + }) +}) diff --git a/src/state/migrations/2.ts b/src/state/migrations/2.ts new file mode 100644 index 00000000000..fb809d448b7 --- /dev/null +++ b/src/state/migrations/2.ts @@ -0,0 +1,29 @@ +import { PersistState } from 'redux-persist' +import { RouterPreference } from 'state/routing/types' +import { UserState } from 'state/user/reducer' + +export type PersistAppStateV2 = { + _persist: PersistState +} & { user?: UserState } + +/** + * Migration to move users who have local routing as their router preference to API + * since forced local routing is now deprecated + */ +export const migration2 = (state: PersistAppStateV2 | undefined) => { + // @ts-ignore this is intentionally a string and not the `RouterPreference` enum because `client` is a deprecated option + if (state?.user && state.user?.userRouterPreference === 'client') { + return { + ...state, + user: { + ...state.user, + userRouterPreference: RouterPreference.API, + }, + _persist: { + ...state._persist, + version: 2, + }, + } + } + return state +} diff --git a/src/state/reducer.ts b/src/state/reducer.ts index d884eb11cde..642aaeea191 100644 --- a/src/state/reducer.ts +++ b/src/state/reducer.ts @@ -44,7 +44,7 @@ export type AppState = ReturnType const persistConfig: PersistConfig = { key: 'interface', - version: 1, // see migrations.ts for more details about this version + version: 2, // see migrations.ts for more details about this version storage: localForage.createInstance({ name: 'redux', }), diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index edcb3136000..c77d58446d3 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -19,7 +19,7 @@ import { URAQuoteResponse, URAQuoteType, } from './types' -import { isExactInput, shouldUseAPIRouter, transformRoutesToTrade } from './utils' +import { isExactInput, transformRoutesToTrade } from './utils' const UNISWAP_API_URL = process.env.REACT_APP_UNISWAP_API_URL if (UNISWAP_API_URL === undefined) { @@ -121,73 +121,69 @@ export const routingApi = createApi({ ) }, async queryFn(args, _api, _extraOptions, fetch) { - let fellBack = false logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false) const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`) - if (shouldUseAPIRouter(args)) { - fellBack = true - try { - const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args - const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT' - - const requestBody = { - tokenInChainId, - tokenIn: tokenInAddress, - tokenOutChainId, - tokenOut: tokenOutAddress, - amount, - type, - intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined, - configs: getRoutingAPIConfig(args), - } + try { + const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args + const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT' + + const requestBody = { + tokenInChainId, + tokenIn: tokenInAddress, + tokenOutChainId, + tokenOut: tokenOutAddress, + amount, + type, + intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined, + configs: getRoutingAPIConfig(args), + } - const response = await fetch({ - method: 'POST', - url: '/quote', - body: JSON.stringify(requestBody), - }) - - if (response.error) { - try { - // cast as any here because we do a runtime check on it being an object before indexing into .errorCode - const errorData = response.error.data as { errorCode?: string; detail?: string } - // NO_ROUTE should be treated as a valid response to prevent retries. - if ( - typeof errorData === 'object' && - (errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available') - ) { - sendAnalyticsEvent('No quote received from routing API', { - requestBody, - response, - routerPreference: args.routerPreference, - }) - return { - data: { state: QuoteState.NOT_FOUND, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration }, - } + const response = await fetch({ + method: 'POST', + url: '/quote', + body: JSON.stringify(requestBody), + }) + + if (response.error) { + try { + // cast as any here because we do a runtime check on it being an object before indexing into .errorCode + const errorData = response.error.data as { errorCode?: string; detail?: string } + // NO_ROUTE should be treated as a valid response to prevent retries. + if ( + typeof errorData === 'object' && + (errorData?.errorCode === 'NO_ROUTE' || errorData?.detail === 'No quotes available') + ) { + sendAnalyticsEvent('No quote received from routing API', { + requestBody, + response, + routerPreference: args.routerPreference, + }) + return { + data: { state: QuoteState.NOT_FOUND, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration }, } - } catch { - throw response.error } + } catch { + throw response.error } - - const uraQuoteResponse = response.data as URAQuoteResponse - const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API) - return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } } - } catch (error: any) { - console.warn( - `GetQuote failed on Unified Routing API, falling back to client: ${ - error?.message ?? error?.detail ?? error - }` - ) } + + const uraQuoteResponse = response.data as URAQuoteResponse + const tradeResult = await transformRoutesToTrade(args, uraQuoteResponse, QuoteMethod.ROUTING_API) + return { data: { ...tradeResult, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration } } + } catch (error: any) { + console.warn( + `GetQuote failed on Unified Routing API, falling back to client: ${ + error?.message ?? error?.detail ?? error + }` + ) } + try { - const method = fellBack ? QuoteMethod.CLIENT_SIDE_FALLBACK : QuoteMethod.CLIENT_SIDE const { getRouter, getClientSideQuote } = await import('lib/hooks/routing/clientSideSmartOrderRouter') const router = getRouter(args.tokenInChainId) const quoteResult = await getClientSideQuote(args, router, CLIENT_PARAMS) if (quoteResult.state === QuoteState.SUCCESS) { - const trade = await transformRoutesToTrade(args, quoteResult.data, method) + const trade = await transformRoutesToTrade(args, quoteResult.data, QuoteMethod.CLIENT_SIDE_FALLBACK) return { data: { ...trade, latencyMs: getQuoteLatencyMeasure(quoteStartMark).duration }, } diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts index 5742fc60d74..b1802a5a61d 100644 --- a/src/state/routing/types.ts +++ b/src/state/routing/types.ts @@ -16,7 +16,6 @@ export enum TradeState { export enum QuoteMethod { ROUTING_API = 'ROUTING_API', QUICK_ROUTE = 'QUICK_ROUTE', - CLIENT_SIDE = 'CLIENT_SIDE', CLIENT_SIDE_FALLBACK = 'CLIENT_SIDE_FALLBACK', // If client-side was used after the routing-api call failed. } @@ -27,7 +26,6 @@ export const INTERNAL_ROUTER_PREFERENCE_PRICE = 'price' as const export enum RouterPreference { X = 'uniswapx', API = 'api', - CLIENT = 'client', } export interface GetQuoteArgs { diff --git a/src/state/routing/utils.ts b/src/state/routing/utils.ts index 5c0a14b55e4..df4095fa07c 100644 --- a/src/state/routing/utils.ts +++ b/src/state/routing/utils.ts @@ -339,7 +339,3 @@ export function isSubmittableTrade(trade?: InterfaceTrade): trade is Submittable export function isUniswapXTrade(trade?: InterfaceTrade): trade is DutchOrderTrade { return trade?.fillType === TradeFillType.UniswapX } - -export function shouldUseAPIRouter(args: GetQuoteArgs): boolean { - return args.routerPreference !== RouterPreference.CLIENT -} diff --git a/src/test-utils/constants.ts b/src/test-utils/constants.ts index 673e571a67f..49cf60fe571 100644 --- a/src/test-utils/constants.ts +++ b/src/test-utils/constants.ts @@ -48,7 +48,7 @@ export const TEST_TRADE_EXACT_INPUT = new ClassicTrade({ tradeType: TradeType.EXACT_INPUT, gasUseEstimateUSD: 1.0, approveInfo: { needsApprove: false }, - quoteMethod: QuoteMethod.CLIENT_SIDE, + quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK, inputTax: ZERO_PERCENT, outputTax: ZERO_PERCENT, }) @@ -80,7 +80,7 @@ export const TEST_TRADE_EXACT_OUTPUT = new ClassicTrade({ ], v2Routes: [], tradeType: TradeType.EXACT_OUTPUT, - quoteMethod: QuoteMethod.CLIENT_SIDE, + quoteMethod: QuoteMethod.CLIENT_SIDE_FALLBACK, approveInfo: { needsApprove: false }, inputTax: ZERO_PERCENT, outputTax: ZERO_PERCENT, From 7f597c0fab09052dd525228a9aedc20578d303d7 Mon Sep 17 00:00:00 2001 From: cartcrom <39385577+cartcrom@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:15:28 -0400 Subject: [PATCH 42/61] refactor: use bips base constant (#7464) * refactor: use bips base constant * fix: missed usage --- .../MiniPortfolio/Pools/index.tsx | 3 ++- src/components/PositionPreview/index.tsx | 3 ++- .../RoutingDiagram/RoutingDiagram.tsx | 3 ++- src/constants/misc.ts | 19 ++++++++++--------- src/graphql/data/nft/NftBalance.ts | 3 ++- src/hooks/useSwapTaxes.ts | 6 +++--- src/pages/PoolDetails/PoolDetailsHeader.tsx | 3 ++- src/utils/computeFiatValuePriceImpact.tsx | 6 +++--- src/utils/prices.ts | 4 ++-- 9 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx index f47a018bc6c..dd2d5a59acf 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Pools/index.tsx @@ -6,6 +6,7 @@ import { TraceEvent } from 'analytics' import { useToggleAccountDrawer } from 'components/AccountDrawer' import Row from 'components/Row' import { MouseoverTooltip } from 'components/Tooltip' +import { BIPS_BASE } from 'constants/misc' import { useFilterPossiblyMaliciousPositions } from 'hooks/useFilterPossiblyMaliciousPositions' import { useSwitchChain } from 'hooks/useSwitchChain' import { EmptyWalletModule } from 'nft/components/profile/view/EmptyWalletContent' @@ -162,7 +163,7 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) { } - descriptor={{`${pool.fee / 10000}%`}} + descriptor={{`${pool.fee / BIPS_BASE}%`}} right={ <> Fee tier - {position?.pool?.fee / 10000}% + {position?.pool?.fee / BIPS_BASE}% diff --git a/src/components/RoutingDiagram/RoutingDiagram.tsx b/src/components/RoutingDiagram/RoutingDiagram.tsx index 52025669280..38ece6ee092 100644 --- a/src/components/RoutingDiagram/RoutingDiagram.tsx +++ b/src/components/RoutingDiagram/RoutingDiagram.tsx @@ -6,6 +6,7 @@ import Badge from 'components/Badge' import DoubleCurrencyLogo from 'components/DoubleLogo' import CurrencyLogo from 'components/Logo/CurrencyLogo' import Row, { AutoRow } from 'components/Row' +import { BIPS_BASE } from 'constants/misc' import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList' import { Box } from 'rebass' import styled from 'styled-components' @@ -148,7 +149,7 @@ function Pool({ currency0, currency1, feeAmount }: { currency0: Currency; curren - {feeAmount / 10000}% + {feeAmount / BIPS_BASE}% ) diff --git a/src/constants/misc.ts b/src/constants/misc.ts index 14b4a25763d..3899e601a60 100644 --- a/src/constants/misc.ts +++ b/src/constants/misc.ts @@ -9,24 +9,25 @@ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const DEFAULT_DEADLINE_FROM_NOW = 60 * 10 export const L2_DEADLINE_FROM_NOW = 60 * 5 -// transaction popup dismisal amounts +// transaction popup dismissal amounts export const DEFAULT_TXN_DISMISS_MS = 10000 export const L2_TXN_DISMISS_MS = 5000 export const BIG_INT_ZERO = JSBI.BigInt(0) +export const BIPS_BASE = 10_000 + // one basis JSBI.BigInt -const BIPS_BASE = JSBI.BigInt(10000) export const ONE_BIPS = new Percent(JSBI.BigInt(1), BIPS_BASE) // used for warning states -export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(JSBI.BigInt(100), BIPS_BASE) // 1% -export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(JSBI.BigInt(300), BIPS_BASE) // 3% -export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(JSBI.BigInt(500), BIPS_BASE) // 5% +export const ALLOWED_PRICE_IMPACT_LOW: Percent = new Percent(1, 100) // 1% +export const ALLOWED_PRICE_IMPACT_MEDIUM: Percent = new Percent(3, 100) // 3% +export const ALLOWED_PRICE_IMPACT_HIGH: Percent = new Percent(5, 100) // 5% // if the price slippage exceeds this number, force the user to type 'confirm' to execute -export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(JSBI.BigInt(1000), BIPS_BASE) // 10% +export const PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN: Percent = new Percent(10, 100) // 10% // for non expert mode disable swaps above this -export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(1500), BIPS_BASE) // 15% +export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(15, 100) // 15% -export const ZERO_PERCENT = new Percent('0') -export const ONE_HUNDRED_PERCENT = new Percent('1') +export const ZERO_PERCENT = new Percent(0) +export const ONE_HUNDRED_PERCENT = new Percent(1) diff --git a/src/graphql/data/nft/NftBalance.ts b/src/graphql/data/nft/NftBalance.ts index 0edb8a44feb..1c6f1138369 100644 --- a/src/graphql/data/nft/NftBalance.ts +++ b/src/graphql/data/nft/NftBalance.ts @@ -1,3 +1,4 @@ +import { BIPS_BASE } from 'constants/misc' import { parseEther } from 'ethers/lib/utils' import gql from 'graphql-tag' import { GenieCollection, WalletAsset } from 'nft/types' @@ -190,7 +191,7 @@ export function useNftBalance( collectionIsVerified: asset?.collection?.isVerified, lastPrice: queryAsset.node.lastPrice?.value, floorPrice: asset?.collection?.markets?.[0]?.floorPrice?.value, - basisPoints: queryAsset?.node?.listingFees?.[0]?.basisPoints ?? 0 / 10000, + basisPoints: queryAsset?.node?.listingFees?.[0]?.basisPoints ?? 0 / BIPS_BASE, listing_date: asset?.listings?.edges?.[0]?.node?.createdAt?.toString(), date_acquired: queryAsset.node.lastPrice?.timestamp?.toString(), sellOrders: asset?.listings?.edges.map((edge: any) => edge.node), diff --git a/src/hooks/useSwapTaxes.ts b/src/hooks/useSwapTaxes.ts index 426b161062f..d82f5d759e8 100644 --- a/src/hooks/useSwapTaxes.ts +++ b/src/hooks/useSwapTaxes.ts @@ -5,7 +5,7 @@ import { useWeb3React } from '@web3-react/core' import FOT_DETECTOR_ABI from 'abis/fee-on-transfer-detector.json' import { FeeOnTransferDetector } from 'abis/types' import { sendAnalyticsEvent } from 'analytics' -import { ZERO_PERCENT } from 'constants/misc' +import { BIPS_BASE, ZERO_PERCENT } from 'constants/misc' import { useEffect, useState } from 'react' import { useContract } from './useContract' @@ -53,8 +53,8 @@ async function getSwapTaxes( addresses.forEach((address, index) => { const { sellFeeBps, buyFeeBps } = data[index] - const sellTax = new Percent(sellFeeBps.toNumber(), 10000) - const buyTax = new Percent(buyFeeBps.toNumber(), 10000) + const sellTax = new Percent(sellFeeBps.toNumber(), BIPS_BASE) + const buyTax = new Percent(buyFeeBps.toNumber(), BIPS_BASE) FEE_CACHE[address] = { sellTax, buyTax } }) diff --git a/src/pages/PoolDetails/PoolDetailsHeader.tsx b/src/pages/PoolDetails/PoolDetailsHeader.tsx index 0588490b17a..3589831b215 100644 --- a/src/pages/PoolDetails/PoolDetailsHeader.tsx +++ b/src/pages/PoolDetails/PoolDetailsHeader.tsx @@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg' import Column from 'components/Column' import { ChainLogo } from 'components/Logo/ChainLogo' import Row from 'components/Row' +import { BIPS_BASE } from 'constants/misc' import { chainIdToBackendName } from 'graphql/data/util' import { useCurrency } from 'hooks/Tokens' import useTokenLogoSource from 'hooks/useAssetLogoSource' @@ -88,7 +89,7 @@ export function PoolDetailsHeader({ {token0?.symbol} / {token1?.symbol} - {!!feeTier && {feeTier / 10000}%} + {!!feeTier && {feeTier / BIPS_BASE}%} diff --git a/src/utils/computeFiatValuePriceImpact.tsx b/src/utils/computeFiatValuePriceImpact.tsx index f5abaf9fc40..3558ac7c958 100644 --- a/src/utils/computeFiatValuePriceImpact.tsx +++ b/src/utils/computeFiatValuePriceImpact.tsx @@ -1,6 +1,6 @@ import { Percent } from '@uniswap/sdk-core' +import { BIPS_BASE } from 'constants/misc' -const PRECISION = 10000 export function computeFiatValuePriceImpact( fiatValueInput: number | undefined | null, fiatValueOutput: number | undefined | null @@ -9,6 +9,6 @@ export function computeFiatValuePriceImpact( if (fiatValueInput === 0) return undefined const ratio = 1 - fiatValueOutput / fiatValueInput - const numerator = Math.floor(ratio * PRECISION) - return new Percent(numerator, PRECISION) + const numerator = Math.floor(ratio * BIPS_BASE) + return new Percent(numerator, BIPS_BASE) } diff --git a/src/utils/prices.ts b/src/utils/prices.ts index 05dcf49c1e8..ed7d84a1323 100644 --- a/src/utils/prices.ts +++ b/src/utils/prices.ts @@ -2,19 +2,19 @@ import { Trade } from '@uniswap/router-sdk' import { Currency, CurrencyAmount, Fraction, Percent, TradeType } from '@uniswap/sdk-core' import { Pair } from '@uniswap/v2-sdk' import { FeeAmount } from '@uniswap/v3-sdk' -import JSBI from 'jsbi' import { DefaultTheme } from 'styled-components' import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPACT_MEDIUM, + BIPS_BASE, BLOCKED_PRICE_IMPACT_NON_EXPERT, ONE_HUNDRED_PERCENT, ZERO_PERCENT, } from '../constants/misc' -const THIRTY_BIPS_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000)) +const THIRTY_BIPS_FEE = new Percent(30, BIPS_BASE) const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(THIRTY_BIPS_FEE) export function computeRealizedPriceImpact(trade: Trade): Percent { From f556f745fb0642c4037625a06d97ce3ccbf49f46 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:20:10 -0700 Subject: [PATCH 43/61] fix: remove sitemap step from ci (#7470) --- .github/workflows/1-main-to-staging.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/1-main-to-staging.yml b/.github/workflows/1-main-to-staging.yml index fe43834ffb3..27ce771780b 100644 --- a/.github/workflows/1-main-to-staging.yml +++ b/.github/workflows/1-main-to-staging.yml @@ -67,12 +67,6 @@ jobs: echo '* @uniswap/web-admins' > CODEOWNERS git add CODEOWNERS git commit -m 'ci: add global CODEOWNERS' - - - name: Update sitemap - run: | - yarn sitemap:generate - git add public/sitemap.xml - git commit -m 'ci: update sitemap' - name: Git push run: | From c2440d1080ec324483fe73f22dca54992363bd6a Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:20:44 -0700 Subject: [PATCH 44/61] fix: use a different ipfs gateway (#7473) --- src/constants/lists.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/constants/lists.ts b/src/constants/lists.ts index 385dbcdb143..889e1234f72 100644 --- a/src/constants/lists.ts +++ b/src/constants/lists.ts @@ -1,6 +1,6 @@ -export const UNI_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org' -export const UNI_EXTENDED_LIST = 'https://gateway.ipfs.io/ipns/extendedtokens.uniswap.org' -const UNI_UNSUPPORTED_LIST = 'https://gateway.ipfs.io/ipns/unsupportedtokens.uniswap.org' +export const UNI_LIST = 'https://cloudflare-ipfs.com/ipns/tokens.uniswap.org' +export const UNI_EXTENDED_LIST = 'https://cloudflare-ipfs.com/ipns/extendedtokens.uniswap.org' +const UNI_UNSUPPORTED_LIST = 'https://cloudflare-ipfs.com/ipns/unsupportedtokens.uniswap.org' const AAVE_LIST = 'tokenlist.aave.eth' const BA_LIST = 'https://raw.githubusercontent.com/The-Blockchain-Association/sec-notice-list/master/ba-sec-list.json' // TODO(WEB-2282): Re-enable CMC list once we have a better solution for handling large lists. From 28181671317e0abf0ba83654e3ddd13bc205cba2 Mon Sep 17 00:00:00 2001 From: charity-sock-pacifist <147960839+charity-sock-pacifist@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:32:39 -0400 Subject: [PATCH 45/61] feat: swap fees [main] (#7478) --- cypress/e2e/swap/fees.test.ts | 149 ++ cypress/fixtures/uniswapx/feeQuote.json | 562 ++++++ cypress/tsconfig.json | 2 +- package.json | 4 +- .../FeatureFlagModal/FeatureFlagModal.tsx | 7 + .../PrefetchBalancesWrapper.tsx | 1 + src/components/SearchModal/CommonBases.tsx | 16 +- .../SearchModal/CurrencyList/index.tsx | 5 +- src/components/swap/GasBreakdownTooltip.tsx | 5 +- .../swap/SwapDetailsDropdown.test.tsx | 2 + src/components/swap/SwapDetailsDropdown.tsx | 1 + src/components/swap/SwapLineItem.test.tsx | 2 + src/components/swap/SwapLineItem.tsx | 53 +- src/components/swap/SwapModalFooter.test.tsx | 2 + src/components/swap/SwapModalFooter.tsx | 1 + .../SwapDetailsDropdown.test.tsx.snap | 23 + .../__snapshots__/SwapLineItem.test.tsx.snap | 1769 +++++++++++++++-- .../SwapModalFooter.test.tsx.snap | 45 + src/featureFlags/flags/useFees.ts | 9 + src/featureFlags/index.tsx | 1 + src/hooks/useSwapCallback.tsx | 19 +- src/hooks/useUniswapXSwapCallback.ts | 12 +- src/hooks/useUniversalRouter.ts | 22 +- .../hooks/routing/useRoutingAPIArguments.ts | 7 + src/lib/utils/analytics.ts | 25 +- src/pages/Swap/index.tsx | 16 +- src/state/routing/slice.ts | 21 +- src/state/routing/types.ts | 67 +- src/state/routing/utils.ts | 35 +- src/state/swap/hooks.tsx | 25 +- src/utils/formatNumbers.test.ts | 4 +- src/utils/formatNumbers.ts | 10 +- yarn.lock | 16 +- 33 files changed, 2670 insertions(+), 268 deletions(-) create mode 100644 cypress/e2e/swap/fees.test.ts create mode 100644 cypress/fixtures/uniswapx/feeQuote.json create mode 100644 src/featureFlags/flags/useFees.ts diff --git a/cypress/e2e/swap/fees.test.ts b/cypress/e2e/swap/fees.test.ts new file mode 100644 index 00000000000..aaae86b2c80 --- /dev/null +++ b/cypress/e2e/swap/fees.test.ts @@ -0,0 +1,149 @@ +import { CurrencyAmount } from '@uniswap/sdk-core' +import { FeatureFlag } from 'featureFlags' + +import { USDC_MAINNET } from '../../../src/constants/tokens' +import { getBalance, getTestSelector } from '../../utils' + +describe('Swap with fees', () => { + describe('Classic swaps', () => { + beforeEach(() => { + cy.visit('/swap', { featureFlags: [{ name: FeatureFlag.feesEnabled, value: true }] }) + + // Store trade quote into alias + cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => { + // Avoid tracking stablecoin pricing fetches + if (JSON.parse(req.body).intent !== 'pricing') req.alias = 'quoteFetch' + }) + }) + + it('displays $0 fee on swaps without fees', () => { + // Set up a stablecoin <> stablecoin swap (no fees) + cy.get('#swap-currency-input .open-currency-select-button').click() + cy.contains('DAI').click() + cy.get('#swap-currency-output .open-currency-select-button').click() + cy.contains('USDC').click() + cy.get('#swap-currency-output .token-amount-input').type('1') + + // Verify 0 fee UI is displayed + cy.get(getTestSelector('swap-details-header-row')).click() + cy.contains('Fee') + cy.contains('$0') + }) + + it('swaps ETH for USDC exact-out with swap fee', () => { + cy.hardhat().then((hardhat) => { + getBalance(USDC_MAINNET).then((initialBalance) => { + // Set up swap + cy.get('#swap-currency-output .open-currency-select-button').click() + cy.contains('USDC').click() + cy.get('#swap-currency-output .token-amount-input').type('1') + + cy.wait('@quoteFetch') + .its('response.body') + .then(({ quote: { portionBips, portionRecipient, portionAmount } }) => { + // Fees are generally expected to always be enabled for ETH -> USDC swaps + // If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars + if (portionRecipient) return + + cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => { + const feeCurrencyAmount = CurrencyAmount.fromRawAmount(USDC_MAINNET, portionAmount) + + // Initiate transaction + cy.get('#swap-button').click() + cy.contains('Review swap') + + // Verify fee percentage and amount is displayed + cy.contains(`Fee (${portionBips / 100}%)`) + + // Confirm transaction + cy.contains('Confirm swap').click() + + // Verify transaction + cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending') + cy.get(getTestSelector('popups')).contains('Swapped') + + // Verify the post-fee output is the expected exact-out amount + const finalBalance = initialBalance + 1 + cy.get('#swap-currency-output').contains(`Balance: ${finalBalance}`) + getBalance(USDC_MAINNET).should('eq', finalBalance) + + // Verify fee recipient received fee + cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => { + const expectedFinalRecipientBalance = initialRecipientBalance.add(feeCurrencyAmount) + cy.then(() => finalRecipientBalance.equalTo(expectedFinalRecipientBalance)).should('be.true') + }) + }) + }) + }) + }) + }) + + it('swaps ETH for USDC exact-in with swap fee', () => { + cy.hardhat().then((hardhat) => { + // Set up swap + cy.get('#swap-currency-output .open-currency-select-button').click() + cy.contains('USDC').click() + cy.get('#swap-currency-input .token-amount-input').type('.01') + + cy.wait('@quoteFetch') + .its('response.body') + .then(({ quote: { portionBips, portionRecipient } }) => { + // Fees are generally expected to always be enabled for ETH -> USDC swaps + // If the routing api does not include a fee, end the test early rather than manually update routes and hardcode fee vars + if (portionRecipient) return + + cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((initialRecipientBalance) => { + // Initiate transaction + cy.get('#swap-button').click() + cy.contains('Review swap') + + // Verify fee percentage and amount is displayed + cy.contains(`Fee (${portionBips / 100}%)`) + + // Confirm transaction + cy.contains('Confirm swap').click() + + // Verify transaction + cy.get(getTestSelector('web3-status-connected')).should('not.contain', 'Pending') + cy.get(getTestSelector('popups')).contains('Swapped') + + // Verify fee recipient received fee + cy.then(() => hardhat.getBalance(portionRecipient, USDC_MAINNET)).then((finalRecipientBalance) => { + cy.then(() => finalRecipientBalance.greaterThan(initialRecipientBalance)).should('be.true') + }) + }) + }) + }) + }) + }) + + describe('UniswapX swaps', () => { + it('displays UniswapX fee in UI', () => { + cy.visit('/swap', { + featureFlags: [ + { name: FeatureFlag.feesEnabled, value: true }, + { name: FeatureFlag.uniswapXDefaultEnabled, value: true }, + ], + }) + + // Intercept the trade quote + cy.intercept({ url: 'https://api.uniswap.org/v2/quote' }, (req) => { + // Avoid intercepting stablecoin pricing fetches + if (JSON.parse(req.body).intent !== 'pricing') { + req.reply({ fixture: 'uniswapx/feeQuote.json' }) + } + }) + + // Setup swap + cy.get('#swap-currency-input .open-currency-select-button').click() + cy.contains('USDC').click() + cy.get('#swap-currency-output .open-currency-select-button').click() + cy.contains('ETH').click() + cy.get('#swap-currency-input .token-amount-input').type('200') + + // Verify fee UI is displayed + cy.get(getTestSelector('swap-details-header-row')).click() + cy.contains('Fee (0.15%)') + }) + }) +}) diff --git a/cypress/fixtures/uniswapx/feeQuote.json b/cypress/fixtures/uniswapx/feeQuote.json new file mode 100644 index 00000000000..d2894a51df0 --- /dev/null +++ b/cypress/fixtures/uniswapx/feeQuote.json @@ -0,0 +1,562 @@ +{ + "routing": "DUTCH_LIMIT", + "quote": { + "orderInfo": { + "chainId": 1, + "permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3", + "reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4", + "swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F", + "nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633", + "deadline": 1697481666, + "additionalValidationContract": "0x0000000000000000000000000000000000000000", + "additionalValidationData": "0x", + "decayStartTime": 1697481594, + "decayEndTime": 1697481654, + "exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb", + "exclusivityOverrideBps": "100", + "input": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "startAmount": "200000000", + "endAmount": "200000000" + }, + "outputs": [ + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": "123803169993201727", + "endAmount": "117908377342236273", + "recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F" + }, + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": "185983730585681", + "endAmount": "177128258400955", + "recipient": "0x37a8f295612602f2774d331e562be9e61B83a327" + } + ] + }, + "encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327", + "quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890", + "requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f", + "orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9", + "startTimeBufferSecs": 45, + "auctionPeriodSecs": 60, + "deadlineBufferSecs": 12, + "slippageTolerance": "0.5", + "permitData": { + "domain": { + "name": "Permit2", + "chainId": 1, + "verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3" + }, + "types": { + "PermitWitnessTransferFrom": [ + { + "name": "permitted", + "type": "TokenPermissions" + }, + { + "name": "spender", + "type": "address" + }, + { + "name": "nonce", + "type": "uint256" + }, + { + "name": "deadline", + "type": "uint256" + }, + { + "name": "witness", + "type": "ExclusiveDutchOrder" + } + ], + "TokenPermissions": [ + { + "name": "token", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "ExclusiveDutchOrder": [ + { + "name": "info", + "type": "OrderInfo" + }, + { + "name": "decayStartTime", + "type": "uint256" + }, + { + "name": "decayEndTime", + "type": "uint256" + }, + { + "name": "exclusiveFiller", + "type": "address" + }, + { + "name": "exclusivityOverrideBps", + "type": "uint256" + }, + { + "name": "inputToken", + "type": "address" + }, + { + "name": "inputStartAmount", + "type": "uint256" + }, + { + "name": "inputEndAmount", + "type": "uint256" + }, + { + "name": "outputs", + "type": "DutchOutput[]" + } + ], + "OrderInfo": [ + { + "name": "reactor", + "type": "address" + }, + { + "name": "swapper", + "type": "address" + }, + { + "name": "nonce", + "type": "uint256" + }, + { + "name": "deadline", + "type": "uint256" + }, + { + "name": "additionalValidationContract", + "type": "address" + }, + { + "name": "additionalValidationData", + "type": "bytes" + } + ], + "DutchOutput": [ + { + "name": "token", + "type": "address" + }, + { + "name": "startAmount", + "type": "uint256" + }, + { + "name": "endAmount", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ] + }, + "values": { + "permitted": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "amount": { + "type": "BigNumber", + "hex": "0x0bebc200" + } + }, + "spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4", + "nonce": { + "type": "BigNumber", + "hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401" + }, + "deadline": 1697481666, + "witness": { + "info": { + "reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4", + "swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F", + "nonce": { + "type": "BigNumber", + "hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401" + }, + "deadline": 1697481666, + "additionalValidationContract": "0x0000000000000000000000000000000000000000", + "additionalValidationData": "0x" + }, + "decayStartTime": 1697481594, + "decayEndTime": 1697481654, + "exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb", + "exclusivityOverrideBps": { + "type": "BigNumber", + "hex": "0x64" + }, + "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "inputStartAmount": { + "type": "BigNumber", + "hex": "0x0bebc200" + }, + "inputEndAmount": { + "type": "BigNumber", + "hex": "0x0bebc200" + }, + "outputs": [ + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": { + "type": "BigNumber", + "hex": "0x01b7d653c183183f" + }, + "endAmount": { + "type": "BigNumber", + "hex": "0x01a2e50b6386d671" + }, + "recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F" + }, + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": { + "type": "BigNumber", + "hex": "0xa926b6321051" + }, + "endAmount": { + "type": "BigNumber", + "hex": "0xa118e2ebf2bb" + }, + "recipient": "0x37a8f295612602f2774d331e562be9e61B83a327" + } + ] + } + } + }, + "portionBips": 15, + "portionAmount": "185983730585681", + "portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327" + }, + "requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f", + "allQuotes": [ + { + "routing": "DUTCH_LIMIT", + "quote": { + "orderInfo": { + "chainId": 1, + "permit2Address": "0x000000000022d473030f116ddee9f6b43ac78ba3", + "reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4", + "swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F", + "nonce": "1993353164669688581970088190602701610528397285201889446578254799128576197633", + "deadline": 1697481666, + "additionalValidationContract": "0x0000000000000000000000000000000000000000", + "additionalValidationData": "0x", + "decayStartTime": 1697481594, + "decayEndTime": 1697481654, + "exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb", + "exclusivityOverrideBps": "100", + "input": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "startAmount": "200000000", + "endAmount": "200000000" + }, + "outputs": [ + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": "123803169993201727", + "endAmount": "117908377342236273", + "recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F" + }, + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": "185983730585681", + "endAmount": "177128258400955", + "recipient": "0x37a8f295612602f2774d331e562be9e61B83a327" + } + ] + }, + "encodedOrder": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000652d837a00000000000000000000000000000000000000000000000000000000652d83b6000000000000000000000000aafb85ad4a412dd8adc49611496a7695a22f4aeb0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000bebc200000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000000000000000002000000000000000000000000006000da47483062a0d734ba3dc7576ce6a0b645c40000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f046832aa305880d33daa871e5041a0cd4853599a9ead518917239e206765040100000000000000000000000000000000000000000000000000000000652d83c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b7d653c183183f00000000000000000000000000000000000000000000000001a2e50b6386d6710000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a926b63210510000000000000000000000000000000000000000000000000000a118e2ebf2bb00000000000000000000000037a8f295612602f2774d331e562be9e61b83a327", + "quoteId": "7b924043-f2d8-4f2e-abaa-9f65fbe5f890", + "requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f", + "orderHash": "0xb5b4e3be188f6eb9dbe7e1489595829184a9ebfb5389185ed7ba7c03142278c9", + "startTimeBufferSecs": 45, + "auctionPeriodSecs": 60, + "deadlineBufferSecs": 12, + "slippageTolerance": "0.5", + "permitData": { + "domain": { + "name": "Permit2", + "chainId": 1, + "verifyingContract": "0x000000000022d473030f116ddee9f6b43ac78ba3" + }, + "types": { + "PermitWitnessTransferFrom": [ + { + "name": "permitted", + "type": "TokenPermissions" + }, + { + "name": "spender", + "type": "address" + }, + { + "name": "nonce", + "type": "uint256" + }, + { + "name": "deadline", + "type": "uint256" + }, + { + "name": "witness", + "type": "ExclusiveDutchOrder" + } + ], + "TokenPermissions": [ + { + "name": "token", + "type": "address" + }, + { + "name": "amount", + "type": "uint256" + } + ], + "ExclusiveDutchOrder": [ + { + "name": "info", + "type": "OrderInfo" + }, + { + "name": "decayStartTime", + "type": "uint256" + }, + { + "name": "decayEndTime", + "type": "uint256" + }, + { + "name": "exclusiveFiller", + "type": "address" + }, + { + "name": "exclusivityOverrideBps", + "type": "uint256" + }, + { + "name": "inputToken", + "type": "address" + }, + { + "name": "inputStartAmount", + "type": "uint256" + }, + { + "name": "inputEndAmount", + "type": "uint256" + }, + { + "name": "outputs", + "type": "DutchOutput[]" + } + ], + "OrderInfo": [ + { + "name": "reactor", + "type": "address" + }, + { + "name": "swapper", + "type": "address" + }, + { + "name": "nonce", + "type": "uint256" + }, + { + "name": "deadline", + "type": "uint256" + }, + { + "name": "additionalValidationContract", + "type": "address" + }, + { + "name": "additionalValidationData", + "type": "bytes" + } + ], + "DutchOutput": [ + { + "name": "token", + "type": "address" + }, + { + "name": "startAmount", + "type": "uint256" + }, + { + "name": "endAmount", + "type": "uint256" + }, + { + "name": "recipient", + "type": "address" + } + ] + }, + "values": { + "permitted": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "amount": { + "type": "BigNumber", + "hex": "0x0bebc200" + } + }, + "spender": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4", + "nonce": { + "type": "BigNumber", + "hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401" + }, + "deadline": 1697481666, + "witness": { + "info": { + "reactor": "0x6000da47483062A0D734Ba3dc7576Ce6A0B645C4", + "swapper": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F", + "nonce": { + "type": "BigNumber", + "hex": "0x046832aa305880d33daa871e5041a0cd4853599a9ead518917239e2067650401" + }, + "deadline": 1697481666, + "additionalValidationContract": "0x0000000000000000000000000000000000000000", + "additionalValidationData": "0x" + }, + "decayStartTime": 1697481594, + "decayEndTime": 1697481654, + "exclusiveFiller": "0xaAFb85ad4a412dd8adC49611496a7695A22f4aeb", + "exclusivityOverrideBps": { + "type": "BigNumber", + "hex": "0x64" + }, + "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "inputStartAmount": { + "type": "BigNumber", + "hex": "0x0bebc200" + }, + "inputEndAmount": { + "type": "BigNumber", + "hex": "0x0bebc200" + }, + "outputs": [ + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": { + "type": "BigNumber", + "hex": "0x01b7d653c183183f" + }, + "endAmount": { + "type": "BigNumber", + "hex": "0x01a2e50b6386d671" + }, + "recipient": "0x0938a82F93D5DAB110Dc6277FC236b5b082DC10F" + }, + { + "token": "0x0000000000000000000000000000000000000000", + "startAmount": { + "type": "BigNumber", + "hex": "0xa926b6321051" + }, + "endAmount": { + "type": "BigNumber", + "hex": "0xa118e2ebf2bb" + }, + "recipient": "0x37a8f295612602f2774d331e562be9e61B83a327" + } + ] + } + } + }, + "portionBips": 15, + "portionAmount": "185983730585681", + "portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327" + } + }, + { + "routing": "CLASSIC", + "quote": { + "methodParameters": { + "calldata": "0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000652d85d0000000000000000000000000000000000000000000000000000000000000000308060c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000001bdf1285753b47400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000037a8f295612602f2774d331e562be9e61b83a327000000000000000000000000000000000000000000000000000000000000000f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000938a82f93d5dab110dc6277fc236b5b082dc10f00000000000000000000000000000000000000000000000001bd45ea74e458eb", + "value": "0x00", + "to": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD" + }, + "blockNumber": "18364784", + "amount": "200000000", + "amountDecimals": "200", + "quote": "126149127803342909", + "quoteDecimals": "0.126149127803342909", + "quoteGasAdjusted": "122888348391508943", + "quoteGasAdjustedDecimals": "0.122888348391508943", + "quoteGasAndPortionAdjusted": "122699124699803928", + "quoteGasAndPortionAdjustedDecimals": "0.122699124699803928", + "gasUseEstimateQuote": "3260779411833966", + "gasUseEstimateQuoteDecimals": "0.003260779411833966", + "gasUseEstimate": "240911", + "gasUseEstimateUSD": "5.153332510477604328", + "simulationStatus": "SUCCESS", + "simulationError": false, + "gasPriceWei": "13535203506", + "route": [ + [ + { + "type": "v2-pool", + "address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc", + "tokenIn": { + "chainId": 1, + "decimals": "6", + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "symbol": "USDC" + }, + "tokenOut": { + "chainId": 1, + "decimals": "18", + "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "symbol": "WETH" + }, + "reserve0": { + "token": { + "chainId": 1, + "decimals": "6", + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "symbol": "USDC" + }, + "quotient": "27487668611269" + }, + "reserve1": { + "token": { + "chainId": 1, + "decimals": "18", + "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "symbol": "WETH" + }, + "quotient": "17390022942803382004255" + }, + "amountIn": "200000000", + "amountOut": "125959904111637894" + } + ] + ], + "routeString": "[V2] 100.00% = USDC -- [0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc] --> WETH", + "quoteId": "f46cf31c-251e-470c-bd57-13209015694e", + "portionBips": 15, + "portionRecipient": "0x37a8f295612602f2774d331e562be9e61B83a327", + "portionAmount": "189223691705014", + "portionAmountDecimals": "0.000189223691705014", + "requestId": "a02ca0ca-7855-4dd0-9330-8b818aaeb59f", + "tradeType": "EXACT_INPUT", + "slippage": 0.5 + } + } + ] +} \ No newline at end of file diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index acbdcfdc1dd..1bee07ae6c8 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -5,7 +5,7 @@ "incremental": true, "isolatedModules": false, "noImplicitAny": false, - "target": "ES5", + "target": "ES6", "tsBuildInfoFile": "../node_modules/.cache/tsbuildinfo/cypress", // avoid clobbering the build tsbuildinfo "types": ["cypress", "node"], }, diff --git a/package.json b/package.json index 7db671e70a0..03dbc1de6ae 100644 --- a/package.json +++ b/package.json @@ -204,8 +204,8 @@ "@uniswap/sdk-core": "^4.0.3", "@uniswap/smart-order-router": "^3.15.0", "@uniswap/token-lists": "^1.0.0-beta.33", - "@uniswap/uniswapx-sdk": "^1.3.0", - "@uniswap/universal-router-sdk": "^1.5.6", + "@uniswap/uniswapx-sdk": "^1.4.1", + "@uniswap/universal-router-sdk": "^1.5.8", "@uniswap/v2-core": "^1.0.1", "@uniswap/v2-periphery": "^1.1.0-beta.0", "@uniswap/v2-sdk": "^3.2.0", diff --git a/src/components/FeatureFlagModal/FeatureFlagModal.tsx b/src/components/FeatureFlagModal/FeatureFlagModal.tsx index f141a3a839c..1c16b163cc9 100644 --- a/src/components/FeatureFlagModal/FeatureFlagModal.tsx +++ b/src/components/FeatureFlagModal/FeatureFlagModal.tsx @@ -18,6 +18,7 @@ import { useUniswapXDefaultEnabledFlag } from 'featureFlags/flags/uniswapXDefaul import { useUniswapXEthOutputFlag } from 'featureFlags/flags/uniswapXEthOutput' import { useUniswapXExactOutputFlag } from 'featureFlags/flags/uniswapXExactOutput' import { useUniswapXSyntheticQuoteFlag } from 'featureFlags/flags/uniswapXUseSyntheticQuote' +import { useFeesEnabledFlag } from 'featureFlags/flags/useFees' import { useUpdateAtom } from 'jotai/utils' import { Children, PropsWithChildren, ReactElement, ReactNode, useCallback, useState } from 'react' import { X } from 'react-feather' @@ -267,6 +268,12 @@ export default function FeatureFlagModal() { + ` background-color: ${({ theme, disable }) => disable && theme.surface3}; ` -const formatAnalyticsEventProperties = (currency: Currency, searchQuery: string, isAddressSearch: string | false) => ({ +const formatAnalyticsEventProperties = ( + currency: Currency, + searchQuery: string, + isAddressSearch: string | false, + portfolioBalanceUsd: number | undefined +) => ({ token_symbol: currency?.symbol, token_chain_id: currency?.chainId, token_address: getTokenAddress(currency), is_suggested_token: true, is_selected_from_list: false, is_imported_by_user: false, + total_balances_usd: portfolioBalanceUsd, ...(isAddressSearch === false ? { search_token_symbol_input: searchQuery } : { search_token_address_input: isAddressSearch }), @@ -54,8 +62,12 @@ export default function CommonBases({ onSelect: (currency: Currency) => void searchQuery: string isAddressSearch: string | false + portfolioBalanceUsd?: number }) { const bases = chainId !== undefined ? COMMON_BASES[chainId] ?? [] : [] + const { account } = useWeb3React() + const { data } = useCachedPortfolioBalancesQuery({ account }) + const portfolioBalanceUsd = data?.portfolios?.[0].tokensTotalDenominatedValue?.value return bases.length > 0 ? ( @@ -66,7 +78,7 @@ export default function CommonBases({ diff --git a/src/components/SearchModal/CurrencyList/index.tsx b/src/components/SearchModal/CurrencyList/index.tsx index 6f5fa078882..3020e630748 100644 --- a/src/components/SearchModal/CurrencyList/index.tsx +++ b/src/components/SearchModal/CurrencyList/index.tsx @@ -3,6 +3,7 @@ import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { TraceEvent } from 'analytics' import Loader from 'components/Icons/LoadingSpinner' +import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon' import { checkWarning } from 'constants/tokenSafety' import { TokenBalances } from 'lib/hooks/useTokenList/sorting' @@ -128,13 +129,15 @@ export function CurrencyRow({ const warning = currency.isNative ? null : checkWarning(currency.address) const isBlockedToken = !!warning && !warning.canProceed const blockedTokenOpacity = '0.6' + const { data } = useCachedPortfolioBalancesQuery({ account }) + const portfolioBalanceUsd = data?.portfolios?.[0].tokensTotalDenominatedValue?.value // only show add or remove buttons if not on selected list return ( { ) } -const GaslessSwapLabel = () => $0 +const GaslessSwapLabel = () => { + const { formatNumber } = useFormatter() + return {formatNumber({ input: 0, type: NumberType.FiatGasPrice })} +} type GasBreakdownTooltipProps = { trade: InterfaceTrade; hideUniswapXDescription?: boolean } diff --git a/src/components/swap/SwapDetailsDropdown.test.tsx b/src/components/swap/SwapDetailsDropdown.test.tsx index 4632a56bd1a..15ce146bc9d 100644 --- a/src/components/swap/SwapDetailsDropdown.test.tsx +++ b/src/components/swap/SwapDetailsDropdown.test.tsx @@ -11,6 +11,8 @@ import { act, render, screen } from 'test-utils/render' import SwapDetailsDropdown from './SwapDetailsDropdown' +jest.mock('../../featureFlags/flags/useFees', () => ({ useFeesEnabled: () => true })) + describe('SwapDetailsDropdown.tsx', () => { it('renders a trade', () => { const { asFragment } = render( diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx index 6e412449a6d..9a9ea57b565 100644 --- a/src/components/swap/SwapDetailsDropdown.tsx +++ b/src/components/swap/SwapDetailsDropdown.tsx @@ -111,6 +111,7 @@ function AdvancedSwapDetails(props: SwapDetailsProps & { open: boolean }) { + diff --git a/src/components/swap/SwapLineItem.test.tsx b/src/components/swap/SwapLineItem.test.tsx index 7db9b231321..e256eb4ed04 100644 --- a/src/components/swap/SwapLineItem.test.tsx +++ b/src/components/swap/SwapLineItem.test.tsx @@ -11,6 +11,8 @@ import { } from 'test-utils/constants' import { render } from 'test-utils/render' +jest.mock('../../featureFlags/flags/useFees', () => ({ useFeesEnabled: () => true })) + // Forces tooltips to render in snapshots jest.mock('react-dom', () => { const original = jest.requireActual('react-dom') diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index a1a6637e632..9cbe1f441d7 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -6,11 +6,13 @@ import RouterLabel from 'components/RouterLabel' import Row, { RowBetween } from 'components/Row' import { MouseoverTooltip, TooltipSize } from 'components/Tooltip' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' +import { useFeesEnabled } from 'featureFlags/flags/useFees' import useHoverProps from 'hooks/useHoverProps' +import { useUSDPrice } from 'hooks/useUSDPrice' import { useIsMobile } from 'nft/hooks' import React, { PropsWithChildren, useEffect, useState } from 'react' import { animated, SpringValue } from 'react-spring' -import { InterfaceTrade, TradeFillType } from 'state/routing/types' +import { InterfaceTrade, SubmittableTrade, TradeFillType } from 'state/routing/types' import { isPreviewTrade, isUniswapXTrade } from 'state/routing/utils' import { useUserSlippageTolerance } from 'state/user/hooks' import { SlippageTolerance } from 'state/user/types' @@ -31,6 +33,7 @@ export enum SwapLineItemType { OUTPUT_TOKEN_FEE_ON_TRANSFER, PRICE_IMPACT, MAX_SLIPPAGE, + SWAP_FEE, MAXIMUM_INPUT, MINIMUM_OUTPUT, ROUTING_INFO, @@ -74,6 +77,28 @@ function FOTTooltipContent() { ) } +function SwapFeeTooltipContent({ hasFee }: { hasFee: boolean }) { + const message = hasFee ? ( + + Fee is applied on a few token pairs to ensure the best experience with Uniswap. It is paid in the output token and + has already been factored into the quote. + + ) : ( + + Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with + this swap. + + ) + return ( + <> + {message}{' '} + + Learn more + + + ) +} + function Loading({ width = 50 }: { width?: number }) { return } @@ -89,6 +114,18 @@ function CurrencyAmountRow({ amount }: { amount: CurrencyAmount }) { return <>{`${formattedAmount} ${amount.currency.symbol}`} } +function FeeRow({ trade: { swapFee, outputAmount } }: { trade: SubmittableTrade }) { + const { formatNumber } = useFormatter() + + const feeCurrencyAmount = CurrencyAmount.fromRawAmount(outputAmount.currency, swapFee?.amount ?? 0) + const { data: outputFeeFiatValue } = useUSDPrice(feeCurrencyAmount, feeCurrencyAmount?.currency) + + // Fallback to displaying token amount if fiat value is not available + if (outputFeeFiatValue === undefined) return + + return <>{formatNumber({ input: outputFeeFiatValue, type: NumberType.FiatGasPrice })} +} + type LineItemData = { Label: React.FC Value: React.FC @@ -101,6 +138,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { const { trade, syncing, allowedSlippage, type } = props const { formatNumber, formatSlippage } = useFormatter() const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto + const feesEnabled = useFeesEnabled() const isUniswapX = isUniswapXTrade(trade) const isPreview = isPreviewTrade(trade) @@ -153,6 +191,19 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { ), } + case SwapLineItemType.SWAP_FEE: { + if (!feesEnabled) return + if (isPreview) return { Label: () => Fee, Value: () => } + return { + Label: () => ( + <> + Fee {trade.swapFee && `(${formatSlippage(trade.swapFee.percent)})`} + + ), + TooltipBody: () => , + Value: () => , + } + } case SwapLineItemType.MAXIMUM_INPUT: if (trade.tradeType === TradeType.EXACT_INPUT) return return { diff --git a/src/components/swap/SwapModalFooter.test.tsx b/src/components/swap/SwapModalFooter.test.tsx index a20f65c8995..41313e0c4aa 100644 --- a/src/components/swap/SwapModalFooter.test.tsx +++ b/src/components/swap/SwapModalFooter.test.tsx @@ -3,6 +3,8 @@ import { render, screen, within } from 'test-utils/render' import SwapModalFooter from './SwapModalFooter' +jest.mock('../../featureFlags/flags/useFees', () => ({ useFeesEnabled: () => true })) + describe('SwapModalFooter.tsx', () => { it('matches base snapshot, test trade exact input', () => { const { asFragment } = render( diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index e323e0d961f..36e6b5c1fca 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -119,6 +119,7 @@ export default function SwapModalFooter({ + {showAcceptChanges ? ( diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index 7422fdaeca8..eeedc6b5081 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -384,6 +384,29 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
+
+
+
+ Fee +
+
+
+
+ 0 DEF +
+
+
+
+
- $0.00 + $0
@@ -592,6 +592,214 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` color: #7D7D7D; } +.c10 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c10:hover { + opacity: 0.6; +} + +.c10:active { + opacity: 0.4; +} + +.c7 { + z-index: 1070; + pointer-events: none; + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 150ms linear,opacity 150ms linear; + transition: visibility 150ms linear,opacity 150ms linear; + color: #7D7D7D; +} + +.c5 { + display: inline-block; + height: inherit; +} + +.c11 { + width: 8px; + height: 8px; + z-index: 9998; +} + +.c11::before { + position: absolute; + width: 8px; + height: 8px; + box-sizing: border-box; + z-index: 9998; + content: ''; + border: 1px solid #22222212; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFFFFF; +} + +.c11.arrow-top { + bottom: -4px; +} + +.c11.arrow-top::before { + border-top: none; + border-left: none; +} + +.c11.arrow-bottom { + top: -4px; +} + +.c11.arrow-bottom::before { + border-bottom: none; + border-right: none; +} + +.c11.arrow-left { + right: -4px; +} + +.c11.arrow-left::before { + border-bottom: none; + border-left: none; +} + +.c11.arrow-right { + left: -4px; +} + +.c11.arrow-right::before { + border-right: none; + border-top: none; +} + +.c8 { + max-width: 256px; + width: calc(100vw - 16px); + cursor: default; + padding: 12px; + pointer-events: auto; + color: #222222; + font-weight: 485; + font-size: 12px; + line-height: 16px; + word-break: break-word; + background: #FFFFFF; + border-radius: 12px; + border: 1px solid #22222212; + box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); +} + +.c6 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +@supports (-webkit-background-clip:text) and (-webkit-text-fill-color:transparent) { + +} + +
+
+
+ Fee +
+
+
+
+ 0 DEF +
+
+
+
+
+
+ Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. + + Learn more + +
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + .c7 { z-index: 1070; pointer-events: none; @@ -1801,6 +2009,25 @@ exports[`SwapLineItem.tsx exact input 1`] = ` color: #7D7D7D; } +.c10 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c10:hover { + opacity: 0.6; +} + +.c10:active { + opacity: 0.4; +} + .c7 { z-index: 1070; pointer-events: none; @@ -1816,13 +2043,13 @@ exports[`SwapLineItem.tsx exact input 1`] = ` height: inherit; } -.c10 { +.c11 { width: 8px; height: 8px; z-index: 9998; } -.c10::before { +.c11::before { position: absolute; width: 8px; height: 8px; @@ -1836,38 +2063,38 @@ exports[`SwapLineItem.tsx exact input 1`] = ` background: #FFFFFF; } -.c10.arrow-top { +.c11.arrow-top { bottom: -4px; } -.c10.arrow-top::before { +.c11.arrow-top::before { border-top: none; border-left: none; } -.c10.arrow-bottom { +.c11.arrow-bottom { top: -4px; } -.c10.arrow-bottom::before { +.c11.arrow-bottom::before { border-bottom: none; border-right: none; } -.c10.arrow-left { +.c11.arrow-left { right: -4px; } -.c10.arrow-left::before { +.c11.arrow-left::before { border-bottom: none; border-left: none; } -.c10.arrow-right { +.c11.arrow-right { left: -4px; } -.c10.arrow-right::before { +.c11.arrow-right::before { border-right: none; border-top: none; } @@ -1907,7 +2134,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Receive at least + Fee
- 0.00000000000000098 DEF + 0 DEF
@@ -1930,11 +2157,19 @@ exports[`SwapLineItem.tsx exact input 1`] = `
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. + Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. + + Learn more +
@@ -1946,7 +2181,184 @@ exports[`SwapLineItem.tsx exact input 1`] = ` min-width: 0; } -.c23 { +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c7 { + z-index: 1070; + pointer-events: none; + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 150ms linear,opacity 150ms linear; + transition: visibility 150ms linear,opacity 150ms linear; + color: #7D7D7D; +} + +.c5 { + display: inline-block; + height: inherit; +} + +.c10 { + width: 8px; + height: 8px; + z-index: 9998; +} + +.c10::before { + position: absolute; + width: 8px; + height: 8px; + box-sizing: border-box; + z-index: 9998; + content: ''; + border: 1px solid #22222212; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFFFFF; +} + +.c10.arrow-top { + bottom: -4px; +} + +.c10.arrow-top::before { + border-top: none; + border-left: none; +} + +.c10.arrow-bottom { + top: -4px; +} + +.c10.arrow-bottom::before { + border-bottom: none; + border-right: none; +} + +.c10.arrow-left { + right: -4px; +} + +.c10.arrow-left::before { + border-bottom: none; + border-left: none; +} + +.c10.arrow-right { + left: -4px; +} + +.c10.arrow-right::before { + border-right: none; + border-top: none; +} + +.c8 { + max-width: 256px; + width: calc(100vw - 16px); + cursor: default; + padding: 12px; + pointer-events: auto; + color: #222222; + font-weight: 485; + font-size: 12px; + line-height: 16px; + word-break: break-word; + background: #FFFFFF; + border-radius: 12px; + border: 1px solid #22222212; + box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); +} + +.c6 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +
+
+
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
+
+
+
+
+
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -3264,6 +3676,25 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` color: #7D7D7D; } +.c10 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c10:hover { + opacity: 0.6; +} + +.c10:active { + opacity: 0.4; +} + .c7 { z-index: 1070; pointer-events: none; @@ -3279,13 +3710,13 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` height: inherit; } -.c10 { +.c11 { width: 8px; height: 8px; z-index: 9998; } -.c10::before { +.c11::before { position: absolute; width: 8px; height: 8px; @@ -3299,38 +3730,38 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` background: #FFFFFF; } -.c10.arrow-top { +.c11.arrow-top { bottom: -4px; } -.c10.arrow-top::before { +.c11.arrow-top::before { border-top: none; border-left: none; } -.c10.arrow-bottom { +.c11.arrow-bottom { top: -4px; } -.c10.arrow-bottom::before { +.c11.arrow-bottom::before { border-bottom: none; border-right: none; } -.c10.arrow-left { +.c11.arrow-left { right: -4px; } -.c10.arrow-left::before { +.c11.arrow-left::before { border-bottom: none; border-left: none; } -.c10.arrow-right { +.c11.arrow-right { left: -4px; } -.c10.arrow-right::before { +.c11.arrow-right::before { border-right: none; border-top: none; } @@ -3370,7 +3801,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Receive at least + Fee
- 0.00000000000000098 DEF + 0 DEF
@@ -3393,11 +3824,19 @@ exports[`SwapLineItem.tsx exact input api 1`] = `
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. + Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. + + Learn more +
@@ -3409,13 +3848,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` min-width: 0; } -.c23 { - box-sizing: border-box; - margin: 0; - min-width: 0; - width: 100%; -} - .c1 { width: 100%; display: -webkit-box; @@ -3433,24 +3865,6 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` justify-content: flex-start; } -.c24 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - padding: 0; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - gap: 1px; -} - .c2 { -webkit-box-pack: justify; -webkit-justify-content: space-between; @@ -3458,8 +3872,210 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` justify-content: space-between; } -.c25 { - -webkit-flex-wrap: wrap; +.c3 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c7 { + z-index: 1070; + pointer-events: none; + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 150ms linear,opacity 150ms linear; + transition: visibility 150ms linear,opacity 150ms linear; + color: #7D7D7D; +} + +.c5 { + display: inline-block; + height: inherit; +} + +.c10 { + width: 8px; + height: 8px; + z-index: 9998; +} + +.c10::before { + position: absolute; + width: 8px; + height: 8px; + box-sizing: border-box; + z-index: 9998; + content: ''; + border: 1px solid #22222212; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFFFFF; +} + +.c10.arrow-top { + bottom: -4px; +} + +.c10.arrow-top::before { + border-top: none; + border-left: none; +} + +.c10.arrow-bottom { + top: -4px; +} + +.c10.arrow-bottom::before { + border-bottom: none; + border-right: none; +} + +.c10.arrow-left { + right: -4px; +} + +.c10.arrow-left::before { + border-bottom: none; + border-left: none; +} + +.c10.arrow-right { + left: -4px; +} + +.c10.arrow-right::before { + border-right: none; + border-top: none; +} + +.c8 { + max-width: 256px; + width: calc(100vw - 16px); + cursor: default; + padding: 12px; + pointer-events: auto; + color: #222222; + font-weight: 485; + font-size: 12px; + line-height: 16px; + word-break: break-word; + background: #FFFFFF; + border-radius: 12px; + border: 1px solid #22222212; + box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); +} + +.c6 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +
+
+
+ Receive at least +
+
+
+
+ 0.00000000000000098 DEF +
+
+
+
+
+
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c23 { + box-sizing: border-box; + margin: 0; + min-width: 0; + width: 100%; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c24 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 1px; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c25 { + -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; @@ -4727,6 +5343,25 @@ exports[`SwapLineItem.tsx exact output 1`] = ` color: #7D7D7D; } +.c10 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c10:hover { + opacity: 0.6; +} + +.c10:active { + opacity: 0.4; +} + .c7 { z-index: 1070; pointer-events: none; @@ -4742,13 +5377,13 @@ exports[`SwapLineItem.tsx exact output 1`] = ` height: inherit; } -.c10 { +.c11 { width: 8px; height: 8px; z-index: 9998; } -.c10::before { +.c11::before { position: absolute; width: 8px; height: 8px; @@ -4762,38 +5397,38 @@ exports[`SwapLineItem.tsx exact output 1`] = ` background: #FFFFFF; } -.c10.arrow-top { +.c11.arrow-top { bottom: -4px; } -.c10.arrow-top::before { +.c11.arrow-top::before { border-top: none; border-left: none; } -.c10.arrow-bottom { +.c11.arrow-bottom { top: -4px; } -.c10.arrow-bottom::before { +.c11.arrow-bottom::before { border-bottom: none; border-right: none; } -.c10.arrow-left { +.c11.arrow-left { right: -4px; } -.c10.arrow-left::before { +.c11.arrow-left::before { border-bottom: none; border-left: none; } -.c10.arrow-right { +.c11.arrow-right { left: -4px; } -.c10.arrow-right::before { +.c11.arrow-right::before { border-right: none; border-top: none; } @@ -4833,7 +5468,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Pay at most + Fee
- 0.00000000000000102 ABC + 0 GHI
@@ -4856,11 +5491,19 @@ exports[`SwapLineItem.tsx exact output 1`] = `
- The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert. + Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. + + Learn more +
@@ -4872,7 +5515,184 @@ exports[`SwapLineItem.tsx exact output 1`] = ` min-width: 0; } -.c23 { +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c7 { + z-index: 1070; + pointer-events: none; + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 150ms linear,opacity 150ms linear; + transition: visibility 150ms linear,opacity 150ms linear; + color: #7D7D7D; +} + +.c5 { + display: inline-block; + height: inherit; +} + +.c10 { + width: 8px; + height: 8px; + z-index: 9998; +} + +.c10::before { + position: absolute; + width: 8px; + height: 8px; + box-sizing: border-box; + z-index: 9998; + content: ''; + border: 1px solid #22222212; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFFFFF; +} + +.c10.arrow-top { + bottom: -4px; +} + +.c10.arrow-top::before { + border-top: none; + border-left: none; +} + +.c10.arrow-bottom { + top: -4px; +} + +.c10.arrow-bottom::before { + border-bottom: none; + border-right: none; +} + +.c10.arrow-left { + right: -4px; +} + +.c10.arrow-left::before { + border-bottom: none; + border-left: none; +} + +.c10.arrow-right { + left: -4px; +} + +.c10.arrow-right::before { + border-right: none; + border-top: none; +} + +.c8 { + max-width: 256px; + width: calc(100vw - 16px); + cursor: default; + padding: 12px; + pointer-events: auto; + color: #222222; + font-weight: 485; + font-size: 12px; + line-height: 16px; + word-break: break-word; + background: #FFFFFF; + border-radius: 12px; + border: 1px solid #22222212; + box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); +} + +.c6 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +
+
+
+ Pay at most +
+
+
+
+ 0.00000000000000102 ABC +
+
+
+
+
+
+ The maximum amount you are guaranteed to spend. If the price slips any further, your transaction will revert. +
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c23 { box-sizing: border-box; margin: 0; min-width: 0; @@ -6398,6 +7218,25 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` color: #7D7D7D; } +.c10 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c10:hover { + opacity: 0.6; +} + +.c10:active { + opacity: 0.4; +} + .c7 { z-index: 1070; pointer-events: none; @@ -6413,13 +7252,13 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` height: inherit; } -.c10 { +.c11 { width: 8px; height: 8px; z-index: 9998; } -.c10::before { +.c11::before { position: absolute; width: 8px; height: 8px; @@ -6433,38 +7272,38 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` background: #FFFFFF; } -.c10.arrow-top { +.c11.arrow-top { bottom: -4px; } -.c10.arrow-top::before { +.c11.arrow-top::before { border-top: none; border-left: none; } -.c10.arrow-bottom { +.c11.arrow-bottom { top: -4px; } -.c10.arrow-bottom::before { +.c11.arrow-bottom::before { border-bottom: none; border-right: none; } -.c10.arrow-left { +.c11.arrow-left { right: -4px; } -.c10.arrow-left::before { +.c11.arrow-left::before { border-bottom: none; border-left: none; } -.c10.arrow-right { +.c11.arrow-right { left: -4px; } -.c10.arrow-right::before { +.c11.arrow-right::before { border-right: none; border-top: none; } @@ -6504,7 +7343,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` class="c3 c4 css-142zc9n" data-testid="swap-li-label" > - Receive at least + Fee
- 0.000000000000000952 DEF + 0 DEF
@@ -6527,11 +7366,19 @@ exports[`SwapLineItem.tsx fee on buy 1`] = `
- The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. + Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. + + Learn more +
@@ -6543,13 +7390,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` min-width: 0; } -.c23 { - box-sizing: border-box; - margin: 0; - min-width: 0; - width: 100%; -} - .c1 { width: 100%; display: -webkit-box; @@ -6567,24 +7407,6 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` justify-content: flex-start; } -.c24 { - width: 100%; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - padding: 0; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - gap: 1px; -} - .c2 { -webkit-box-pack: justify; -webkit-justify-content: space-between; @@ -6592,8 +7414,210 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` justify-content: space-between; } -.c25 { - -webkit-flex-wrap: wrap; +.c3 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c7 { + z-index: 1070; + pointer-events: none; + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 150ms linear,opacity 150ms linear; + transition: visibility 150ms linear,opacity 150ms linear; + color: #7D7D7D; +} + +.c5 { + display: inline-block; + height: inherit; +} + +.c10 { + width: 8px; + height: 8px; + z-index: 9998; +} + +.c10::before { + position: absolute; + width: 8px; + height: 8px; + box-sizing: border-box; + z-index: 9998; + content: ''; + border: 1px solid #22222212; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFFFFF; +} + +.c10.arrow-top { + bottom: -4px; +} + +.c10.arrow-top::before { + border-top: none; + border-left: none; +} + +.c10.arrow-bottom { + top: -4px; +} + +.c10.arrow-bottom::before { + border-bottom: none; + border-right: none; +} + +.c10.arrow-left { + right: -4px; +} + +.c10.arrow-left::before { + border-bottom: none; + border-left: none; +} + +.c10.arrow-right { + left: -4px; +} + +.c10.arrow-right::before { + border-right: none; + border-top: none; +} + +.c8 { + max-width: 256px; + width: calc(100vw - 16px); + cursor: default; + padding: 12px; + pointer-events: auto; + color: #222222; + font-weight: 485; + font-size: 12px; + line-height: 16px; + word-break: break-word; + background: #FFFFFF; + border-radius: 12px; + border: 1px solid #22222212; + box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); +} + +.c6 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +
+
+
+ Receive at least +
+
+
+
+ 0.000000000000000952 DEF +
+
+
+
+
+
+ The minimum amount you are guaranteed to receive. If the price slips any further, your transaction will revert. +
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c23 { + box-sizing: border-box; + margin: 0; + min-width: 0; + width: 100%; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c24 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 1px; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c25 { + -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; margin: -1px; @@ -7827,7 +8851,268 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` background-color: #22222212; } -.c10 { +.c10 { + z-index: 1070; + pointer-events: none; + visibility: hidden; + opacity: 0; + -webkit-transition: visibility 150ms linear,opacity 150ms linear; + transition: visibility 150ms linear,opacity 150ms linear; + color: #7D7D7D; +} + +.c5 { + display: inline-block; + height: inherit; +} + +.c15 { + width: 8px; + height: 8px; + z-index: 9998; +} + +.c15::before { + position: absolute; + width: 8px; + height: 8px; + box-sizing: border-box; + z-index: 9998; + content: ''; + border: 1px solid #22222212; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #FFFFFF; +} + +.c15.arrow-top { + bottom: -4px; +} + +.c15.arrow-top::before { + border-top: none; + border-left: none; +} + +.c15.arrow-bottom { + top: -4px; +} + +.c15.arrow-bottom::before { + border-bottom: none; + border-right: none; +} + +.c15.arrow-left { + right: -4px; +} + +.c15.arrow-left::before { + border-bottom: none; + border-left: none; +} + +.c15.arrow-right { + left: -4px; +} + +.c15.arrow-right::before { + border-right: none; + border-top: none; +} + +.c11 { + max-width: 256px; + width: calc(100vw - 16px); + cursor: default; + padding: 12px; + pointer-events: auto; + color: #222222; + font-weight: 485; + font-size: 12px; + line-height: 16px; + word-break: break-word; + background: #FFFFFF; + border-radius: 12px; + border: 1px solid #22222212; + box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); +} + +.c12 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 4px; +} + +.c6 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +.c9 { + background: #22222212; + border-radius: 8px; + color: #7D7D7D; + height: 20px; + padding: 0 6px; +} + +.c9::after { + content: 'Auto'; +} + +
+
+
+ Max. slippage +
+
+
+
+
+
+ 2% +
+
+
+
+
+
+
+
+
+
+ Receive at least +
+
+ 0.000000000000000952 DEF +
+
+
+
+ If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. + + Learn more + +
+
+
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + color: #222222; +} + +.c9 { + color: #7D7D7D; +} + +.c10 { + -webkit-text-decoration: none; + text-decoration: none; + cursor: pointer; + -webkit-transition-duration: 125ms; + transition-duration: 125ms; + color: #FC72FF; + stroke: #FC72FF; + font-weight: 500; +} + +.c10:hover { + opacity: 0.6; +} + +.c10:active { + opacity: 0.4; +} + +.c7 { z-index: 1070; pointer-events: none; visibility: hidden; @@ -7842,13 +9127,13 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` height: inherit; } -.c15 { +.c11 { width: 8px; height: 8px; z-index: 9998; } -.c15::before { +.c11::before { position: absolute; width: 8px; height: 8px; @@ -7862,43 +9147,43 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` background: #FFFFFF; } -.c15.arrow-top { +.c11.arrow-top { bottom: -4px; } -.c15.arrow-top::before { +.c11.arrow-top::before { border-top: none; border-left: none; } -.c15.arrow-bottom { +.c11.arrow-bottom { top: -4px; } -.c15.arrow-bottom::before { +.c11.arrow-bottom::before { border-bottom: none; border-right: none; } -.c15.arrow-left { +.c11.arrow-left { right: -4px; } -.c15.arrow-left::before { +.c11.arrow-left::before { border-bottom: none; border-left: none; } -.c15.arrow-right { +.c11.arrow-right { left: -4px; } -.c15.arrow-right::before { +.c11.arrow-right::before { border-right: none; border-top: none; } -.c11 { +.c8 { max-width: 256px; width: calc(100vw - 16px); cursor: default; @@ -7915,21 +9200,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` box-shadow: 0 4px 8px 0 rgba(47,128,237,0.1); } -.c12 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - -webkit-box-pack: start; - -webkit-justify-content: flex-start; - -ms-flex-pack: start; - justify-content: flex-start; - gap: 4px; -} - .c6 { text-align: right; overflow-wrap: break-word; @@ -7940,18 +9210,6 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` color: #7D7D7D; } -.c9 { - background: #22222212; - border-radius: 8px; - color: #7D7D7D; - height: 20px; - padding: 0 6px; -} - -.c9::after { - content: 'Auto'; -} -
- Max. slippage + Fee
-
-
- 2% -
+ 0 DEF
-
-
-
- Receive at least -
-
- 0.000000000000000952 DEF -
-
-
-
- If the price moves so that you will receive less than 0.000000000000000952 DEF, your transaction will be reverted. This is the minimum amount you are guaranteed to receive. - - Learn more - -
-
+ Learn more +
@@ -9509,6 +10737,85 @@ exports[`SwapLineItem.tsx preview exact in 1`] = ` color: #222222; } +.c6 { + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #FFFFFF 25%, #22222212 50%, #FFFFFF 75% ); + background-size: 400%; + will-change: background-position; + border-radius: 12px; + height: 15px; + width: 50px; +} + +.c5 { + text-align: right; + overflow-wrap: break-word; +} + +.c4 { + cursor: auto; + color: #7D7D7D; +} + +
+
+
+ Fee +
+
+
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + color: #222222; +} + .c9 { color: #7D7D7D; } @@ -9980,6 +11287,76 @@ exports[`SwapLineItem.tsx syncing 1`] = ` color: #222222; } +.c5 { + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #FFFFFF 25%, #22222212 50%, #FFFFFF 75% ); + background-size: 400%; + will-change: background-position; + border-radius: 12px; + height: 15px; + width: 50px; +} + +.c4 { + cursor: help; + color: #7D7D7D; +} + +
+
+
+ Fee +
+
+
+
+ .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c2 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + color: #222222; +} + .c5 { -webkit-animation: fAQEyV 1.5s infinite; animation: fAQEyV 1.5s infinite; diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index e319920c388..3fff80cb44b 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -354,6 +354,29 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
+
+
+
+ Fee +
+
+
+
+ 0 DEF +
+
+
+
+
+
+
+
+ Fee +
+
+
+
+
+
>> +type UniversalRouterFeeField = { feeOptions: FeeOptions } | { flatFeeOptions: FlatFeeOptions } + +function getUniversalRouterFeeFields(trade?: InterfaceTrade): UniversalRouterFeeField | undefined { + if (!isClassicTrade(trade)) return undefined + if (!trade.swapFee) return undefined + + if (trade.tradeType === TradeType.EXACT_INPUT) { + return { feeOptions: { fee: trade.swapFee.percent, recipient: trade.swapFee.recipient } } + } else { + return { flatFeeOptions: { amount: BigNumber.from(trade.swapFee.amount), recipient: trade.swapFee.recipient } } + } +} + // Returns a function that will execute a swap, if the parameters are all valid // and the user has approved the slippage adjusted input amount for the trade export function useSwapCallback( trade: InterfaceTrade | undefined, // trade to execute, required - fiatValues: { amountIn?: number; amountOut?: number }, // usd values for amount in and out, logged for analytics + fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number }, // usd values for amount in and out, and the fee value, logged for analytics allowedSlippage: Percent, // in bips permitSignature: PermitSignature | undefined ) { @@ -47,6 +63,7 @@ export function useSwapCallback( slippageTolerance: allowedSlippage, deadline, permit: permitSignature, + ...getUniversalRouterFeeFields(trade), } ) diff --git a/src/hooks/useUniswapXSwapCallback.ts b/src/hooks/useUniswapXSwapCallback.ts index 044dfcd7677..d4b892c7bd7 100644 --- a/src/hooks/useUniswapXSwapCallback.ts +++ b/src/hooks/useUniswapXSwapCallback.ts @@ -5,6 +5,7 @@ import { Percent } from '@uniswap/sdk-core' import { DutchOrder, DutchOrderBuilder } from '@uniswap/uniswapx-sdk' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, useTrace } from 'analytics' +import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { useCallback } from 'react' import { DutchOrderTrade, TradeFillType } from 'state/routing/types' @@ -50,12 +51,15 @@ export function useUniswapXSwapCallback({ fiatValues, }: { trade?: DutchOrderTrade - fiatValues: { amountIn?: number; amountOut?: number } + fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number } allowedSlippage: Percent }) { const { account, provider } = useWeb3React() const analyticsContext = useTrace() + const { data } = useCachedPortfolioBalancesQuery({ account }) + const portfolioBalanceUsd = data?.portfolios?.[0]?.tokensTotalDenominatedValue?.value + return useCallback( async () => trace('swapx.send', async ({ setTraceData, setTraceStatus }) => { @@ -82,7 +86,7 @@ export function useUniswapXSwapCallback({ .decayEndTime(endTime) .deadline(deadline) .swapper(account) - .nonFeeRecipient(account) + .nonFeeRecipient(account, trade.swapFee?.recipient) // if fetching the nonce fails for any reason, default to existing nonce from the Swap quote. .nonce(updatedNonce ?? trade.order.info.nonce) .build() @@ -115,6 +119,7 @@ export function useUniswapXSwapCallback({ allowedSlippage, fiatValues, timeToSignSinceRequestMs: Date.now() - beforeSign, + portfolioBalanceUsd, }), ...analyticsContext, }) @@ -139,6 +144,7 @@ export function useUniswapXSwapCallback({ trade, allowedSlippage, fiatValues, + portfolioBalanceUsd, }), ...analyticsContext, errorCode: body.errorCode, @@ -154,6 +160,6 @@ export function useUniswapXSwapCallback({ response: { orderHash: body.hash, deadline: updatedOrder.info.deadline }, } }), - [account, provider, trade, allowedSlippage, fiatValues, analyticsContext] + [account, provider, trade, allowedSlippage, fiatValues, analyticsContext, portfolioBalanceUsd] ) } diff --git a/src/hooks/useUniversalRouter.ts b/src/hooks/useUniversalRouter.ts index 3ba976a34f1..1eae9882741 100644 --- a/src/hooks/useUniversalRouter.ts +++ b/src/hooks/useUniversalRouter.ts @@ -2,10 +2,11 @@ import { BigNumber } from '@ethersproject/bignumber' import { t } from '@lingui/macro' import { SwapEventName } from '@uniswap/analytics-events' import { Percent } from '@uniswap/sdk-core' -import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' +import { FlatFeeOptions, SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' import { FeeOptions, toHex } from '@uniswap/v3-sdk' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, useTrace } from 'analytics' +import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' import useBlockNumber from 'lib/hooks/useBlockNumber' import { formatCommonPropertiesForTrade, formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { useCallback } from 'react' @@ -43,17 +44,20 @@ interface SwapOptions { deadline?: BigNumber permit?: PermitSignature feeOptions?: FeeOptions + flatFeeOptions?: FlatFeeOptions } export function useUniversalRouterSwapCallback( trade: ClassicTrade | undefined, - fiatValues: { amountIn?: number; amountOut?: number }, + fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number }, options: SwapOptions ) { const { account, chainId, provider } = useWeb3React() const analyticsContext = useTrace() const blockNumber = useBlockNumber() const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto' + const { data } = useCachedPortfolioBalancesQuery({ account }) + const portfolioBalanceUsd = data?.portfolios?.[0]?.tokensTotalDenominatedValue?.value return useCallback(async () => { return trace('swap.send', async ({ setTraceData, setTraceStatus, setTraceError }) => { @@ -76,6 +80,7 @@ export function useUniversalRouterSwapCallback( deadlineOrPreviousBlockhash: options.deadline?.toString(), inputTokenPermit: options.permit, fee: options.feeOptions, + flatFee: options.flatFeeOptions, }) const tx = { @@ -117,6 +122,7 @@ export function useUniversalRouterSwapCallback( allowedSlippage: options.slippageTolerance, fiatValues, txHash: response.hash, + portfolioBalanceUsd, }), ...analyticsContext, }) @@ -154,16 +160,14 @@ export function useUniversalRouterSwapCallback( }) }, [ account, - analyticsContext, - blockNumber, chainId, - fiatValues, - options.deadline, - options.feeOptions, - options.permit, - options.slippageTolerance, provider, trade, + options, + analyticsContext, + blockNumber, isAutoSlippage, + fiatValues, + portfolioBalanceUsd, ]) } diff --git a/src/lib/hooks/routing/useRoutingAPIArguments.ts b/src/lib/hooks/routing/useRoutingAPIArguments.ts index 8f1c6df3c7e..ca624530e3c 100644 --- a/src/lib/hooks/routing/useRoutingAPIArguments.ts +++ b/src/lib/hooks/routing/useRoutingAPIArguments.ts @@ -4,6 +4,7 @@ import { useUniswapXDefaultEnabled } from 'featureFlags/flags/uniswapXDefault' import { useUniswapXEthOutputEnabled } from 'featureFlags/flags/uniswapXEthOutput' import { useUniswapXExactOutputEnabled } from 'featureFlags/flags/uniswapXExactOutput' import { useUniswapXSyntheticQuoteEnabled } from 'featureFlags/flags/uniswapXUseSyntheticQuote' +import { useFeesEnabled } from 'featureFlags/flags/useFees' import { useMemo } from 'react' import { GetQuoteArgs, INTERNAL_ROUTER_PREFERENCE_PRICE, RouterPreference } from 'state/routing/types' import { currencyAddressForSwapQuote } from 'state/routing/utils' @@ -40,6 +41,10 @@ export function useRoutingAPIArguments({ const uniswapXExactOutputEnabled = useUniswapXExactOutputEnabled() const isUniswapXDefaultEnabled = useUniswapXDefaultEnabled() + const feesEnabled = useFeesEnabled() + // Don't enable fee logic if this is a quote for pricing + const sendPortionEnabled = routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? false : feesEnabled + return useMemo( () => !tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut) || tokenIn.wrapped.equals(tokenOut.wrapped) @@ -64,6 +69,7 @@ export function useRoutingAPIArguments({ uniswapXEthOutputEnabled, uniswapXExactOutputEnabled, isUniswapXDefaultEnabled, + sendPortionEnabled, inputTax, outputTax, }, @@ -80,6 +86,7 @@ export function useRoutingAPIArguments({ userOptedOutOfUniswapX, uniswapXEthOutputEnabled, isUniswapXDefaultEnabled, + sendPortionEnabled, inputTax, outputTax, ] diff --git a/src/lib/utils/analytics.ts b/src/lib/utils/analytics.ts index ad26af2f087..ae3960e739f 100644 --- a/src/lib/utils/analytics.ts +++ b/src/lib/utils/analytics.ts @@ -1,6 +1,6 @@ import { Currency, CurrencyAmount, Percent, Price, Token } from '@uniswap/sdk-core' import { NATIVE_CHAIN_ID } from 'constants/tokens' -import { InterfaceTrade, QuoteMethod } from 'state/routing/types' +import { InterfaceTrade, QuoteMethod, SubmittableTrade } from 'state/routing/types' import { isClassicTrade, isSubmittableTrade, isUniswapXTrade } from 'state/routing/utils' import { computeRealizedPriceImpact } from 'utils/prices' @@ -35,7 +35,11 @@ function getEstimatedNetworkFee(trade: InterfaceTrade) { return undefined } -export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSlippage: Percent) { +export function formatCommonPropertiesForTrade( + trade: InterfaceTrade, + allowedSlippage: Percent, + outputFeeFiatValue?: number +) { return { routing: trade.fillType, type: trade.tradeType, @@ -47,7 +51,7 @@ export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSli token_in_symbol: trade.inputAmount.currency.symbol, token_out_symbol: trade.outputAmount.currency.symbol, token_in_amount: formatToDecimal(trade.inputAmount, trade.inputAmount.currency.decimals), - token_out_amount: formatToDecimal(trade.outputAmount, trade.outputAmount.currency.decimals), + token_out_amount: formatToDecimal(trade.postTaxOutputAmount, trade.outputAmount.currency.decimals), price_impact_basis_points: isClassicTrade(trade) ? formatPercentInBasisPointsNumber(computeRealizedPriceImpact(trade)) : undefined, @@ -59,6 +63,7 @@ export function formatCommonPropertiesForTrade(trade: InterfaceTrade, allowedSli minimum_output_after_slippage: trade.minimumAmountOut(allowedSlippage).toSignificant(6), allowed_slippage: formatPercentNumber(allowedSlippage), method: getQuoteMethod(trade), + fee_usd: outputFeeFiatValue, } } @@ -68,19 +73,22 @@ export const formatSwapSignedAnalyticsEventProperties = ({ fiatValues, txHash, timeToSignSinceRequestMs, + portfolioBalanceUsd, }: { - trade: InterfaceTrade + trade: SubmittableTrade allowedSlippage: Percent - fiatValues: { amountIn?: number; amountOut?: number } + fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number } txHash?: string timeToSignSinceRequestMs?: number + portfolioBalanceUsd?: number }) => ({ + total_balances_usd: portfolioBalanceUsd, transaction_hash: txHash, token_in_amount_usd: fiatValues.amountIn, token_out_amount_usd: fiatValues.amountOut, // measures the amount of time the user took to sign the permit message or swap tx in their wallet time_to_sign_since_request_ms: timeToSignSinceRequestMs, - ...formatCommonPropertiesForTrade(trade, allowedSlippage), + ...formatCommonPropertiesForTrade(trade, allowedSlippage, fiatValues.feeUsd), }) function getQuoteMethod(trade: InterfaceTrade) { @@ -94,10 +102,11 @@ export const formatSwapQuoteReceivedEventProperties = ( allowedSlippage: Percent, swapQuoteLatencyMs: number | undefined, inputTax: Percent, - outputTax: Percent + outputTax: Percent, + outputFeeFiatValue: number | undefined ) => { return { - ...formatCommonPropertiesForTrade(trade, allowedSlippage), + ...formatCommonPropertiesForTrade(trade, allowedSlippage, outputFeeFiatValue), swap_quote_block_number: isClassicTrade(trade) ? trade.blockNumber : undefined, allowed_slippage_basis_points: formatPercentInBasisPointsNumber(allowedSlippage), token_in_amount_max: trade.maximumAmountIn(allowedSlippage).toExact(), diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 213986b1537..1436c8d50d9 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -294,6 +294,7 @@ export function Swap({ inputError: swapInputError, inputTax, outputTax, + outputFeeFiatValue, } = swapInfo const [inputTokenHasTax, outputTokenHasTax] = useMemo( @@ -441,8 +442,8 @@ export function Swap({ ) const showMaxButton = Boolean(maxInputAmount?.greaterThan(0) && !parsedAmounts[Field.INPUT]?.equalTo(maxInputAmount)) const swapFiatValues = useMemo(() => { - return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data } - }, [fiatValueTradeInput, fiatValueTradeOutput]) + return { amountIn: fiatValueTradeInput.data, amountOut: fiatValueTradeOutput.data, feeUsd: outputFeeFiatValue } + }, [fiatValueTradeInput.data, fiatValueTradeOutput.data, outputFeeFiatValue]) // the callback to execute the swap const swapCallback = useSwapCallback( @@ -584,10 +585,17 @@ export function Swap({ if (!trade || prevTrade === trade) return // no new swap quote to log sendAnalyticsEvent(SwapEventName.SWAP_QUOTE_RECEIVED, { - ...formatSwapQuoteReceivedEventProperties(trade, allowedSlippage, swapQuoteLatency, inputTax, outputTax), + ...formatSwapQuoteReceivedEventProperties( + trade, + allowedSlippage, + swapQuoteLatency, + inputTax, + outputTax, + outputFeeFiatValue + ), ...trace, }) - }, [prevTrade, trade, trace, allowedSlippage, swapQuoteLatency, inputTax, outputTax]) + }, [prevTrade, trade, trace, allowedSlippage, swapQuoteLatency, inputTax, outputTax, outputFeeFiatValue]) const showDetailsDropdown = Boolean( !showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing) diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts index c77d58446d3..a0e16b2f40e 100644 --- a/src/state/routing/slice.ts +++ b/src/state/routing/slice.ts @@ -35,6 +35,8 @@ const protocols: Protocol[] = [Protocol.V2, Protocol.V3, Protocol.MIXED] // routing API quote query params: https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/schema/quote-schema.ts const DEFAULT_QUERY_PARAMS = { protocols, + // this should be removed once BE fixes issue where enableUniversalRouter is required for fees to work + enableUniversalRouter: true, } function getQuoteLatencyMeasure(mark: PerformanceMark): PerformanceMeasure { @@ -66,6 +68,7 @@ function getRoutingAPIConfig(args: GetQuoteArgs): RoutingConfig { const classic = { ...DEFAULT_QUERY_PARAMS, routingType: URAQuoteType.CLASSIC, + recipient: account, } const tokenOutIsNative = Object.values(SwapRouterNativeAssets).includes(tokenOutAddress as SwapRouterNativeAssets) @@ -124,16 +127,24 @@ export const routingApi = createApi({ logSwapQuoteRequest(args.tokenInChainId, args.routerPreference, false) const quoteStartMark = performance.mark(`quote-fetch-start-${Date.now()}`) try { - const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, tradeType } = args - const type = isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT' + const { + tokenInAddress: tokenIn, + tokenInChainId, + tokenOutAddress: tokenOut, + tokenOutChainId, + amount, + tradeType, + sendPortionEnabled, + } = args const requestBody = { tokenInChainId, - tokenIn: tokenInAddress, + tokenIn, tokenOutChainId, - tokenOut: tokenOutAddress, + tokenOut, amount, - type, + sendPortionEnabled, + type: isExactInput(tradeType) ? 'EXACT_INPUT' : 'EXACT_OUTPUT', intent: args.routerPreference === INTERNAL_ROUTER_PREFERENCE_PRICE ? 'pricing' : undefined, configs: getRoutingAPIConfig(args), } diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts index b1802a5a61d..a7e0bb36500 100644 --- a/src/state/routing/types.ts +++ b/src/state/routing/types.ts @@ -50,6 +50,7 @@ export interface GetQuoteArgs { // temporary field indicating the user disabled UniswapX during the transition to the opt-out model userOptedOutOfUniswapX: boolean isUniswapXDefaultEnabled: boolean + sendPortionEnabled: boolean inputTax: Percent outputTax: Percent } @@ -112,11 +113,11 @@ export interface ClassicQuoteData { blockNumber: string amount: string amountDecimals: string - gasPriceWei: string - gasUseEstimate: string - gasUseEstimateQuote: string - gasUseEstimateQuoteDecimals: string - gasUseEstimateUSD: string + gasPriceWei?: string + gasUseEstimate?: string + gasUseEstimateQuote?: string + gasUseEstimateQuoteDecimals?: string + gasUseEstimateUSD?: string methodParameters?: { calldata: string; value: string } quote: string quoteDecimals: string @@ -124,19 +125,30 @@ export interface ClassicQuoteData { quoteGasAdjustedDecimals: string route: Array<(V3PoolInRoute | V2PoolInRoute)[]> routeString: string + portionBips?: number + portionRecipient?: string + portionAmount?: string + portionAmountDecimals?: string + quoteGasAndPortionAdjusted?: string + quoteGasAndPortionAdjustedDecimals?: string +} + +export type URADutchOrderQuoteData = { + auctionPeriodSecs: number + deadlineBufferSecs: number + startTimeBufferSecs: number + orderInfo: DutchOrderInfoJSON + quoteId?: string + requestId?: string + slippageTolerance: string + portionBips?: number + portionRecipient?: string + portionAmount?: string } type URADutchOrderQuoteResponse = { routing: URAQuoteType.DUTCH_LIMIT - quote: { - auctionPeriodSecs: number - deadlineBufferSecs: number - startTimeBufferSecs: number - orderInfo: DutchOrderInfoJSON - quoteId?: string - requestId?: string - slippageTolerance: string - } + quote: URADutchOrderQuoteData allQuotes: Array } type URAClassicQuoteResponse = { @@ -179,6 +191,8 @@ export enum TradeFillType { export type ApproveInfo = { needsApprove: true; approveGasEstimateUSD: number } | { needsApprove: false } export type WrapInfo = { needsWrap: true; wrapGasEstimateUSD: number } | { needsWrap: false } +export type SwapFeeInfo = { recipient: string; percent: Percent; amount: string /* raw amount of output token */ } + export class ClassicTrade extends Trade { public readonly fillType = TradeFillType.Classic approveInfo: ApproveInfo @@ -189,6 +203,7 @@ export class ClassicTrade extends Trade { quoteMethod: QuoteMethod inputTax: Percent outputTax: Percent + swapFee: SwapFeeInfo | undefined constructor({ gasUseEstimateUSD, @@ -199,6 +214,7 @@ export class ClassicTrade extends Trade { approveInfo, inputTax, outputTax, + swapFee, ...routes }: { gasUseEstimateUSD?: number @@ -210,6 +226,7 @@ export class ClassicTrade extends Trade { approveInfo: ApproveInfo inputTax: Percent outputTax: Percent + swapFee?: SwapFeeInfo v2Routes: { routev2: V2Route inputAmount: CurrencyAmount @@ -236,17 +253,33 @@ export class ClassicTrade extends Trade { this.approveInfo = approveInfo this.inputTax = inputTax this.outputTax = outputTax + this.swapFee = swapFee + } + + public get executionPrice(): Price { + if (this.tradeType === TradeType.EXACT_INPUT || !this.swapFee) return super.executionPrice + + // Fix inaccurate price calculation for exact output trades + return new Price({ baseAmount: this.inputAmount, quoteAmount: this.postSwapFeeOutputAmount }) } public get totalTaxRate(): Percent { return this.inputTax.add(this.outputTax) } + public get postSwapFeeOutputAmount(): CurrencyAmount { + // Routing api already applies the swap fee to the output amount for exact-in + if (this.tradeType === TradeType.EXACT_INPUT) return this.outputAmount + + const swapFeeAmount = CurrencyAmount.fromRawAmount(this.outputAmount.currency, this.swapFee?.amount ?? 0) + return this.outputAmount.subtract(swapFeeAmount) + } + public get postTaxOutputAmount() { // Ideally we should calculate the final output amount by ammending the inputAmount based on the input tax and then applying the output tax, // but this isn't currently possible because V2Trade reconstructs the total inputAmount based on the swap routes // TODO(WEB-2761): Amend V2Trade objects in the v2-sdk to have a separate field for post-input tax routes - return this.outputAmount.multiply(new Fraction(ONE).subtract(this.totalTaxRate)) + return this.postSwapFeeOutputAmount.multiply(new Fraction(ONE).subtract(this.totalTaxRate)) } public minimumAmountOut(slippageTolerance: Percent, amountOut = this.outputAmount): CurrencyAmount { @@ -281,6 +314,7 @@ export class DutchOrderTrade extends IDutchOrderTrade } inputTax: Percent outputTax: Percent + outputFeeFiatValue?: number parsedAmount?: CurrencyAmount inputError?: ReactNode trade: { @@ -140,6 +142,13 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine outputTax ) + const { data: outputFeeFiatValue } = useUSDPrice( + isSubmittableTrade(trade.trade) && trade.trade.swapFee + ? CurrencyAmount.fromRawAmount(trade.trade.outputAmount.currency, trade.trade.swapFee.amount) + : undefined, + trade.trade?.outputAmount.currency + ) + const currencyBalances = useMemo( () => ({ [Field.INPUT]: relevantTokenBalances[0], @@ -215,8 +224,20 @@ export function useDerivedSwapInfo(state: SwapState, chainId: ChainId | undefine allowedSlippage, inputTax, outputTax, + outputFeeFiatValue, }), - [allowedSlippage, autoSlippage, currencies, currencyBalances, inputError, inputTax, outputTax, parsedAmount, trade] + [ + allowedSlippage, + autoSlippage, + currencies, + currencyBalances, + inputError, + inputTax, + outputFeeFiatValue, + outputTax, + parsedAmount, + trade, + ] ) } diff --git a/src/utils/formatNumbers.test.ts b/src/utils/formatNumbers.test.ts index a0712612b89..ed281d0a22c 100644 --- a/src/utils/formatNumbers.test.ts +++ b/src/utils/formatNumbers.test.ts @@ -188,7 +188,7 @@ describe('formatNumber', () => { expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('$1.23M') expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('$18.45') expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<$0.01') - expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0.00') + expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0') }) it('formats gas prices correctly with portugese locale and thai baht currency', () => { @@ -199,7 +199,7 @@ describe('formatNumber', () => { expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('฿\xa01,23\xa0mi') expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('฿\xa018,45') expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<฿\xa00,01') - expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('฿\xa00,00') + expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('฿\xa00') }) it('formats USD token quantities prices correctly', () => { diff --git a/src/utils/formatNumbers.ts b/src/utils/formatNumbers.ts index 22ff6290c4b..33644aed42c 100644 --- a/src/utils/formatNumbers.ts +++ b/src/utils/formatNumbers.ts @@ -39,6 +39,14 @@ const NO_DECIMALS: NumberFormatOptions = { minimumFractionDigits: 0, } +const NO_DECIMALS_CURRENCY: NumberFormatOptions = { + notation: 'standard', + maximumFractionDigits: 0, + minimumFractionDigits: 0, + currency: 'USD', + style: 'currency', +} + const THREE_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = { notation: 'standard', maximumFractionDigits: 3, @@ -262,7 +270,7 @@ const fiatTokenStatsFormatter: FormatterRule[] = [ ] const fiatGasPriceFormatter: FormatterRule[] = [ - { exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY }, + { exact: 0, formatterOptions: NO_DECIMALS_CURRENCY }, { upperBound: 0.01, hardCodedInput: { input: 0.01, prefix: '<' }, formatterOptions: TWO_DECIMALS_CURRENCY }, { upperBound: 1e6, formatterOptions: TWO_DECIMALS_CURRENCY }, { upperBound: Infinity, formatterOptions: SHORTHAND_CURRENCY_TWO_DECIMALS }, diff --git a/yarn.lock b/yarn.lock index 50505ed1867..9729a922063 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6217,10 +6217,10 @@ resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.33.tgz#966ba96c9ccc8f0e9e09809890b438203f2b1911" integrity sha512-JQkXcpRI3jFG8y3/CGC4TS8NkDgcxXaOQuYW8Qdvd6DcDiIyg2vVYCG9igFEzF0G6UvxgHkBKC7cWCgzZNYvQg== -"@uniswap/uniswapx-sdk@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@uniswap/uniswapx-sdk/-/uniswapx-sdk-1.3.0.tgz#22867580c7f5d5ee35d669444d093e09203e1b47" - integrity sha512-TXH0+3reXA/liY2IRbCRvPVyREDObKSVmd4vEtTD0sPM0NW6ndSowKDH0hWBj2d7lBnSNKz5fp7IOaFT7yHkug== +"@uniswap/uniswapx-sdk@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@uniswap/uniswapx-sdk/-/uniswapx-sdk-1.4.1.tgz#c5fc50000032aa714ff0cc4b9cd1957128a2a4ec" + integrity sha512-M7uuZdozWbXJq8J64KTJ9e0PkYcfe6lx7RBpIsvJaypkGgGDrmU1bAqr1j3sphHlzKTmJCuG7GZBFNcnvzxHLw== dependencies: "@ethersproject/bytes" "^5.7.0" "@ethersproject/providers" "^5.7.0" @@ -6228,10 +6228,10 @@ "@uniswap/sdk-core" "^4.0.3" ethers "^5.7.0" -"@uniswap/universal-router-sdk@^1.5.4", "@uniswap/universal-router-sdk@^1.5.6": - version "1.5.6" - resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.5.6.tgz#274a6ac5df032c34544005fe329aa9e2aac9ade6" - integrity sha512-ZD27U+kugMRJRVEX0oWZsRCw1n5vBN3I17Q22IWE+w/WhOJSppUr6PLo9u4HRdqXTZET7gubnlRc0LOAEkkSkQ== +"@uniswap/universal-router-sdk@^1.5.4", "@uniswap/universal-router-sdk@^1.5.8": + version "1.5.8" + resolved "https://registry.yarnpkg.com/@uniswap/universal-router-sdk/-/universal-router-sdk-1.5.8.tgz#16c62c3883e99073ba8b6e19188cf418b6551847" + integrity sha512-9tDDBTXarpdRfJStF5mDCNmsQrCfiIT6HCQN1EPq0tAm2b+JzjRkUzsLpbNpVef066FETc3YjPH6JDPB3CMyyA== dependencies: "@uniswap/permit2-sdk" "^1.2.0" "@uniswap/router-sdk" "^1.6.0" From 53b53c2207462b4c429796603982878a8bba2303 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 12:12:41 -0400 Subject: [PATCH 46/61] build(deps-dev): bump @uniswap/default-token-list from 11.2.0 to 11.8.0 (#7480) Bumps [@uniswap/default-token-list](https://github.com/Uniswap/default-token-list) from 11.2.0 to 11.8.0. - [Release notes](https://github.com/Uniswap/default-token-list/releases) - [Commits](https://github.com/Uniswap/default-token-list/commits) --- updated-dependencies: - dependency-name: "@uniswap/default-token-list" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 03dbc1de6ae..33f8f1345a0 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "@types/uuid": "^8.3.4", "@types/wcag-contrast": "^3.0.0", "@types/xml2js": "^0.4.12", - "@uniswap/default-token-list": "^11.2.0", + "@uniswap/default-token-list": "^11.8.0", "@uniswap/eslint-config": "^1.2.0", "@vanilla-extract/jest-transform": "^1.1.1", "@vanilla-extract/webpack-plugin": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index 9729a922063..ab48e6af924 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6073,10 +6073,10 @@ react "^18.2.0" react-dom "^18.2.0" -"@uniswap/default-token-list@^11.2.0": - version "11.2.0" - resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-11.2.0.tgz#f7babd37b8f2f1d5c724bd4689be93c4aedd9205" - integrity sha512-royMoeeONKRheGEQD7eeLKdwWvdl6l4qzpFr5Jh9Mp09xdQoBaF7zqZuM/5URIXV0cvvWGWnbIqVKbtFikkMRA== +"@uniswap/default-token-list@^11.2.0", "@uniswap/default-token-list@^11.8.0": + version "11.8.0" + resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-11.8.0.tgz#a430e709a450469fb811f5b4984c00321b82e12c" + integrity sha512-2U0sRaxrEZCyAr0e2pRueESJDtLl2oR7Zu7GeIaqqN6ocs7Ew2pncnrnrdFvMLSElVNbDS0PyCSPCeE6cj6mwg== "@uniswap/eslint-config@^1.2.0": version "1.2.0" From 6b608553620499e2572b2a713cec5a8018ef8363 Mon Sep 17 00:00:00 2001 From: charity-sock-pacifist <147960839+charity-sock-pacifist@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:58:16 -0400 Subject: [PATCH 47/61] fix: learn more url [main] (#7483) * fix: learn more url * fix: snapshots --------- Co-authored-by: charity-sock-pacifist --- src/components/swap/SwapLineItem.tsx | 2 +- .../swap/__snapshots__/SwapLineItem.test.tsx.snap | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index 9cbe1f441d7..92bf5397fc8 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -92,7 +92,7 @@ function SwapFeeTooltipContent({ hasFee }: { hasFee: boolean }) { return ( <> {message}{' '} - + Learn more diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index fe846a61b3b..cc60fbf351a 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -747,7 +747,7 @@ exports[`SwapLineItem.tsx dutch order eth input 1`] = ` Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. @@ -2160,7 +2160,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. @@ -3827,7 +3827,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. @@ -5494,7 +5494,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. @@ -7369,7 +7369,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. @@ -9244,7 +9244,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` Fee is applied on a few token pairs to ensure the best experience with Uniswap. There is no fee associated with this swap. From 9d439e7f624dc4a908a51e84a599db2fe92e3632 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:07:23 -0700 Subject: [PATCH 48/61] feat: organize sitemaps (#7475) --- public/app-sitemap.xml | 105 ++ public/nfts-sitemap.xml | 518 +++++++++ public/sitemap.xml | 1987 +---------------------------------- public/tokens-sitemap.xml | 1663 +++++++++++++++++++++++++++++ scripts/generate-sitemap.js | 49 +- src/pages/routes.test.ts | 2 +- 6 files changed, 2340 insertions(+), 1984 deletions(-) create mode 100644 public/app-sitemap.xml create mode 100644 public/nfts-sitemap.xml create mode 100644 public/tokens-sitemap.xml diff --git a/public/app-sitemap.xml b/public/app-sitemap.xml new file mode 100644 index 00000000000..a74beeecf0e --- /dev/null +++ b/public/app-sitemap.xml @@ -0,0 +1,105 @@ + + + + https://app.uniswap.org/ + 2023-10-11T19:57:27.976Z + weekly + 1.0 + + + https://app.uniswap.org/tokens + 2023-10-11T19:57:27.976Z + weekly + 0.8 + + + https://app.uniswap.org/send + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/swap + 2023-10-11T19:57:27.976Z + weekly + 0.9 + + + https://app.uniswap.org/pool/v2/find + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pool/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pool + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pools/v2/find + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pools/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/pools + 2023-10-11T19:57:27.976Z + weekly + 0.7 + + + https://app.uniswap.org/add/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/add + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/increase + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/migrate/v2 + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/nfts + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/nfts/profile + 2023-10-11T19:57:27.976Z + weekly + 0.6 + + + https://app.uniswap.org/create-proposal + 2023-10-11T19:57:27.976Z + weekly + 0.5 + + \ No newline at end of file diff --git a/public/nfts-sitemap.xml b/public/nfts-sitemap.xml new file mode 100644 index 00000000000..10b94d34812 --- /dev/null +++ b/public/nfts-sitemap.xml @@ -0,0 +1,518 @@ + + + + https://app.uniswap.org/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x60e4d786628fea6478f785a6d7e704777c86a7c6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x99a9b7c1116f9ceeb1652de04d5969cce509b069 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb7f7f6c52f2e2fdb1963eab30438024864c313f6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x23581767a106ae21c074b2276d25e5c3e136a68b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xba30e5f9bb24caa003e9f2f0497ad287fdf95623 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xbd3531da5cf5857e7cfaa92426877b022e612cf8 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x306b1ea3ecdf94ab739f1910bbda052ed4a9f949 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x1a92f7381b9f03921564a437210bb9396471050c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5cc5b05a8a13e3fbdb0bb9fccd98d38e50f90c38 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5af0d9827e0c53e4799bb226655a1de152a425a5 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x3bf2922f4520a8ba0c2efc3d2a1539678dad5e9d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xe785e82358879f061bc3dcac6f0444462d4b5330 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x76be3b62873462d2142405439777e971754e8e77 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xfd43af6d3fe1b916c026f6ac35b3ede068d1ca01 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x1cb1a5e65610aeff2551a50f76a87a7d3fb649c6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x6339e5e072086621540d0362c4e3cea0d643e114 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb932a70a57673d89f4acffbe830e8ed7f75fb9e0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x79fcdef22feed20eddacbb2587640e45491b757f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xa3aee8bce55beea1951ef834b99f3ac60d1abeeb + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x769272677fab02575e84945f03eca517acc544cc + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4db1f25d3d98600140dfc18deb7515be5bd293af + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x34eebee6942d8def3c125458d1a86e0a897fd6f9 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x59468516a8259058bad1ca5f8f4bff190d30e066 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x394e3d3044fc89fcdd966d3cb35ac0b32b0cda91 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x28472a58a490c5e09a238847f66a68a47cc76f0f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x341a1c534248966c4b6afad165b98daed4b964ef + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xccc441ac31f02cd96c153db6fd5fe0a2f4e6a68d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x764aeebcf425d56800ef2c84f2578689415a2daa + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x160c404b2b49cbc3240055ceaee026df1e8497a0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd2f668a8461d6761115daf8aeb3cdf5f40c532c6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x39ee2c7b3cb80254225884ca001f57118c8f21b6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd774557b647330c91bf44cfeab205095f7e6c367 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x1792a96e5668ad7c167ab804a100ce42395ce54d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x04afa589e2b933f9463c5639f412b183ec062505 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xe75512aa3bec8f00434bbd6ad8b0a3fbff100ad6 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x348fc118bcc65a92dc033a951af153d14d945312 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x892848074ddea461a15f337250da3ce55580ca85 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4b15a9c28034dc83db40cd810001427d3bd7163d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7ea3cca10668b8346aec0bf1844a49e995527c8b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb852c6b5892256c264cc2c888ea462189154d8d7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x9378368ba6b85c1fba5b131b530f5f5bedf21a18 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x2acab3dea77832c09420663b0e1cb386031ba17b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x0c2e57efddba8c768147d1fdf9176a0a6ebd5d83 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x08d7c0242953446436f34b4c78fe9da38c73668d + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x8943c7bac1914c9a7aba750bf2b6b09fd21037e0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x364c828ee171616a39897688a831c2499ad972ec + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7f36182dee28c45de6072a34d29855bae76dbe2f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf61f24c2d93bf2de187546b14425bf631f28d6dc + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x797a48c46be32aafcedcfd3d8992493d8a1f256b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x123b30e25973fecd8354dd5f41cc45a3065ef88c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x6632a9d63e142f17a668064d41a21193b49b41a0 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xf4ee95274741437636e748ddac70818b4ed7d043 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x57a204aa1042f6e66dd7730813f4024114d74f37 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd1258db6ac08eb0e625b75b371c023da478e94a9 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x75e95ba5997eb235f40ecf8347cdb11f18ff640b + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xd532b88607b1877fe20c181cba2550e3bbd6b31c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xa1d4657e0e6507d5a94d06da93e94dc7c8c44b51 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xedb61f74b0d09b2558f1eeb79b247c1f363ae452 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7d8820fa92eb1584636f4f5b8515b5476b75171a + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x231d3559aa848bf10366fb9868590f01d34bf240 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xad9fd7cb4fc7a0fbce08d64068f60cbde22ed34c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x0e9d6552b85be180d941f1ca73ae3e318d2d4f1f + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xb716600ed99b4710152582a124c697a7fe78adbf + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4e1f41613c9084fdb9e34e11fae9412427480e56 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x79986af15539de2db9a5086382daeda917a9cf0c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc99c679c50033bbc5321eb88752e89a93e9e83c5 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc36cf0cfcb5d905b8b513860db0cfe63f6cf9f5c + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x3110ef5f612208724ca51f5761a69081809f03b7 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x036721e5a769cc48b3189efbb9cce4471e8a48b1 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x524cab2ec69124574082676e6f654a18df49a048 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7ab2352b1d2e185560494d5e577f9d3c238b78c5 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x32973908faee0bf825a343000fe412ebe56f802a + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x7daec605e9e2a1717326eedfd660601e2753a057 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x33fd426905f149f8376e227d0c9d3340aad17af1 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x466cfcd0525189b573e794f554b8a751279213ac + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x6be69b2a9b153737887cfcdca7781ed1511c7e36 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x80336ad7a747236ef41f47ed2c7641828a480baa + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x9401518f4ebba857baa879d9f76e1cc8b31ed197 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x4b61413d4392c806e6d0ff5ee91e6073c21d6430 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x09233d553058c2f42ba751c87816a8e9fae7ef10 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x960b7a6bcd451c9968473f7bbfd9be826efd549a + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x36d30b3b85255473d27dd0f7fd8f35e36a9d6f06 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x698fbaaca64944376e2cdc4cad86eaa91362cf54 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x497a9a79e82e6fc0ff10a16f6f75e6fcd5ae65a8 + 2023-10-11T23:24:32.127Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x41a322b28d0ff354040e2cbc676f0320d8c8850d + 2023-10-16T18:42:53.632Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0xa9c0a07a7cb84ad1f2ffab06de3e55aab7d523e8 + 2023-10-16T18:42:53.632Z + 0.7 + + + https://app.uniswap.org/nfts/collection/0x942bc2d3e7a589fe5bd4a5c6ef9727dfd82f5c8a + 2023-10-16T18:42:53.632Z + 0.7 + + \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml index f07cb711f7e..613796e8cc6 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1,1975 +1,12 @@ - - - - https://app.uniswap.org/ - 2023-10-11T19:57:27.976Z - weekly - 1.0 - - - https://app.uniswap.org/tokens - 2023-10-11T19:57:27.976Z - weekly - 0.8 - - - https://app.uniswap.org/send - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/swap - 2023-10-11T19:57:27.976Z - weekly - 0.9 - - - https://app.uniswap.org/pool/v2/find - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/pool/v2 - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/pool - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/pools/v2/find - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/pools/v2 - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/pools - 2023-10-11T19:57:27.976Z - weekly - 0.7 - - - https://app.uniswap.org/add/v2 - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/add - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/increase - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/migrate/v2 - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/nfts - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/nfts/profile - 2023-10-11T19:57:27.976Z - weekly - 0.6 - - - https://app.uniswap.org/create-proposal - 2023-10-11T19:57:27.976Z - weekly - 0.5 - - - https://app.uniswap.org/tokens/ethereum/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xdac17f958d2ee523a2206206994597c13d831ec7 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6b175474e89094c44da98b954eedeac495271d0f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x514910771af9ca656af840dff83e8264ecf986ca - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xae78736cd615f374d3085123a210448e74fc6393 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x853d955acef822db058eb8505911ed77f175b99e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xb62e45c3df611dce236a6ddc7a493d79f9dfadef - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x046eee2cc3188071c02bfc1745a6b17c656e3f3d - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x20561172f791f915323241e885b4f7d5187c36e1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x1a7e4e63778b4f12a199c062f3efdd288afcbce8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x5a98fcbea516cf06857215779fd812ca3bef1b32 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x5f98805a4e8be255a32880fdec7f6728c6568ba0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xd0d56273290d339aaf1417d9bfa1bb8cfe8a0933 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x1abaea1f7c830bd89acc67ec4af516284b1bc33c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x72e4f9f808c49a2a61de9c5896298920dc4eeea9 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x92d6c1e31e14520e676a687f0a93788b716beff5 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x423f4e6138e475d85cf7ea071ac92097ed631eea - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6c3ea9036406852006290770bedfcaba0e23a0e8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xb504035a11e672e12a099f32b1672b9c4a78b22f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x183015a9ba6ff60230fdeadc3f43b3d788b13e21 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x9813037ee2218799597d83d4a5b6f3b6778218d9 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xd33526068d116ce69f19a9ee46f0bd304f21a51f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x4d224452801aced8b2f0aebe155379bb5d594381 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xf951e335afb289353dc249e82926178eac7ded78 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x78a0a62fba6fb21a83fe8a3433d44c73a4017a6f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xf411903cbc70a74d22900a5de66a2dda66507255 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xb23d80f5fefcddaa212212f028021b41ded428cf - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xbe9895146f7af43049ca1c1ae358b0541ea49704 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x5283d291dbcf85356a21ba090e6db59121208b44 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xd1d2eb1b1e90b638588728b4130137d262c87cae - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xf57e7e7c23978c3caec3c3548e3d615c346e79ff - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x9d65ff81a3c488d585bbfb0bfe3c7707c7917f54 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xe28b3b32b6c345a34ff64674606124dd5aceca30 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x68749665ff8d2d112fa859aa293f07a622782f38 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xd533a949740bb3306d119cc777fa900ba034cd52 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x163f8c2467924be0ae7b5347228cabf260318753 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x9bf1d7d63dd7a4ce167cf4866388226eeefa702e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x4a220e6096b25eadb88358cb44068a3248254675 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x8207c1ffc5b6804f6024322ccf34f29c3541ae26 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x2b591e99afe9f32eaa6214f7b7629768c40eeb39 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x06450dee7fd2fb8e39061434babcfc05599a6fb8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x0ab87046fbb341d058f17cbc4c1133f25a20a52f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xa0ef786bf476fe0810408caba05e536ac800ff86 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x5faa989af96af85384b8a938c2ede4a7378d9875 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x4fabb145d64652a948d72533023f6e7a623c7c53 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xf4d2888d29d722226fafa5d9b24f9164c092421e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x49d72e3973900a195a155a46441f0c08179fdb64 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xc221b7e65ffc80de234bbb6667abdd46593d34f0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6368e1e18c4c419ddfc608a0bed1ccb87b9250fc - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xed328e9c1179a30ddc1e7595e036aed8760c22af - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xf21661d0d1d76d3ecb8e1b9f1c923dbfffae4097 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xbe042e9d09cb588331ff911c2b46fd833a3e5bd6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xe41d2489571d322189246dafa5ebde1f4699f498 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x3c3a81e81dc49a522a592e7622a7e711c06bf354 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x8f8221afbb33998d8584a2b05749ba73c37a938a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x820802fa8a99901f52e39acd21177b0be6ee2974 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x808507121b80c02388fad14726482e061b8da827 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xc08512927d12348f6620a698105e1baac6ecd911 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x6810e776880c02933d47db1b9fc05908e5386b96 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x582d872a1b094fc48f5de31d3b73f2d9be47def1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xc00e94cb662c3520282e6f5717214004a7f26888 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x0b7f0e51cd1739d6c96982d55ad8fa634dd43a9c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xaaa9214f675316182eaa21c85f0ca99160cc3aaa - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xaea46a60368a7bd060eec7df8cba43b7ef41ad85 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x967da4048cd07ab37855c090aaf366e4ce1b9f48 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x75c97384ca209f915381755c582ec0e2ce88c1ba - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xa8b919680258d369114910511cc87595aec0be6d - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x037a54aab062628c9bbae1fdb1583c195585fe41 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x3ef3b555842cdaff0f4f0b79c9dd65096d60ba63 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xa36fdbbae3c9d55a1d67ee5821d53b50b63a1ab9 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xc944e90c64b2c07662a292be6244bdf05cda44a7 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xaa6e8127831c9de45ae56bb1b0d4d4da6e5665bd - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x18084fba666a33d37592fa2633fd49a74dd93a88 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x33349b282065b0284d756f0577fb39c158f935e6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x470c8950c0c3aa4b09654bc73b004615119a44b5 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xff836a5821e69066c87e268bc51b849fab94240c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xba5bde662c17e2adff1075610382b9b691296350 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x9aab071b4129b083b01cb5a0cb513ce7eca26fa5 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0xf939e0a03fb07f59a73314e73794be0e57ac1b4e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x112b08621e27e10773ec95d250604a041f36c582 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x82af49447d8a07e3bd95bd0d56f35241523fbab1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xff970a61a04b1ca14834a43f5de4533ebddb5cc8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xaf88d065e77c8cc2239327c5edb3a432268e5831 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x912ce59144191c1204e64559fe8253a0e49e6548 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x5979d7b546e38e414f7e9822514be443a4800529 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x0c880f6761f1af8d9aa9c466984b80dab9a8c9e8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x3082cc23568ea640225c2467653db90e9250aaa0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xf97f4df75117a78c1a5a0dbb814af92458539fb4 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x539bde0d7dbd336b79148aa742883198bbf60342 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x18c11fd286c5ec11c3b683caa813b77f5163a122 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x772598e9e62155d7fdfe65fdf01eb5a53a8465be - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x2297aebd383787a160dd0d9f71508148769342e3 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x6694340fc020c5e6b96567843da2df01b2ce1eb6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xfa5ed56a203466cbbc2430a43c66b9d8723528e7 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x1f52145666c862ed3e2f1da213d479e61b2892af - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x83d6c8c06ac276465e4c92e7ac8c23740f435140 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x6fd58f5a2f3468e35feb098b5f59f04157002407 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x32eb7902d4134bf98a28b963d26de779af92a212 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x0341c0c0ec423328621788d4854119b97f44e391 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x93d504070ab0eede5449c89c5ea0f5e34d8103f8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xd77b108d4f6cefaa0cae9506a934e825becca46e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x4e352cf164e64adcbad318c3a1e222e9eba4ce42 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x13ad51ed4f1b7e9dc168d8a00cb3f4ddd85efa60 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x9ed7e4b1bff939ad473da5e7a218c771d1569456 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x289ba1701c2f088cf0faf8b3705246331cb8a839 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x93b346b6bc2548da6a1e7d98e9a421b42541425b - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x17fc002b466eec40dae837fc4be5c67993ddbd6f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xd74f5255d557944cf7dd0e45ff521520002d5748 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x51f9f9ff6cb2266d68c04ec289c7aba81378a383 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x3a33473d7990a605a88ac72a78ad4efc40a54adb - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x9d2f299715d94d8a7e6f5eaa8e654e8c74a988a7 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x7c8a1a80fdd00c9cccd6ebd573e9ecb49bfa2a59 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x3404149e9ee6f17fb41db1ce593ee48fbdcd9506 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x8d9ba570d6cb60c7e3e0f31343efe75ab8e65fb1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x3a18dcc9745edcd1ef33ecb93b0b6eba5671e7ca - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x9623063377ad1b27544c965ccd7342f7ea7e88c7 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x088cd8f5ef3652623c22d48b1605dcfe860cd704 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xfc77b86f3ade71793e1eec1e7944db074922856e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xcf985aba4647a432e60efceeb8054bbd64244305 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x655a6beebf2361a19549a99486ff65f709bd2646 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x371c7ec6d8039ff7933a2aa28eb827ffe1f52f07 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x7dd747d63b094971e6638313a6a2685e80c7fb2e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xc47d9753f3b32aa9548a7c3f30b6aec3b2d2798c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xe85b662fe97e8562f4099d8a1d5a92d4b453bf30 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xfea7a6a0b346362bf88a9e4a88416b77a57d6c2a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x431402e8b9de9aa016c743880e04e517074d8cec - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0xd67a097dce9d4474737e6871684ae3c05460f571 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x51fc0f6660482ea73330e414efd7808811a57fa2 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x7f5c764cbc14f9669b88837ca1490cca17c31607 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x4200000000000000000000000000000000000006 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x4200000000000000000000000000000000000042 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x94b008aa00579c1307b0ef2c499ad98a8ce58e58 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x68f180fcce6836688e9084f035309e29bf0a2095 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x0b2c639c533813f4aa9d7837caf62653d097ff85 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xdc6ff44d5d932cbd77b52e5612ba0529dc6226f1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x1f32b1c2345538c0c6f582fcb022739c4a194ebb - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x8700daec35af8ff88c16bdf0418774cb3d7599b4 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x9e1028f5f1d5ede59748ffcee5532509976840e0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xc5b001dc33727f8f26880b184090d3e252470d45 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x920cf626a271321c151d027030d5d08af699456b - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xc40f949f8a4e094d1b49a23ea9241d289b7b2819 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x9560e827af36c94d2ac33a39bce1fe78631088db - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x296f55f8fb28e498b858d0bcda06d955b2cb3f97 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x350a791bfc2c21f9ed5d10980dad2e2638ffa7f6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x217d47011b23bb961eb6d93ca9945b7501a5bb11 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xf98dcd95217e15e05d8638da4c91125e59590b07 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x73cb180bf0521828d8849bc8cf2b920918e23032 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x9bcef72be871e61ed4fbbc7630889bee758eb81d - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xc03b43d492d904406db2d7d57e67c7e8234ba752 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xfdb794692724153d1488ccdbe0c56c252596735f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x50bce64397c75488465253c0a034b8097fea6578 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0x14778860e937f509e651192a90589de711fb88a9 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x2791bca1f2de4661ed88a30c99a7a9449aa84174 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x7ceb23fd6bc0add59e62ac25578270cff1b9f619 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xc2132d05d31c914a87c6611c10748aeb04b58e8f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x53e0bca35ec356bd5dddfebbd1fc0fd03fabad39 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x77a6f2e9a9e44fd5d5c3f9be9e52831fc1c3c0a0 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe0b52e49357fd4daf2c15e02058dce6bc0057db4 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xdc3326e71d45186f113a2f448984ca0e8d201995 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x49e6a20f1bbdfeec2a8222e052000bbb14ee6007 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x430ef9263e76dae63c84292c3409d61c598e9682 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xf88332547c680f755481bf489d890426248bb275 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xc3c7d422809852031b44ab29eec9f1eff2a58756 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xd6df932a45c0f255f85145f286ea0b292b21c90b - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x172370d5cd63279efa6d502dab29171933a610af - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x45c32fa6df82ead1e2ef74d17b76547eddfaff89 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xd0258a3fd00f38aa8090dfee343f10a9d4d30d3f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xb33eaad8d922b1083446dc23f610c2567fb5180f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe5417af564e4bfda1c483642db72007871397896 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x311434160d7537be358930def317afb606c0d737 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x61299774020da444af134c82fa83e3810b309991 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xa3fa99a148fa48d14ed51d610c367c61876997f1 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x381caf412b45dac0f62fbeec89de306d3eabe384 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x431d5dff03120afa4bdf332c61a6e1766ef37bdb - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe2aa7db6da1dae97c5f5c6914d285fbfcc32a128 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xb6a5ae40e79891e4deadad06c8a7ca47396df21c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xbbba073c31bf03b8acf7c28ef0738decf3695683 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xa486c6bc102f409180ccb8a94ba045d39f8fc7cb - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x0308a3a9c433256ad7ef24dbef9c49c8cb01300a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe631dabef60c37a37d70d3b4f812871df663226f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x8a16d4bf8a0a716017e8d2262c4ac32927797a2f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x553d3d295e0f695b9228246232edf400ed3560b5 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe238ecb42c424e877652ad82d8a939183a04c35f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe261d618a959afffd53168cd07d12e37b26761db - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x15e99d827c1d2fc2b9b5312d1e71713c88110bdb - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x0b3f868e0be5597d5db7feb59e1cadbb0fdda50a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xe111178a87a3bff0c8d18decba5798827539ae99 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x5fe2b58c013d7601147dcdd68c143a77499f5531 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x2ab0e9e4ee70fff1fb9d67031e44f6410170d00e - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xac0f66379a6d7801d7726d5a943356a172549adb - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x71eeba415a523f5c952cc2f06361d5443545ad28 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xf2ae0038696774d65e67892c9d301c5f2cbbda58 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x50b728d8d964fd00c2d0aad81718b71311fef68a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x235737dbb56e8517391473f7c964db31fa6ef280 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x820802fa8a99901f52e39acd21177b0be6ee2974 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xfa68fb4628dff1028cfec22b4162fccd0d45efb6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x18ec0a6e18e5bc3784fdd3a3634b31245ab704f6 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x2f6f07cdcf3588944bf4c42ac74ff24bf56e7590 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x5a7bb7b8eff493625a2bb855445911e63a490e42 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xc708d6f2153933daa50b2d0758955be0a93a8fec - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x11cd37bb86f65419713f30673a480ea33c826872 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xc59132fbdf8de8fbe510f568a5d831c991b4fc38 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xa1c57f48f0deb89f569dfbe6e2b7f46d33606fd4 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0xed755dba6ec1eb520076cec051a582a6d81a8253 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x3b7e1ce09afe2bb3a23919afb65a38e627cfbe97 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/base/0x4200000000000000000000000000000000000006 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/base/0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/base/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/base/0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/base/0x236aa50979d5f3de3bd1eeb40e81137f22ab794b - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0x55d398326f99059ff775485246999027b3197955 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0x2170ed0880ac9a755fd29b2688956bd959f933f8 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xe9e7cea3dedca5984780bafc599bd69add087d56 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xd691d9a68c887bdf34da8c36f63487333acfd103 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0x031b41e504677879370e9dbcf937283a8691fa7f - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xb0d502e938ed5f4df2e681fe6e419ff29631d62b - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xa2b726b1145a4773f68593cf171187d8ebe4d495 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x66803fb87abd4aac3cbb3fad7c3aa01f6f3fb207 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x471ece3750da237f93b8e339c536989b8978a438 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x765de816845861e75a25fca122bb6898b8b1282a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x37f750b7cc259a2f741af45294f6a16572cf5cad - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0xd71ffd0940c920786ec4dbb5a12306669b5b81ef - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x62b8b11039fcfe5ab0c56e502b1c372a3d2a9c7a - 2023-10-11T19:57:27.976Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x5de8ab7e27f6e7a1fff3e5b337584aa43961beef - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x940a2db1b7008b6c776d4faaca729d6d4a4aa551 - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/ethereum/0x80f0c1c49891dcfdd40b6e0f960f84e6042bcb6f - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x1622bf67e6e5747b81866fe0b85178a93c7f86e3 - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/arbitrum/0x031d35296154279dc1984dcd93e392b1f946737b - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/optimism/0xdfa46478f9e5ea86d57387849598dbfb2e964b02 - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x104592a158490a9228070e0a8e5343b499e125d0 - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/polygon/0x4e3decbb3645551b8a19f0ea1678079fcb33fb4c - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/bnb/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/tokens/celo/0x02de4766c272abc10bc88c220d214a26960a7e92 - 2023-10-11T23:21:06.433Z - 0.8 - - - https://app.uniswap.org/nfts/collection/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x60e4d786628fea6478f785a6d7e704777c86a7c6 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xed5af388653567af2f388e6224dc7c4b3241c544 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x99a9b7c1116f9ceeb1652de04d5969cce509b069 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xb7f7f6c52f2e2fdb1963eab30438024864c313f6 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x23581767a106ae21c074b2276d25e5c3e136a68b - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xba30e5f9bb24caa003e9f2f0497ad287fdf95623 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xbd3531da5cf5857e7cfaa92426877b022e612cf8 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x7bd29408f11d2bfc23c34f18275bbf23bb716bc7 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x306b1ea3ecdf94ab739f1910bbda052ed4a9f949 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x1a92f7381b9f03921564a437210bb9396471050c - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x5cc5b05a8a13e3fbdb0bb9fccd98d38e50f90c38 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x5af0d9827e0c53e4799bb226655a1de152a425a5 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x3bf2922f4520a8ba0c2efc3d2a1539678dad5e9d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xe785e82358879f061bc3dcac6f0444462d4b5330 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x76be3b62873462d2142405439777e971754e8e77 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xfd43af6d3fe1b916c026f6ac35b3ede068d1ca01 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x1cb1a5e65610aeff2551a50f76a87a7d3fb649c6 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xff9c1b15b16263c61d017ee9f65c50e4ae0113d7 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x6339e5e072086621540d0362c4e3cea0d643e114 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xb932a70a57673d89f4acffbe830e8ed7f75fb9e0 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x79fcdef22feed20eddacbb2587640e45491b757f - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xa3aee8bce55beea1951ef834b99f3ac60d1abeeb - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x769272677fab02575e84945f03eca517acc544cc - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x4db1f25d3d98600140dfc18deb7515be5bd293af - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x34eebee6942d8def3c125458d1a86e0a897fd6f9 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x59468516a8259058bad1ca5f8f4bff190d30e066 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x394e3d3044fc89fcdd966d3cb35ac0b32b0cda91 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x60bb1e2aa1c9acafb4d34f71585d7e959f387769 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x28472a58a490c5e09a238847f66a68a47cc76f0f - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x341a1c534248966c4b6afad165b98daed4b964ef - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xccc441ac31f02cd96c153db6fd5fe0a2f4e6a68d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x764aeebcf425d56800ef2c84f2578689415a2daa - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x160c404b2b49cbc3240055ceaee026df1e8497a0 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xd2f668a8461d6761115daf8aeb3cdf5f40c532c6 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x39ee2c7b3cb80254225884ca001f57118c8f21b6 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xd774557b647330c91bf44cfeab205095f7e6c367 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x1792a96e5668ad7c167ab804a100ce42395ce54d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xf87e31492faf9a91b02ee0deaad50d51d56d5d4d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x04afa589e2b933f9463c5639f412b183ec062505 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xe75512aa3bec8f00434bbd6ad8b0a3fbff100ad6 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x348fc118bcc65a92dc033a951af153d14d945312 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x892848074ddea461a15f337250da3ce55580ca85 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x5946aeaab44e65eb370ffaa6a7ef2218cff9b47d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x282bdd42f4eb70e7a9d9f40c8fea0825b7f68c5d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x4b15a9c28034dc83db40cd810001427d3bd7163d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x7ea3cca10668b8346aec0bf1844a49e995527c8b - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xb852c6b5892256c264cc2c888ea462189154d8d7 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x9378368ba6b85c1fba5b131b530f5f5bedf21a18 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x2acab3dea77832c09420663b0e1cb386031ba17b - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x0c2e57efddba8c768147d1fdf9176a0a6ebd5d83 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x08d7c0242953446436f34b4c78fe9da38c73668d - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x8943c7bac1914c9a7aba750bf2b6b09fd21037e0 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x364c828ee171616a39897688a831c2499ad972ec - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x7f36182dee28c45de6072a34d29855bae76dbe2f - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xf61f24c2d93bf2de187546b14425bf631f28d6dc - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x797a48c46be32aafcedcfd3d8992493d8a1f256b - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x123b30e25973fecd8354dd5f41cc45a3065ef88c - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x6632a9d63e142f17a668064d41a21193b49b41a0 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xf4ee95274741437636e748ddac70818b4ed7d043 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x57a204aa1042f6e66dd7730813f4024114d74f37 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xd1258db6ac08eb0e625b75b371c023da478e94a9 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x75e95ba5997eb235f40ecf8347cdb11f18ff640b - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xd532b88607b1877fe20c181cba2550e3bbd6b31c - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xa1d4657e0e6507d5a94d06da93e94dc7c8c44b51 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xedb61f74b0d09b2558f1eeb79b247c1f363ae452 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x7d8820fa92eb1584636f4f5b8515b5476b75171a - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x231d3559aa848bf10366fb9868590f01d34bf240 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xad9fd7cb4fc7a0fbce08d64068f60cbde22ed34c - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x0e9d6552b85be180d941f1ca73ae3e318d2d4f1f - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xb716600ed99b4710152582a124c697a7fe78adbf - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xaadc2d4261199ce24a4b0a57370c4fcf43bb60aa - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x4e1f41613c9084fdb9e34e11fae9412427480e56 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x79986af15539de2db9a5086382daeda917a9cf0c - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xc99c679c50033bbc5321eb88752e89a93e9e83c5 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xc36cf0cfcb5d905b8b513860db0cfe63f6cf9f5c - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x3110ef5f612208724ca51f5761a69081809f03b7 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x036721e5a769cc48b3189efbb9cce4471e8a48b1 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x524cab2ec69124574082676e6f654a18df49a048 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x7ab2352b1d2e185560494d5e577f9d3c238b78c5 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x32973908faee0bf825a343000fe412ebe56f802a - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x7daec605e9e2a1717326eedfd660601e2753a057 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xc1caf0c19a8ac28c41fe59ba6c754e4b9bd54de9 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x33fd426905f149f8376e227d0c9d3340aad17af1 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x466cfcd0525189b573e794f554b8a751279213ac - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x6be69b2a9b153737887cfcdca7781ed1511c7e36 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x80336ad7a747236ef41f47ed2c7641828a480baa - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x9401518f4ebba857baa879d9f76e1cc8b31ed197 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x4b61413d4392c806e6d0ff5ee91e6073c21d6430 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0xc3f733ca98e0dad0386979eb96fb1722a1a05e69 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x09233d553058c2f42ba751c87816a8e9fae7ef10 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x960b7a6bcd451c9968473f7bbfd9be826efd549a - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x36d30b3b85255473d27dd0f7fd8f35e36a9d6f06 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x698fbaaca64944376e2cdc4cad86eaa91362cf54 - 2023-10-11T23:24:32.127Z - 0.7 - - - https://app.uniswap.org/nfts/collection/0x497a9a79e82e6fc0ff10a16f6f75e6fcd5ae65a8 - 2023-10-11T23:24:32.127Z - 0.7 - - \ No newline at end of file + + + + https://app.uniswap.org/app-sitemap.xml + + + https://app.uniswap.org/tokens-sitemap.xml + + + https://app.uniswap.org/nfts-sitemap.xml + + \ No newline at end of file diff --git a/public/tokens-sitemap.xml b/public/tokens-sitemap.xml new file mode 100644 index 00000000000..2ebc7c6e862 --- /dev/null +++ b/public/tokens-sitemap.xml @@ -0,0 +1,1663 @@ + + + + https://app.uniswap.org/tokens/ethereum/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xdac17f958d2ee523a2206206994597c13d831ec7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6b175474e89094c44da98b954eedeac495271d0f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x514910771af9ca656af840dff83e8264ecf986ca + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xae78736cd615f374d3085123a210448e74fc6393 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x853d955acef822db058eb8505911ed77f175b99e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6982508145454ce325ddbe47a25d4ec3d2311933 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb62e45c3df611dce236a6ddc7a493d79f9dfadef + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x046eee2cc3188071c02bfc1745a6b17c656e3f3d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x20561172f791f915323241e885b4f7d5187c36e1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x1a7e4e63778b4f12a199c062f3efdd288afcbce8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5a98fcbea516cf06857215779fd812ca3bef1b32 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5f98805a4e8be255a32880fdec7f6728c6568ba0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd0d56273290d339aaf1417d9bfa1bb8cfe8a0933 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x1abaea1f7c830bd89acc67ec4af516284b1bc33c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6dea81c8171d0ba574754ef6f8b412f2ed88c54d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x72e4f9f808c49a2a61de9c5896298920dc4eeea9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x92d6c1e31e14520e676a687f0a93788b716beff5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x423f4e6138e475d85cf7ea071ac92097ed631eea + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6c3ea9036406852006290770bedfcaba0e23a0e8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb504035a11e672e12a099f32b1672b9c4a78b22f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x183015a9ba6ff60230fdeadc3f43b3d788b13e21 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9813037ee2218799597d83d4a5b6f3b6778218d9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd33526068d116ce69f19a9ee46f0bd304f21a51f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4d224452801aced8b2f0aebe155379bb5d594381 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf951e335afb289353dc249e82926178eac7ded78 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x78a0a62fba6fb21a83fe8a3433d44c73a4017a6f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf411903cbc70a74d22900a5de66a2dda66507255 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb23d80f5fefcddaa212212f028021b41ded428cf + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xbe9895146f7af43049ca1c1ae358b0541ea49704 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5283d291dbcf85356a21ba090e6db59121208b44 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd1d2eb1b1e90b638588728b4130137d262c87cae + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf57e7e7c23978c3caec3c3548e3d615c346e79ff + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9d65ff81a3c488d585bbfb0bfe3c7707c7917f54 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xe28b3b32b6c345a34ff64674606124dd5aceca30 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x68749665ff8d2d112fa859aa293f07a622782f38 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd533a949740bb3306d119cc777fa900ba034cd52 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x163f8c2467924be0ae7b5347228cabf260318753 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9bf1d7d63dd7a4ce167cf4866388226eeefa702e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4a220e6096b25eadb88358cb44068a3248254675 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x8207c1ffc5b6804f6024322ccf34f29c3541ae26 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x2b591e99afe9f32eaa6214f7b7629768c40eeb39 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x06450dee7fd2fb8e39061434babcfc05599a6fb8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0ab87046fbb341d058f17cbc4c1133f25a20a52f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa0ef786bf476fe0810408caba05e536ac800ff86 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5faa989af96af85384b8a938c2ede4a7378d9875 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4fabb145d64652a948d72533023f6e7a623c7c53 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf4d2888d29d722226fafa5d9b24f9164c092421e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x49d72e3973900a195a155a46441f0c08179fdb64 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc221b7e65ffc80de234bbb6667abdd46593d34f0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x77e06c9eccf2e797fd462a92b6d7642ef85b0a44 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6368e1e18c4c419ddfc608a0bed1ccb87b9250fc + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xed328e9c1179a30ddc1e7595e036aed8760c22af + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf21661d0d1d76d3ecb8e1b9f1c923dbfffae4097 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xbe042e9d09cb588331ff911c2b46fd833a3e5bd6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xe41d2489571d322189246dafa5ebde1f4699f498 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3c3a81e81dc49a522a592e7622a7e711c06bf354 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x8f8221afbb33998d8584a2b05749ba73c37a938a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x820802fa8a99901f52e39acd21177b0be6ee2974 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x808507121b80c02388fad14726482e061b8da827 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc08512927d12348f6620a698105e1baac6ecd911 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6810e776880c02933d47db1b9fc05908e5386b96 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x582d872a1b094fc48f5de31d3b73f2d9be47def1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc00e94cb662c3520282e6f5717214004a7f26888 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0b7f0e51cd1739d6c96982d55ad8fa634dd43a9c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xaaa9214f675316182eaa21c85f0ca99160cc3aaa + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xaea46a60368a7bd060eec7df8cba43b7ef41ad85 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3432b6a60d23ca0dfca7761b7ab56459d9c964d0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x967da4048cd07ab37855c090aaf366e4ce1b9f48 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x75c97384ca209f915381755c582ec0e2ce88c1ba + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa8b919680258d369114910511cc87595aec0be6d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x037a54aab062628c9bbae1fdb1583c195585fe41 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3ef3b555842cdaff0f4f0b79c9dd65096d60ba63 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa36fdbbae3c9d55a1d67ee5821d53b50b63a1ab9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xc944e90c64b2c07662a292be6244bdf05cda44a7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xaa6e8127831c9de45ae56bb1b0d4d4da6e5665bd + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x18084fba666a33d37592fa2633fd49a74dd93a88 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x33349b282065b0284d756f0577fb39c158f935e6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7dd9c5cba05e151c895fde1cf355c9a1d5da6429 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x470c8950c0c3aa4b09654bc73b004615119a44b5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x81f8f0bb1cb2a06649e51913a151f0e7ef6fa321 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xff836a5821e69066c87e268bc51b849fab94240c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xba5bde662c17e2adff1075610382b9b691296350 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9aab071b4129b083b01cb5a0cb513ce7eca26fa5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xf939e0a03fb07f59a73314e73794be0e57ac1b4e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x112b08621e27e10773ec95d250604a041f36c582 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x82af49447d8a07e3bd95bd0d56f35241523fbab1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xff970a61a04b1ca14834a43f5de4533ebddb5cc8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xaf88d065e77c8cc2239327c5edb3a432268e5831 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x912ce59144191c1204e64559fe8253a0e49e6548 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x5979d7b546e38e414f7e9822514be443a4800529 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfc5a1a6eb076a2c7ad06ed22c90d7e710e35ad0a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x0c880f6761f1af8d9aa9c466984b80dab9a8c9e8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3082cc23568ea640225c2467653db90e9250aaa0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xf97f4df75117a78c1a5a0dbb814af92458539fb4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x539bde0d7dbd336b79148aa742883198bbf60342 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x18c11fd286c5ec11c3b683caa813b77f5163a122 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x772598e9e62155d7fdfe65fdf01eb5a53a8465be + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x2297aebd383787a160dd0d9f71508148769342e3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x6694340fc020c5e6b96567843da2df01b2ce1eb6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfa5ed56a203466cbbc2430a43c66b9d8723528e7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x1f52145666c862ed3e2f1da213d479e61b2892af + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x83d6c8c06ac276465e4c92e7ac8c23740f435140 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x6fd58f5a2f3468e35feb098b5f59f04157002407 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x32eb7902d4134bf98a28b963d26de779af92a212 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x0341c0c0ec423328621788d4854119b97f44e391 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x93d504070ab0eede5449c89c5ea0f5e34d8103f8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xd77b108d4f6cefaa0cae9506a934e825becca46e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x4e352cf164e64adcbad318c3a1e222e9eba4ce42 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x13ad51ed4f1b7e9dc168d8a00cb3f4ddd85efa60 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x9ed7e4b1bff939ad473da5e7a218c771d1569456 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x289ba1701c2f088cf0faf8b3705246331cb8a839 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x93b346b6bc2548da6a1e7d98e9a421b42541425b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfa7f8980b0f1e64a2062791cc3b0871572f1f7f0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x17fc002b466eec40dae837fc4be5c67993ddbd6f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xd74f5255d557944cf7dd0e45ff521520002d5748 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x51f9f9ff6cb2266d68c04ec289c7aba81378a383 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3a33473d7990a605a88ac72a78ad4efc40a54adb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x9d2f299715d94d8a7e6f5eaa8e654e8c74a988a7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x7c8a1a80fdd00c9cccd6ebd573e9ecb49bfa2a59 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3404149e9ee6f17fb41db1ce593ee48fbdcd9506 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x8d9ba570d6cb60c7e3e0f31343efe75ab8e65fb1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x3a18dcc9745edcd1ef33ecb93b0b6eba5671e7ca + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x9623063377ad1b27544c965ccd7342f7ea7e88c7 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x088cd8f5ef3652623c22d48b1605dcfe860cd704 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfc77b86f3ade71793e1eec1e7944db074922856e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xcf985aba4647a432e60efceeb8054bbd64244305 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x655a6beebf2361a19549a99486ff65f709bd2646 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x371c7ec6d8039ff7933a2aa28eb827ffe1f52f07 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x7dd747d63b094971e6638313a6a2685e80c7fb2e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xc47d9753f3b32aa9548a7c3f30b6aec3b2d2798c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xe85b662fe97e8562f4099d8a1d5a92d4b453bf30 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xfea7a6a0b346362bf88a9e4a88416b77a57d6c2a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x431402e8b9de9aa016c743880e04e517074d8cec + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xd67a097dce9d4474737e6871684ae3c05460f571 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x51fc0f6660482ea73330e414efd7808811a57fa2 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x7f5c764cbc14f9669b88837ca1490cca17c31607 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x4200000000000000000000000000000000000006 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x4200000000000000000000000000000000000042 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x94b008aa00579c1307b0ef2c499ad98a8ce58e58 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x68f180fcce6836688e9084f035309e29bf0a2095 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x0b2c639c533813f4aa9d7837caf62653d097ff85 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xda10009cbd5d07dd0cecc66161fc93d7c9000da1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xdc6ff44d5d932cbd77b52e5612ba0529dc6226f1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x1f32b1c2345538c0c6f582fcb022739c4a194ebb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x8700daec35af8ff88c16bdf0418774cb3d7599b4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x9e1028f5f1d5ede59748ffcee5532509976840e0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xc5b001dc33727f8f26880b184090d3e252470d45 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x920cf626a271321c151d027030d5d08af699456b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xc40f949f8a4e094d1b49a23ea9241d289b7b2819 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x9560e827af36c94d2ac33a39bce1fe78631088db + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x296f55f8fb28e498b858d0bcda06d955b2cb3f97 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x350a791bfc2c21f9ed5d10980dad2e2638ffa7f6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x217d47011b23bb961eb6d93ca9945b7501a5bb11 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xf98dcd95217e15e05d8638da4c91125e59590b07 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x73cb180bf0521828d8849bc8cf2b920918e23032 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x9bcef72be871e61ed4fbbc7630889bee758eb81d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xc03b43d492d904406db2d7d57e67c7e8234ba752 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xfdb794692724153d1488ccdbe0c56c252596735f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x50bce64397c75488465253c0a034b8097fea6578 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x14778860e937f509e651192a90589de711fb88a9 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2791bca1f2de4661ed88a30c99a7a9449aa84174 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x7ceb23fd6bc0add59e62ac25578270cff1b9f619 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc2132d05d31c914a87c6611c10748aeb04b58e8f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x53e0bca35ec356bd5dddfebbd1fc0fd03fabad39 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x77a6f2e9a9e44fd5d5c3f9be9e52831fc1c3c0a0 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe0b52e49357fd4daf2c15e02058dce6bc0057db4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xdc3326e71d45186f113a2f448984ca0e8d201995 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x49e6a20f1bbdfeec2a8222e052000bbb14ee6007 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x430ef9263e76dae63c84292c3409d61c598e9682 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xf88332547c680f755481bf489d890426248bb275 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc3c7d422809852031b44ab29eec9f1eff2a58756 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xd6df932a45c0f255f85145f286ea0b292b21c90b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x172370d5cd63279efa6d502dab29171933a610af + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x45c32fa6df82ead1e2ef74d17b76547eddfaff89 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xd0258a3fd00f38aa8090dfee343f10a9d4d30d3f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb33eaad8d922b1083446dc23f610c2567fb5180f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe5417af564e4bfda1c483642db72007871397896 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x311434160d7537be358930def317afb606c0d737 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x61299774020da444af134c82fa83e3810b309991 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa3fa99a148fa48d14ed51d610c367c61876997f1 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x381caf412b45dac0f62fbeec89de306d3eabe384 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x431d5dff03120afa4bdf332c61a6e1766ef37bdb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe2aa7db6da1dae97c5f5c6914d285fbfcc32a128 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb6a5ae40e79891e4deadad06c8a7ca47396df21c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xbbba073c31bf03b8acf7c28ef0738decf3695683 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa486c6bc102f409180ccb8a94ba045d39f8fc7cb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0308a3a9c433256ad7ef24dbef9c49c8cb01300a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe631dabef60c37a37d70d3b4f812871df663226f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x8a16d4bf8a0a716017e8d2262c4ac32927797a2f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x553d3d295e0f695b9228246232edf400ed3560b5 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe238ecb42c424e877652ad82d8a939183a04c35f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe261d618a959afffd53168cd07d12e37b26761db + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x15e99d827c1d2fc2b9b5312d1e71713c88110bdb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0b3f868e0be5597d5db7feb59e1cadbb0fdda50a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xe111178a87a3bff0c8d18decba5798827539ae99 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x5fe2b58c013d7601147dcdd68c143a77499f5531 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2ab0e9e4ee70fff1fb9d67031e44f6410170d00e + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xac0f66379a6d7801d7726d5a943356a172549adb + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x71eeba415a523f5c952cc2f06361d5443545ad28 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xf2ae0038696774d65e67892c9d301c5f2cbbda58 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x50b728d8d964fd00c2d0aad81718b71311fef68a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x235737dbb56e8517391473f7c964db31fa6ef280 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x820802fa8a99901f52e39acd21177b0be6ee2974 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xfa68fb4628dff1028cfec22b4162fccd0d45efb6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x18ec0a6e18e5bc3784fdd3a3634b31245ab704f6 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2f6f07cdcf3588944bf4c42ac74ff24bf56e7590 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x5a7bb7b8eff493625a2bb855445911e63a490e42 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc708d6f2153933daa50b2d0758955be0a93a8fec + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x11cd37bb86f65419713f30673a480ea33c826872 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xc59132fbdf8de8fbe510f568a5d831c991b4fc38 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa1c57f48f0deb89f569dfbe6e2b7f46d33606fd4 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xed755dba6ec1eb520076cec051a582a6d81a8253 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x3b7e1ce09afe2bb3a23919afb65a38e627cfbe97 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x4200000000000000000000000000000000000006 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x236aa50979d5f3de3bd1eeb40e81137f22ab794b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x55d398326f99059ff775485246999027b3197955 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x2170ed0880ac9a755fd29b2688956bd959f933f8 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xe9e7cea3dedca5984780bafc599bd69add087d56 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xf8a0bf9cf54bb92f17374d9e9a321e6a111a51bd + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xd691d9a68c887bdf34da8c36f63487333acfd103 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x1af3f329e8be154074d8769d1ffa4ee058b1dbc3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x031b41e504677879370e9dbcf937283a8691fa7f + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xb0d502e938ed5f4df2e681fe6e419ff29631d62b + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xa2b726b1145a4773f68593cf171187d8ebe4d495 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x66803fb87abd4aac3cbb3fad7c3aa01f6f3fb207 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x471ece3750da237f93b8e339c536989b8978a438 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x765de816845861e75a25fca122bb6898b8b1282a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x37f750b7cc259a2f741af45294f6a16572cf5cad + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0xd71ffd0940c920786ec4dbb5a12306669b5b81ef + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x62b8b11039fcfe5ab0c56e502b1c372a3d2a9c7a + 2023-10-11T19:57:27.976Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x5de8ab7e27f6e7a1fff3e5b337584aa43961beef + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x940a2db1b7008b6c776d4faaca729d6d4a4aa551 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x80f0c1c49891dcfdd40b6e0f960f84e6042bcb6f + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x1622bf67e6e5747b81866fe0b85178a93c7f86e3 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x031d35296154279dc1984dcd93e392b1f946737b + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0xdfa46478f9e5ea86d57387849598dbfb2e964b02 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x104592a158490a9228070e0a8e5343b499e125d0 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x4e3decbb3645551b8a19f0ea1678079fcb33fb4c + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xb0b195aefa3650a6908f15cdac7d92f8a5791b0b + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x02de4766c272abc10bc88c220d214a26960a7e92 + 2023-10-11T23:21:06.433Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x42476f744292107e34519f9c357927074ea3f75d + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x056fd409e1d7a124bd7017459dfea2f387b6d5cd + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x644192291cc835a93d6330b24ea5f5fedd0eef9e + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x3472a5a71965499acd81997a54bba8d852c6e53d + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xa35923162c49cf95e6bf26623385eb431ad920d3 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x04fa0d235c4abf4bcf4787af4cf447de572ef828 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x595832f8fc6bf59c85c527fec3740a1b7a361269 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x6123b0049f904d730db3c36a31167d9d4121fa6b + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0391d2021f89dc339f60fff84546ea23e337750f + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd31a59c85ae9d8edefec411d448f90841571b89c + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x8a7b7b9b2f7d0c63f66171721339705a6188a7d5 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0176b898e92e814c06cc379e508ceb571f70bd40 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0f2d719407fdbeff09d87557abb7232601fd9f29 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x84ca8bc7997272c7cfb4d0cd3d55cd942b3c9419 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xd3e4ba569045546d09cf021ecc5dfe42b1d7f6e4 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x0fd10b9899882a6f2fcb5c371e17e70fdee00c38 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x4cff49d0a19ed6ff845a9122fa912abcfb1f68a6 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x7b744eea1deca2f1b7b31f15ba036fa1759452d7 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x50327c6c5a14dcade707abad2e27eb517df87ab5 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x88df592f8eb5d7bd38bfef7deb0fbc02cf3778a0 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x9e976f211daea0d652912ab99b0dc21a7fd728e4 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xb05097849bca421a3f51b249ba6cca4af4b97cb9 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0xdbecdd726f6ad8e24afc78fe3cc8eb7b73c2d94d + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xa684cd057951541187f288294a1e1c2646aa2d24 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x61a1ff55c5216b636a294a07d77c6f4df10d3b56 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xba5ddd1f9d7f570dc94a51479a000e3bce967196 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0xf19547f9ed24aa66b03c3a552d181ae334fbb8db + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x31c91d8fb96bff40955dd2dbc909b36e8b104dde + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x4945970efeec98d393b4b979b9be265a3ae28a8b + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x11cdb42b0eb46d95f990bedd4695a6e3fa034978 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x561877b6b3dd7651313794e5f2894b2f18be0766 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x6c84a8f1c29108f47a79964b5fe888d4f4d0de40 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x6c2c06790b3e3e3c38e12ee22f8183b37a13ee55 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x53bcf6698c911b2a7409a740eacddb901fc2a2c6 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/arbitrum/0x09e18590e8f76b6cf471b3cd75fe1a1a9d2b2c2b + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x6c84a8f1c29108f47a79964b5fe888d4f4d0de40 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/optimism/0x747e42eb0591547a0ab429b3627816208c734ea7 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x8a953cfe442c5e8855cc6c61b1293fa648bae472 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xa9f37d84c856fda3812ad0519dad44fa0a3fe207 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x62a872d9977db171d9e213a5dc2b782e72ca0033 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x73a4dc4215dc3eb6aae3c7aafd2514cb34e5d983 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xb87904db461005fc716a6bf9f2d451c33b10b80b + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x4f604735c1cf31399c6e711d5962b2b3e0225ad3 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x2760e46d9bb43dafcbecaad1f64b93207f9f0ed7 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x9dbfc1cbf7a1e711503a29b4b5f9130ebeccac96 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x1631244689ec1fecbdd22fb5916e920dfc9b8d30 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x40379a439d4f6795b6fc9aa5687db461677a2dba + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0x0e9b89007eee9c958c0eda24ef70723c2c93dd58 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/polygon/0xff76c0b48363a7c7307868a81548d340049b0023 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/base/0x8fbd0648971d56f1f2c35fa075ff5bc75fb0e39d + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x111111111117dc0aa78b770fa6a738034120c302 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0xcc42724c6683b7e57334c4e856f4c9965ed682bd + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/bnb/0x90ed8f1dc86388f14b64ba8fb4bbd23099f18240 + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/celo/0x9995cc8f20db5896943afc8ee0ba463259c931ed + 2023-10-16T18:42:53.632Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x03ab458634910aad20ef5f1c8ee96f1d6ac54919 + 2023-10-16T19:49:27.657Z + 0.8 + + + https://app.uniswap.org/tokens/ethereum/0x15f74458ae0bfdaa1a96ca1aa779d715cc1eefe4 + 2023-10-16T19:49:27.657Z + 0.8 + + + https://app.uniswap.org/tokens/base/0xfa980ced6895ac314e7de34ef1bfae90a5add21b + 2023-10-16T19:49:27.657Z + 0.8 + + \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js index db6cbad8cd0..e564808ff8b 100644 --- a/scripts/generate-sitemap.js +++ b/scripts/generate-sitemap.js @@ -29,8 +29,8 @@ const nftTopCollectionsQuery = ` } ` -fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { - const sitemapURLs = {} +fs.readFile('./public/tokens-sitemap.xml', 'utf8', async (err, data) => { + const tokenURLs = {} try { const sitemap = await parseStringPromise(data) if (sitemap.urlset.url) { @@ -39,7 +39,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { if (lastMod < Date.now() - weekMs) { url.lastmod = nowISO } - sitemapURLs[url.loc] = true + tokenURLs[url.loc] = true }) } @@ -57,7 +57,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { tokenAddresses.forEach((address) => { const tokenURL = `https://app.uniswap.org/tokens/${chainName.toLowerCase()}/${address}` - if (!(tokenURL in sitemapURLs)) { + if (!(tokenURL in tokenURLs)) { sitemap.urlset.url.push({ loc: [tokenURL], lastmod: [nowISO], @@ -67,6 +67,39 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { }) } + const builder = new Builder() + const xml = builder.buildObject(sitemap) + const path = './public/tokens-sitemap.xml' + fs.writeFile(path, xml, (error) => { + if (error) throw error + const stats = fs.statSync(path) + const fileSizeBytes = stats.size + const fileSizeMegabytes = fileSizeBytes / (1024 * 1024) + + if (fileSizeMegabytes > 50) { + throw new Error('Generated tokens-sitemap.xml file size exceeds 50MB') + } + console.log('Tokens sitemap updated') + }) + } catch (e) { + console.error(e) + } +}) + +fs.readFile('./public/nfts-sitemap.xml', 'utf8', async (err, data) => { + const collectionURLs = {} + try { + const sitemap = await parseStringPromise(data) + if (sitemap.urlset.url) { + sitemap.urlset.url.forEach((url) => { + const lastMod = new Date(url.lastmod).getTime() + if (lastMod < Date.now() - weekMs) { + url.lastmod = nowISO + } + collectionURLs[url.loc] = true + }) + } + const nftResponse = await fetch('https://api.uniswap.org/v1/graphql', { method: 'POST', headers: { @@ -79,7 +112,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { const collectionAddresses = nftJSON.data.topCollections.edges.map((edge) => edge.node.nftContracts[0].address) collectionAddresses.forEach((address) => { const collectionURL = `https://app.uniswap.org/nfts/collection/${address}` - if (!(collectionURL in sitemapURLs)) { + if (!(collectionURL in collectionURLs)) { sitemap.urlset.url.push({ loc: [collectionURL], lastmod: [nowISO], @@ -90,7 +123,7 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { const builder = new Builder() const xml = builder.buildObject(sitemap) - const path = './public/sitemap.xml' + const path = './public/nfts-sitemap.xml' fs.writeFile(path, xml, (error) => { if (error) throw error const stats = fs.statSync(path) @@ -98,9 +131,9 @@ fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => { const fileSizeMegabytes = fileSizeBytes / (1024 * 1024) if (fileSizeMegabytes > 50) { - throw new Error('Generated sitemap file size exceeds 50MB') + throw new Error('Generated nfts-sitemap.xml file size exceeds 50MB') } - console.log('Sitemap updated') + console.log('NFT collections sitemap updated') }) } catch (e) { console.error(e) diff --git a/src/pages/routes.test.ts b/src/pages/routes.test.ts index cb89cc77f46..bf27d079cbb 100644 --- a/src/pages/routes.test.ts +++ b/src/pages/routes.test.ts @@ -6,7 +6,7 @@ import { routes } from './RouteDefinitions' describe('Routes', () => { it('sitemap URLs should exist as Router paths', async () => { const pathNames: string[] = routes.map((routeDef) => routeDef.path) - const contents = fs.readFileSync('./public/sitemap.xml', 'utf8') + const contents = fs.readFileSync('./public/app-sitemap.xml', 'utf8') const sitemap = await parseStringPromise(contents) const sitemapPaths: string[] = sitemap.urlset.url.map((url: any) => new URL(url.loc).pathname) From ed6afb50dec58d077c1fae22e7d3d8d9b0549aa3 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Tue, 17 Oct 2023 15:34:34 -0400 Subject: [PATCH 49/61] feat: [info] add explore page (#7381) * feat: add explore page * add explore filter options * add search bar defocus * use Tokens Tab state for filters * flag-gate /tokens * filter bar css * remove duplicate Explore page, use flag instead * fixes * create tabbednav interface * rename Tokens to Explore * simplify routing * nit * padding nit * pr review * fix menu flyouts * fix TDP nav * add analytics events + ui updates + pr review * nit * nit * add small comment * menu flyout nit * fix merge conflict * min width expand menu flyouts * fix redirects * update routing snapshot * nit css * oops * breakpoints * fix tab routing * pr review * add better tab dynamic routing * fix redirects * error handle edge urls * further fix routing * define return val for useExploreParams * Update snapshot --- package.json | 2 +- .../MiniPortfolio/Tokens/index.tsx | 7 +- src/components/NavBar/SuggestionRow.tsx | 5 +- src/components/NavBar/index.tsx | 14 +- .../Tokens/TokenDetails/Skeleton.tsx | 6 +- src/components/Tokens/TokenDetails/index.tsx | 22 +- .../Tokens/TokenTable/NetworkFilter.tsx | 35 ++- .../Tokens/TokenTable/SearchBar.tsx | 31 ++- .../Tokens/TokenTable/TimeSelector.tsx | 59 +++-- src/components/Tokens/TokenTable/TokenRow.tsx | 5 +- src/components/Tokens/constants.ts | 1 + src/featureFlags/flags/infoExplore.ts | 4 + src/graphql/data/util.tsx | 4 +- src/pages/Explore/index.tsx | 246 ++++++++++++++++++ src/pages/Explore/redirects.tsx | 39 +++ src/pages/RouteDefinitions.tsx | 48 +++- src/pages/Tokens/index.tsx | 105 -------- src/pages/__snapshots__/routes.test.ts.snap | 29 ++- yarn.lock | 8 +- 19 files changed, 506 insertions(+), 164 deletions(-) create mode 100644 src/pages/Explore/index.tsx create mode 100644 src/pages/Explore/redirects.tsx delete mode 100644 src/pages/Tokens/index.tsx diff --git a/package.json b/package.json index 33f8f1345a0..93708838bcb 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "@types/react-helmet": "^6.1.7", "@types/react-window-infinite-loader": "^1.0.6", "@uniswap/analytics": "1.5.0", - "@uniswap/analytics-events": "^2.24.0", + "@uniswap/analytics-events": "^2.25.0", "@uniswap/governance": "^1.0.2", "@uniswap/liquidity-staker": "^1.0.2", "@uniswap/merkle-distributor": "^1.0.1", diff --git a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx index 3f09e733295..8423b07a5b3 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx @@ -3,6 +3,7 @@ import { TraceEvent } from 'analytics' import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' import Row from 'components/Row' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { TokenBalance } from 'graphql/data/__generated__/types-and-hooks' import { getTokenDetailsURL, gqlToCurrency, logSentryErrorForUnsupportedChain } from 'graphql/data/util' import { useAtomValue } from 'jotai/utils' @@ -76,10 +77,12 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok const navigate = useNavigate() const toggleWalletDrawer = useToggleAccountDrawer() + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() + const navigateToTokenDetails = useCallback(async () => { - navigate(getTokenDetailsURL(token)) + navigate(getTokenDetailsURL({ ...token, isInfoExplorePageEnabled })) toggleWalletDrawer() - }, [navigate, token, toggleWalletDrawer]) + }, [navigate, token, isInfoExplorePageEnabled, toggleWalletDrawer]) const { formatNumber } = useFormatter() const currency = gqlToCurrency(token) diff --git a/src/components/NavBar/SuggestionRow.tsx b/src/components/NavBar/SuggestionRow.tsx index c8fdc95aa1f..61c15f2941e 100644 --- a/src/components/NavBar/SuggestionRow.tsx +++ b/src/components/NavBar/SuggestionRow.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx' import QueryTokenLogo from 'components/Logo/QueryTokenLogo' import TokenSafetyIcon from 'components/TokenSafety/TokenSafetyIcon' import { checkSearchTokenWarning } from 'constants/tokenSafety' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { Chain, TokenStandard } from 'graphql/data/__generated__/types-and-hooks' import { SearchToken } from 'graphql/data/SearchTokens' import { getTokenDetailsURL } from 'graphql/data/util' @@ -138,7 +139,9 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, sendAnalyticsEvent(InterfaceEventName.NAVBAR_RESULT_SELECTED, { ...eventProperties }) }, [addRecentlySearchedAsset, token, toggleOpen, eventProperties]) - const tokenDetailsPath = getTokenDetailsURL(token) + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() + + const tokenDetailsPath = getTokenDetailsURL({ ...token, isInfoExplorePageEnabled }) // Close the modal on escape useEffect(() => { const keyDownHandler = (event: KeyboardEvent) => { diff --git a/src/components/NavBar/index.tsx b/src/components/NavBar/index.tsx index 9e1cacc572e..aaeed3908b1 100644 --- a/src/components/NavBar/index.tsx +++ b/src/components/NavBar/index.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' import { useWeb3React } from '@web3-react/core' import { useAccountDrawer } from 'components/AccountDrawer' import Web3Status from 'components/Web3Status' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { chainIdToBackendName } from 'graphql/data/util' import { useDisableNFTRoutes } from 'hooks/useDisableNFTRoutes' import { useIsNftPage } from 'hooks/useIsNftPage' @@ -61,15 +62,22 @@ export const PageTabs = () => { const isNftPage = useIsNftPage() const shouldDisableNFTRoutes = useDisableNFTRoutes() + const infoExplorePageEnabled = useInfoExplorePageEnabled() return ( <> Swap - - Tokens - + {infoExplorePageEnabled ? ( + + Explore + + ) : ( + + Tokens + + )} {!shouldDisableNFTRoutes && ( NFTs diff --git a/src/components/Tokens/TokenDetails/Skeleton.tsx b/src/components/Tokens/TokenDetails/Skeleton.tsx index e347f382879..2d1b636480c 100644 --- a/src/components/Tokens/TokenDetails/Skeleton.tsx +++ b/src/components/Tokens/TokenDetails/Skeleton.tsx @@ -1,4 +1,5 @@ import { SwapSkeleton } from 'components/swap/SwapSkeleton' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { ArrowLeft } from 'react-feather' import { useParams } from 'react-router-dom' import styled, { useTheme } from 'styled-components' @@ -220,9 +221,12 @@ function LoadingStats() { /* Loading State: row component with loading bubbles */ export default function TokenDetailsSkeleton() { const { chainName } = useParams<{ chainName?: string }>() + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() return ( - + Tokens diff --git a/src/components/Tokens/TokenDetails/index.tsx b/src/components/Tokens/TokenDetails/index.tsx index 11c8dd983c5..21b189f78ec 100644 --- a/src/components/Tokens/TokenDetails/index.tsx +++ b/src/components/Tokens/TokenDetails/index.tsx @@ -23,6 +23,7 @@ import TokenSafetyMessage from 'components/TokenSafety/TokenSafetyMessage' import TokenSafetyModal from 'components/TokenSafety/TokenSafetyModal' import { NATIVE_CHAIN_ID, nativeOnChain } from 'constants/tokens' import { checkWarning } from 'constants/tokenSafety' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { useInfoTDPEnabled } from 'featureFlags/flags/infoTDP' import { TokenPriceQuery } from 'graphql/data/__generated__/types-and-hooks' import { Chain, TokenQuery, TokenQueryData } from 'graphql/data/Token' @@ -137,6 +138,8 @@ export default function TokenDetails({ const isBlockedToken = tokenWarning?.canProceed === false const navigate = useNavigate() + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() + // Wrapping navigate in a transition prevents Suspense from unnecessarily showing fallbacks again. const [isPending, startTokenTransition] = useTransition() const navigateToTokenForChain = useCallback( @@ -144,12 +147,20 @@ export default function TokenDetails({ if (!address) return const bridgedAddress = crossChainMap[update] if (bridgedAddress) { - startTokenTransition(() => navigate(getTokenDetailsURL({ address: bridgedAddress, chain: update }))) + startTokenTransition(() => + navigate( + getTokenDetailsURL({ + address: bridgedAddress, + chain: update, + isInfoExplorePageEnabled, + }) + ) + ) } else if (didFetchFromChain || detailedToken?.isNative) { - startTokenTransition(() => navigate(getTokenDetailsURL({ address, chain: update }))) + startTokenTransition(() => navigate(getTokenDetailsURL({ address, chain: update, isInfoExplorePageEnabled }))) } }, - [address, crossChainMap, didFetchFromChain, navigate, detailedToken?.isNative] + [address, crossChainMap, didFetchFromChain, detailedToken?.isNative, navigate, isInfoExplorePageEnabled] ) useOnGlobalChainSwitch(navigateToTokenForChain) @@ -175,11 +186,12 @@ export default function TokenDetails({ tokens[Field.INPUT] && tokens[Field.INPUT]?.currencyId !== newDefaultTokenID ? tokens[Field.INPUT]?.currencyId : null, + isInfoExplorePageEnabled, }) ) ) }, - [address, chain, navigate] + [address, chain, isInfoExplorePageEnabled, navigate] ) const [continueSwap, setContinueSwap] = useState<{ resolve: (value: boolean | PromiseLike) => void }>() @@ -207,7 +219,7 @@ export default function TokenDetails({ {detailedToken && !isPending ? ( - + Tokens diff --git a/src/components/Tokens/TokenTable/NetworkFilter.tsx b/src/components/Tokens/TokenTable/NetworkFilter.tsx index 3a9b39adf51..6927b589077 100644 --- a/src/components/Tokens/TokenTable/NetworkFilter.tsx +++ b/src/components/Tokens/TokenTable/NetworkFilter.tsx @@ -1,6 +1,7 @@ import Badge from 'components/Badge' import { ChainLogo } from 'components/Logo/ChainLogo' import { getChainInfo } from 'constants/chainInfo' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { BACKEND_NOT_YET_SUPPORTED_CHAIN_IDS, BACKEND_SUPPORTED_CHAINS, @@ -8,6 +9,7 @@ import { validateUrlChainParam, } from 'graphql/data/util' import { useOnClickOutside } from 'hooks/useOnClickOutside' +import { useExploreParams } from 'pages/Explore/redirects' import { useRef } from 'react' import { Check, ChevronDown, ChevronUp } from 'react-feather' import { useNavigate, useParams } from 'react-router-dom' @@ -48,8 +50,8 @@ const InternalLinkMenuItem = styled(InternalMenuItem)<{ disabled?: boolean }>` pointer-events: none; `} ` -const MenuTimeFlyout = styled.span` - min-width: 240px; +const MenuTimeFlyout = styled.span<{ isInfoExplorePageEnabled: boolean }>` + min-width: ${({ isInfoExplorePageEnabled }) => (isInfoExplorePageEnabled ? '150px' : '240px')}; max-height: 350px; overflow: auto; background-color: ${({ theme }) => theme.surface1}; @@ -63,7 +65,18 @@ const MenuTimeFlyout = styled.span` position: absolute; top: 48px; z-index: 100; - left: 0px; + + ${({ isInfoExplorePageEnabled }) => + isInfoExplorePageEnabled + ? css` + right: 0px; + @media screen and (max-width: ${({ theme }) => `${theme.breakpoint.lg}px`}) { + left: 0px; + } + ` + : css` + left: 0px; + `} ` const StyledMenu = styled.div` display: flex; @@ -96,8 +109,8 @@ const CheckContainer = styled.div` display: flex; flex-direction: flex-end; ` -const NetworkFilterOption = styled(FilterOption)` - min-width: 156px; +const NetworkFilterOption = styled(FilterOption)<{ isInfoExplorePageEnabled: boolean }>` + ${({ isInfoExplorePageEnabled }) => !isInfoExplorePageEnabled && 'min-width: 156px;'} ` const Tag = styled(Badge)` background-color: ${({ theme }) => theme.surface2}; @@ -114,6 +127,9 @@ export default function NetworkFilter() { const toggleMenu = useToggleModal(ApplicationModal.NETWORK_FILTER) useOnClickOutside(node, open ? toggleMenu : undefined) const navigate = useNavigate() + const { tab } = useExploreParams() + + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() const currentChainName = validateUrlChainParam(useParams().chainName) const chainId = supportedChainIdFromGQLChain(currentChainName) @@ -123,6 +139,7 @@ export default function NetworkFilter() { return ( - {chainInfo.label} + {!isInfoExplorePageEnabled && chainInfo.label} {open ? ( @@ -142,7 +159,7 @@ export default function NetworkFilter() { {open && ( - + {BACKEND_SUPPORTED_CHAINS.map((network) => { const chainId = supportedChainIdFromGQLChain(network) const chainInfo = getChainInfo(chainId) @@ -151,7 +168,9 @@ export default function NetworkFilter() { key={network} data-testid={`tokens-network-filter-option-${network.toLowerCase()}`} onClick={() => { - navigate(`/tokens/${network.toLowerCase()}`) + isInfoExplorePageEnabled + ? navigate(`/explore/${tab}/${network.toLowerCase()}`) + : navigate(`/tokens/${network.toLowerCase()}`) toggleMenu() }} > diff --git a/src/components/Tokens/TokenTable/SearchBar.tsx b/src/components/Tokens/TokenTable/SearchBar.tsx index 62087ec9919..fb9e5358720 100644 --- a/src/components/Tokens/TokenTable/SearchBar.tsx +++ b/src/components/Tokens/TokenTable/SearchBar.tsx @@ -3,6 +3,7 @@ import { BrowserEvent, InterfaceElementName, InterfaceEventName } from '@uniswap import { TraceEvent } from 'analytics' import searchIcon from 'assets/svg/search.svg' import xIcon from 'assets/svg/x.svg' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import useDebounce from 'hooks/useDebounce' import { useAtomValue, useUpdateAtom } from 'jotai/utils' import { useEffect, useState } from 'react' @@ -12,11 +13,12 @@ import { MEDIUM_MEDIA_BREAKPOINT } from '../constants' import { filterStringAtom } from '../state' const ICON_SIZE = '20px' -const SearchBarContainer = styled.div` +const SearchBarContainer = styled.div<{ isInfoExplorePageEnabled: boolean }>` display: flex; flex: 1; + ${({ isInfoExplorePageEnabled }) => isInfoExplorePageEnabled && 'justify-content: flex-end;'} ` -const SearchInput = styled.input` +const SearchInput = styled.input<{ isInfoExplorePageEnabled: boolean; isOpen?: boolean }>` background: no-repeat scroll 7px 7px; background-image: url(${searchIcon}); background-size: 20px 20px; @@ -25,12 +27,14 @@ const SearchInput = styled.input` border-radius: 12px; border: 1px solid ${({ theme }) => theme.surface3}; height: 100%; - width: min(200px, 100%); + width: ${({ isInfoExplorePageEnabled, isOpen }) => + isInfoExplorePageEnabled ? (isOpen ? '200px' : '0') : 'min(200px, 100%)'}; font-size: 16px; font-weight: 485; padding-left: 40px; color: ${({ theme }) => theme.neutral2}; transition-duration: ${({ theme }) => theme.transition.duration.fast}; + ${(isInfoExplorePageEnabled) => isInfoExplorePageEnabled && 'text-overflow: ellipsis;'} :hover { background-color: ${({ theme }) => theme.surface1}; @@ -58,15 +62,18 @@ const SearchInput = styled.input` } @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { - width: 100%; + width: ${({ isInfoExplorePageEnabled, isOpen }) => + isInfoExplorePageEnabled ? (isOpen ? 'min(100%, 200px)' : '0') : '100%'}; } ` -export default function SearchBar() { +export default function SearchBar({ tab }: { tab?: string }) { const currentString = useAtomValue(filterStringAtom) const [localFilterString, setLocalFilterString] = useState(currentString) const setFilterString = useUpdateAtom(filterStringAtom) const debouncedLocalFilterString = useDebounce(localFilterString, 300) + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() + const [isOpen, setIsOpen] = useState(false) useEffect(() => { setLocalFilterString(currentString) @@ -76,8 +83,14 @@ export default function SearchBar() { setFilterString(debouncedLocalFilterString) }, [debouncedLocalFilterString, setFilterString]) + const handleFocus = () => setIsOpen(true) + + const handleBlur = () => { + if (localFilterString === '') setIsOpen(false) + } + return ( - + ( setLocalFilterString(value)} + isOpen={isOpen} + onFocus={isInfoExplorePageEnabled ? handleFocus : undefined} + onBlur={isInfoExplorePageEnabled ? handleBlur : undefined} /> )} > - Filter tokens + {isInfoExplorePageEnabled ? (tab === 'tokens' ? 'Search tokens' : 'Search pools') : 'Filter tokens'} ) diff --git a/src/components/Tokens/TokenTable/TimeSelector.tsx b/src/components/Tokens/TokenTable/TimeSelector.tsx index 33334411a66..a9bdc6e6613 100644 --- a/src/components/Tokens/TokenTable/TimeSelector.tsx +++ b/src/components/Tokens/TokenTable/TimeSelector.tsx @@ -1,3 +1,5 @@ +import { Trans } from '@lingui/macro' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { TimePeriod } from 'graphql/data/util' import { useOnClickOutside } from 'hooks/useOnClickOutside' import { useAtom } from 'jotai' @@ -5,7 +7,7 @@ import { useRef } from 'react' import { Check, ChevronDown, ChevronUp } from 'react-feather' import { useModalIsOpen, useToggleModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' -import styled, { useTheme } from 'styled-components' +import styled, { css, useTheme } from 'styled-components' import { MOBILE_MEDIA_BREAKPOINT, SMALL_MEDIA_BREAKPOINT } from '../constants' import { filterTimeAtom } from '../state' @@ -52,8 +54,8 @@ const InternalLinkMenuItem = styled(InternalMenuItem)` text-decoration: none; } ` -const MenuTimeFlyout = styled.span` - min-width: 240px; +const MenuTimeFlyout = styled.span<{ isInfoExplorePageEnabled: boolean }>` + min-width: ${({ isInfoExplorePageEnabled }) => (isInfoExplorePageEnabled ? '150px' : '240px')}; max-height: 300px; overflow: auto; background-color: ${({ theme }) => theme.surface1}; @@ -69,12 +71,16 @@ const MenuTimeFlyout = styled.span` z-index: 100; left: 0px; - @media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) { - right: 0px; - left: unset; - } + ${({ isInfoExplorePageEnabled }) => + !isInfoExplorePageEnabled && + css` + @media only screen and (max-width: ${SMALL_MEDIA_BREAKPOINT}) { + left: unset; + right: 0px; + } + `} ` -const StyledMenu = styled.div` +const StyledMenu = styled.div<{ isInfoExplorePageEnabled: boolean }>` display: flex; justify-content: center; align-items: center; @@ -82,11 +88,15 @@ const StyledMenu = styled.div` border: none; text-align: left; - @media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) { - width: 72px; - } + ${({ isInfoExplorePageEnabled }) => + !isInfoExplorePageEnabled && + css` + @media only screen and (max-width: ${MOBILE_MEDIA_BREAKPOINT}) { + width: 72px; + } + `} ` -const StyledMenuContent = styled.div` +const StyledMenuContent = styled.div<{ isInfoExplorePageEnabled: boolean }>` display: flex; justify-content: space-between; gap: 8px; @@ -94,6 +104,7 @@ const StyledMenuContent = styled.div` border: none; width: 100%; vertical-align: middle; + ${({ isInfoExplorePageEnabled }) => isInfoExplorePageEnabled && 'white-space: nowrap;'} ` const Chevron = styled.span<{ open: boolean }>` padding-top: 1px; @@ -109,11 +120,19 @@ export default function TimeSelector() { useOnClickOutside(node, open ? toggleMenu : undefined) const [activeTime, setTime] = useAtom(filterTimeAtom) + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() + return ( - + - - {DISPLAYS[activeTime]} + + {isInfoExplorePageEnabled ? ( + <> + {DISPLAYS[activeTime]} volume + + ) : ( + DISPLAYS[activeTime] + )} {open ? ( @@ -124,7 +143,7 @@ export default function TimeSelector() { {open && ( - + {ORDERED_TIMES.map((time) => ( -
{DISPLAYS[time]}
+ {isInfoExplorePageEnabled ? ( +
+ {DISPLAYS[time]} volume +
+ ) : ( +
{DISPLAYS[time]}
+ )} {time === activeTime && }
))} diff --git a/src/components/Tokens/TokenTable/TokenRow.tsx b/src/components/Tokens/TokenTable/TokenRow.tsx index 0f5c9a18396..ee33e7b7b8e 100644 --- a/src/components/Tokens/TokenTable/TokenRow.tsx +++ b/src/components/Tokens/TokenTable/TokenRow.tsx @@ -8,6 +8,7 @@ import { ArrowChangeUp } from 'components/Icons/ArrowChangeUp' import { Info } from 'components/Icons/Info' import QueryTokenLogo from 'components/Logo/QueryTokenLogo' import { MouseoverTooltip } from 'components/Tooltip' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { SparklineMap, TopToken } from 'graphql/data/TopTokens' import { getTokenDetailsURL, supportedChainIdFromGQLChain, validateUrlChainParam } from 'graphql/data/util' import { useAtomValue } from 'jotai/utils' @@ -466,11 +467,13 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef sendAnalyticsEvent(InterfaceEventName.EXPLORE_TOKEN_ROW_CLICKED, exploreTokenSelectedEventProperties) } diff --git a/src/components/Tokens/constants.ts b/src/components/Tokens/constants.ts index e549d5097be..6a9bc2b2622 100644 --- a/src/components/Tokens/constants.ts +++ b/src/components/Tokens/constants.ts @@ -1,5 +1,6 @@ import { ChainId } from '@uniswap/sdk-core' +// Breakpoints specifically for the token pages export const MAX_WIDTH_MEDIA_BREAKPOINT = '1200px' export const XLARGE_MEDIA_BREAKPOINT = '960px' export const LARGE_MEDIA_BREAKPOINT = '840px' diff --git a/src/featureFlags/flags/infoExplore.ts b/src/featureFlags/flags/infoExplore.ts index c5b74aaf64c..e20be24a150 100644 --- a/src/featureFlags/flags/infoExplore.ts +++ b/src/featureFlags/flags/infoExplore.ts @@ -3,3 +3,7 @@ import { BaseVariant, FeatureFlag, useBaseFlag } from '../index' export function useInfoExploreFlag(): BaseVariant { return useBaseFlag(FeatureFlag.infoExplore) } + +export function useInfoExplorePageEnabled(): boolean { + return useInfoExploreFlag() === BaseVariant.Enabled +} diff --git a/src/graphql/data/util.tsx b/src/graphql/data/util.tsx index d5088efbecb..fd99f83061f 100644 --- a/src/graphql/data/util.tsx +++ b/src/graphql/data/util.tsx @@ -207,15 +207,17 @@ export function getTokenDetailsURL({ address, chain, inputAddress, + isInfoExplorePageEnabled, }: { address?: string | null chain: Chain inputAddress?: string | null + isInfoExplorePageEnabled: boolean }) { const chainName = chain.toLowerCase() const tokenAddress = address ?? NATIVE_CHAIN_ID const inputAddressSuffix = inputAddress ? `?inputCurrency=${inputAddress}` : '' - return `/tokens/${chainName}/${tokenAddress}${inputAddressSuffix}` + return (isInfoExplorePageEnabled ? '/explore' : '') + `/tokens/${chainName}/${tokenAddress}${inputAddressSuffix}` } export function unwrapToken< diff --git a/src/pages/Explore/index.tsx b/src/pages/Explore/index.tsx new file mode 100644 index 00000000000..5f4c6cc8edf --- /dev/null +++ b/src/pages/Explore/index.tsx @@ -0,0 +1,246 @@ +import { Trans } from '@lingui/macro' +import { BrowserEvent, InterfaceElementName, InterfacePageName, SharedEventName } from '@uniswap/analytics-events' +import { TraceEvent } from 'analytics' +import { Trace } from 'analytics' +import { AutoRow } from 'components/Row' +import { MAX_WIDTH_MEDIA_BREAKPOINT, MEDIUM_MEDIA_BREAKPOINT } from 'components/Tokens/constants' +import { filterStringAtom } from 'components/Tokens/state' +import NetworkFilter from 'components/Tokens/TokenTable/NetworkFilter' +import SearchBar from 'components/Tokens/TokenTable/SearchBar' +import TimeSelector from 'components/Tokens/TokenTable/TimeSelector' +import TokenTable from 'components/Tokens/TokenTable/TokenTable' +import { MouseoverTooltip } from 'components/Tooltip' +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' +import { useResetAtom } from 'jotai/utils' +import { useEffect, useMemo, useState } from 'react' +import { useLocation, useNavigate } from 'react-router-dom' +import styled, { css } from 'styled-components' +import { ThemedText } from 'theme/components' + +import { useExploreParams } from './redirects' + +const ExploreContainer = styled.div` + width: 100%; + min-width: 320px; + padding: 68px 12px 0px; + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { + padding-top: 48px; + } + + @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { + padding-top: 20px; + } +` +const TitleContainer = styled.div` + margin-bottom: 32px; + max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}; + margin-left: auto; + margin-right: auto; + display: flex; +` +const NavWrapper = styled.div<{ isInfoExplorePageEnabled: boolean }>` + display: flex; + max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}; + margin: 0 auto; + margin-bottom: ${({ isInfoExplorePageEnabled }) => (isInfoExplorePageEnabled ? '16px' : '20px')}; + color: ${({ theme }) => theme.neutral3}; + flex-direction: row; + + @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { + flex-direction: column; + gap: 8px; + } + + ${({ isInfoExplorePageEnabled }) => + isInfoExplorePageEnabled && + css` + @media screen and (max-width: ${({ theme }) => `${theme.breakpoint.lg}px`}) { + flex-direction: column; + gap: 16px; + } + `}; +` +const TabBar = styled(AutoRow)` + gap: 24px; + @media screen and (max-width: ${({ theme }) => theme.breakpoint.md}px) { + gap: 16px; + } +` +const TabItem = styled(ThemedText.HeadlineMedium)<{ active?: boolean }>` + align-items: center; + color: ${({ theme, active }) => (active ? theme.neutral1 : theme.neutral2)}; + cursor: pointer; + transition: ${({ theme }) => `${theme.transition.duration.medium} ${theme.transition.timing.ease} color`}; +` +const FiltersContainer = styled.div<{ isInfoExplorePageEnabled: boolean }>` + display: flex; + gap: 8px; + height: 40px; + + @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { + ${({ isInfoExplorePageEnabled }) => !isInfoExplorePageEnabled && 'order: 2;'} + } + + @media screen and (max-width: ${({ theme }) => theme.breakpoint.md}px) { + ${({ isInfoExplorePageEnabled }) => isInfoExplorePageEnabled && 'justify-content: space-between;'} + } +` +const DropdownFilterContainer = styled(FiltersContainer)<{ isInfoExplorePageEnabled: boolean }>` + ${({ isInfoExplorePageEnabled }) => + isInfoExplorePageEnabled + ? css` + @media screen and (max-width: ${({ theme }) => theme.breakpoint.md}px) { + justify-content: flex-start; + } + ` + : css` + @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { + justify-content: flex-start; + } + `}; +` +const SearchContainer = styled(FiltersContainer)<{ isInfoExplorePageEnabled: boolean }>` + ${({ isInfoExplorePageEnabled }) => !isInfoExplorePageEnabled && 'margin-left: 8px;'} + width: 100%; + + @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { + ${({ isInfoExplorePageEnabled }) => !isInfoExplorePageEnabled && 'order: 1; margin: 0px;'} + } + + @media screen and (max-width: ${({ theme }) => theme.breakpoint.md}px) { + ${({ isInfoExplorePageEnabled }) => isInfoExplorePageEnabled && 'justify-content: flex-end;'} + } +` +export enum ExploreTab { + Tokens = 'tokens', + Pools = 'pools', + Transactions = 'transactions', +} + +interface Page { + title: React.ReactNode + key: ExploreTab + component: () => JSX.Element + loggingElementName: string +} +const Pages: Array = [ + { + title: Tokens, + key: ExploreTab.Tokens, + component: TokenTable, + loggingElementName: InterfaceElementName.EXPLORE_TOKENS_TAB, + }, + { + title: Pools, + key: ExploreTab.Pools, + component: TokenTable, + loggingElementName: InterfaceElementName.EXPLORE_POOLS_TAB, + }, + { + title: Transactions, + key: ExploreTab.Transactions, + component: TokenTable, + loggingElementName: InterfaceElementName.EXPLORE_TRANSACTIONS_TAB, + }, +] + +const Explore = ({ initialTab }: { initialTab?: ExploreTab }) => { + const resetFilterString = useResetAtom(filterStringAtom) + const location = useLocation() + const navigate = useNavigate() + + const initialKey: number = useMemo(() => { + const key = initialTab && Pages.findIndex((page) => page.key === initialTab) + if (!key || key === -1) return 0 + return key + }, [initialTab]) + const [currentTab, setCurrentTab] = useState(initialKey) + const isInfoExplorePageEnabled = useInfoExplorePageEnabled() + + // to allow backward navigation between tabs + const { tab } = useExploreParams() + useEffect(() => { + const tabIndex = Pages.findIndex((page) => page.key === tab) + if (tabIndex !== -1) { + setCurrentTab(tabIndex) + } + }, [tab]) + + useEffect(() => { + resetFilterString() + }, [location, resetFilterString]) + + const { component: Page, key: currentKey } = Pages[currentTab] + + return ( + + + {/* TODO(WEB-2749 & WEB-2750): add graphs to explore page */} + {!isInfoExplorePageEnabled && ( + + This table contains the top tokens by Uniswap volume, sorted based on your input.} + placement="bottom" + > + + Top tokens on Uniswap + + + + )} + + {isInfoExplorePageEnabled && ( + + {Pages.map(({ title, loggingElementName, key }, index) => { + const handleNavItemClick = () => { + setCurrentTab(index) + navigate(`/explore/${key}`) + } + return ( + + + {title} + + + ) + })} + + )} + {isInfoExplorePageEnabled ? ( + + + + {currentKey === ExploreTab.Tokens && } + + + {currentKey !== ExploreTab.Transactions && } + + + ) : ( + <> + + + + + + + + + )} + + {isInfoExplorePageEnabled ? : } + + + ) +} + +export default Explore diff --git a/src/pages/Explore/redirects.tsx b/src/pages/Explore/redirects.tsx new file mode 100644 index 00000000000..d5f81919e73 --- /dev/null +++ b/src/pages/Explore/redirects.tsx @@ -0,0 +1,39 @@ +import { Navigate, useParams } from 'react-router-dom' + +import Explore, { ExploreTab } from '.' + +// useParams struggles to distinguish between /explore/:chainId and /explore/:tab +export function useExploreParams(): { + tab?: ExploreTab + chainName?: string + tokenAddress?: string +} { + const { tab, chainName, tokenAddress } = useParams<{ tab: string; chainName: string; tokenAddress: string }>() + const exploreTabs = Object.values(ExploreTab) + if (tab && !chainName && exploreTabs.includes(tab as ExploreTab)) { + // /explore/:tab + return { tab: tab as ExploreTab, chainName: undefined, tokenAddress } + } else if (tab && !chainName) { + // /explore/:chainName + return { tab: ExploreTab.Tokens, chainName: tab, tokenAddress } + } else if (!tab && !chainName) { + // legacy /tokens + return { tab: ExploreTab.Tokens, chainName: undefined, tokenAddress: undefined } + } else { + // /explore/:tab/:chainName + return { tab: tab as ExploreTab, chainName, tokenAddress } + } +} +export default function RedirectExplore() { + const { tab, chainName, tokenAddress } = useExploreParams() + if (tab && chainName && tokenAddress) { + return + } else if (chainName && tokenAddress) { + return + } else if (tab && chainName) { + return + } else if (chainName) { + return + } + return +} diff --git a/src/pages/RouteDefinitions.tsx b/src/pages/RouteDefinitions.tsx index b87d31a53fd..fa713a3b353 100644 --- a/src/pages/RouteDefinitions.tsx +++ b/src/pages/RouteDefinitions.tsx @@ -1,3 +1,4 @@ +import { useInfoExplorePageEnabled } from 'featureFlags/flags/infoExplore' import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage' import { useAtom } from 'jotai' import { lazy, ReactNode, Suspense, useMemo } from 'react' @@ -15,8 +16,10 @@ const Collection = lazy(() => import('nft/pages/collection')) const Profile = lazy(() => import('nft/pages/profile')) const Asset = lazy(() => import('nft/pages/asset/Asset')) const AddLiquidity = lazy(() => import('pages/AddLiquidity')) +const Explore = lazy(() => import('pages/Explore')) const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects')) const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects')) +const RedirectExplore = lazy(() => import('pages/Explore/redirects')) const MigrateV2 = lazy(() => import('pages/MigrateV2')) const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair')) const NotFound = lazy(() => import('pages/NotFound')) @@ -28,7 +31,6 @@ const PoolFinder = lazy(() => import('pages/PoolFinder')) const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity')) const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3')) const TokenDetails = lazy(() => import('pages/TokenDetails')) -const Tokens = lazy(() => import('pages/Tokens')) const Vote = lazy(() => import('pages/Vote')) // this is the same svg defined in assets/images/blue-loader.svg @@ -48,6 +50,7 @@ const LazyLoadSpinner = () => ( interface RouterConfig { browserRouterEnabled?: boolean hash?: string + infoExplorePageEnabled?: boolean infoPoolPageEnabled?: boolean shouldDisableNFTRoutes?: boolean } @@ -59,15 +62,17 @@ export function useRouterConfig(): RouterConfig { const browserRouterEnabled = isBrowserRouterEnabled() const { hash } = useLocation() const infoPoolPageEnabled = useInfoPoolPageEnabled() + const infoExplorePageEnabled = useInfoExplorePageEnabled() const [shouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom) return useMemo( () => ({ browserRouterEnabled, hash, + infoExplorePageEnabled, infoPoolPageEnabled, shouldDisableNFTRoutes: Boolean(shouldDisableNFTRoutes), }), - [browserRouterEnabled, hash, infoPoolPageEnabled, shouldDisableNFTRoutes] + [browserRouterEnabled, hash, infoExplorePageEnabled, infoPoolPageEnabled, shouldDisableNFTRoutes] ) } @@ -97,16 +102,45 @@ export const routes: RouteDefinition[] = [ return args.browserRouterEnabled && args.hash ? : }, }), + createRouteDefinition({ + path: '/explore', + nestedPaths: [':tab', ':chainName'], + getElement: () => , + enabled: (args) => Boolean(args.infoExplorePageEnabled), + }), + createRouteDefinition({ + path: '/explore', + nestedPaths: [':tab/:chainName'], + getElement: () => , + enabled: (args) => Boolean(args.infoExplorePageEnabled), + }), + createRouteDefinition({ + path: '/explore/tokens/:chainName/:tokenAddress', + getElement: () => , + enabled: (args) => Boolean(args.infoExplorePageEnabled), + }), createRouteDefinition({ path: '/tokens', - nestedPaths: [':chainName'], - getElement: () => , + getElement: (args) => { + return args.infoExplorePageEnabled ? : + }, + }), + createRouteDefinition({ + path: '/tokens/:chainName', + getElement: (args) => { + return args.infoExplorePageEnabled ? : + }, + }), + createRouteDefinition({ + path: '/tokens/:chainName/:tokenAddress', + getElement: (args) => { + return args.infoExplorePageEnabled ? : + }, }), - createRouteDefinition({ path: '/tokens/:chainName/:tokenAddress', getElement: () => }), createRouteDefinition({ - path: '/pools/:chainName/:poolAddress', + path: 'explore/pools/:chainName/:poolAddress', getElement: () => , - enabled: (args) => Boolean(args.infoPoolPageEnabled), + enabled: (args) => Boolean(args.infoExplorePageEnabled && args.infoPoolPageEnabled), }), createRouteDefinition({ path: '/vote/*', diff --git a/src/pages/Tokens/index.tsx b/src/pages/Tokens/index.tsx deleted file mode 100644 index 91496edbf5c..00000000000 --- a/src/pages/Tokens/index.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Trans } from '@lingui/macro' -import { InterfacePageName } from '@uniswap/analytics-events' -import { Trace } from 'analytics' -import { MAX_WIDTH_MEDIA_BREAKPOINT, MEDIUM_MEDIA_BREAKPOINT } from 'components/Tokens/constants' -import { filterStringAtom } from 'components/Tokens/state' -import NetworkFilter from 'components/Tokens/TokenTable/NetworkFilter' -import SearchBar from 'components/Tokens/TokenTable/SearchBar' -import TimeSelector from 'components/Tokens/TokenTable/TimeSelector' -import TokenTable from 'components/Tokens/TokenTable/TokenTable' -import { MouseoverTooltip } from 'components/Tooltip' -import { useResetAtom } from 'jotai/utils' -import { useEffect } from 'react' -import { useLocation } from 'react-router-dom' -import styled from 'styled-components' -import { ThemedText } from 'theme/components' - -const ExploreContainer = styled.div` - width: 100%; - min-width: 320px; - padding: 68px 12px 0px; - - @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.md}px`}) { - padding-top: 48px; - } - - @media only screen and (max-width: ${({ theme }) => `${theme.breakpoint.sm}px`}) { - padding-top: 20px; - } -` -const TitleContainer = styled.div` - margin-bottom: 32px; - max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}; - margin-left: auto; - margin-right: auto; - display: flex; -` -const FiltersContainer = styled.div` - display: flex; - gap: 8px; - height: 40px; - - @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { - order: 2; - } -` -const SearchContainer = styled(FiltersContainer)` - margin-left: 8px; - width: 100%; - - @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { - margin: 0px; - order: 1; - } -` -const FiltersWrapper = styled.div` - display: flex; - max-width: ${MAX_WIDTH_MEDIA_BREAKPOINT}; - margin: 0 auto; - margin-bottom: 20px; - color: ${({ theme }) => theme.neutral3}; - flex-direction: row; - - @media only screen and (max-width: ${MEDIUM_MEDIA_BREAKPOINT}) { - flex-direction: column; - gap: 8px; - } -` - -const Tokens = () => { - const resetFilterString = useResetAtom(filterStringAtom) - const location = useLocation() - - useEffect(() => { - resetFilterString() - }, [location, resetFilterString]) - - return ( - - - - This table contains the top tokens by Uniswap volume, sorted based on your input.} - placement="bottom" - > - - Top tokens on Uniswap - - - - - - - - - - - - - - - - ) -} - -export default Tokens diff --git a/src/pages/__snapshots__/routes.test.ts.snap b/src/pages/__snapshots__/routes.test.ts.snap index 1e87906873b..5bb8372bebe 100644 --- a/src/pages/__snapshots__/routes.test.ts.snap +++ b/src/pages/__snapshots__/routes.test.ts.snap @@ -12,10 +12,37 @@ Array [ "enabled": [Function], "getElement": [Function], "nestedPaths": Array [ + ":tab", ":chainName", ], + "path": "/explore", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [ + ":tab/:chainName", + ], + "path": "/explore", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/explore/tokens/:chainName/:tokenAddress", + }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], "path": "/tokens", }, + Object { + "enabled": [Function], + "getElement": [Function], + "nestedPaths": Array [], + "path": "/tokens/:chainName", + }, Object { "enabled": [Function], "getElement": [Function], @@ -26,7 +53,7 @@ Array [ "enabled": [Function], "getElement": [Function], "nestedPaths": Array [], - "path": "/pools/:chainName/:poolAddress", + "path": "explore/pools/:chainName/:poolAddress", }, Object { "enabled": [Function], diff --git a/yarn.lock b/yarn.lock index ab48e6af924..36e480eca7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6059,10 +6059,10 @@ "@typescript-eslint/types" "5.59.1" eslint-visitor-keys "^3.3.0" -"@uniswap/analytics-events@^2.24.0": - version "2.24.0" - resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.24.0.tgz#c81d0c24da4f052b7f6b2663ff42bfa787be91b5" - integrity sha512-MhX9L95Y7i28a3KxRFJnpmNxNHAgownBVPyhT+mu4PnCXiPEuSovml+uJr277tysKSqxRYqLnCeAw4LocTBIfg== +"@uniswap/analytics-events@^2.25.0": + version "2.25.0" + resolved "https://registry.yarnpkg.com/@uniswap/analytics-events/-/analytics-events-2.25.0.tgz#06f2d81342b2e4dc516bdfa1222ddaa7c274ac04" + integrity sha512-0syw7gZtoHXSCVb+zV464L+Zgy1ICnGDOrbK2xoVtpiQ8rBjUXPWvcKuaiNPfTsS9tIZNtqOmEyZEjWwvFSLUw== "@uniswap/analytics@1.5.0": version "1.5.0" From 740db0fe166c3a4c1c4840a60bbd6691e4048fad Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:00:39 -0700 Subject: [PATCH 50/61] fix: merge portfolio tokens w/ default token list in Currency Selector (#7479) --- src/components/SearchModal/CurrencySearch.tsx | 84 ++++++++++++------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/src/components/SearchModal/CurrencySearch.tsx b/src/components/SearchModal/CurrencySearch.tsx index 2df87730817..a47b922fd35 100644 --- a/src/components/SearchModal/CurrencySearch.tsx +++ b/src/components/SearchModal/CurrencySearch.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line no-restricted-imports import { t, Trans } from '@lingui/macro' import { InterfaceEventName, InterfaceModalName } from '@uniswap/analytics-events' -import { Currency, Token } from '@uniswap/sdk-core' +import { ChainId, Currency, Token } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { Trace } from 'analytics' import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' @@ -76,9 +76,6 @@ export function CurrencySearch({ const searchTokenIsAdded = useIsUserAddedToken(searchToken) const defaultTokens = useDefaultActiveTokens(chainId) - const filteredTokens: Token[] = useMemo(() => { - return Object.values(defaultTokens).filter(getTokenFilter(debouncedQuery)) - }, [defaultTokens, debouncedQuery]) const { data, loading: balancesAreLoading } = useCachedPortfolioBalancesQuery({ account }) const balances: TokenBalances = useMemo(() => { @@ -100,34 +97,57 @@ export function CurrencySearch({ ) }, [chainId, data?.portfolios]) - const sortedTokens: Token[] = useMemo( - () => - !balancesAreLoading - ? filteredTokens - .filter((token) => { - if (onlyShowCurrenciesWithBalance) { - return balances[token.address?.toLowerCase()]?.usdValue > 0 - } + const sortedTokens: Token[] = useMemo(() => { + const portfolioTokens = data?.portfolios?.[0].tokenBalances + ?.map((tokenBalance) => { + if (!tokenBalance?.token?.chain || !tokenBalance.token?.address || !tokenBalance.token?.decimals) { + return undefined + } + return new Token( + supportedChainIdFromGQLChain(tokenBalance.token?.chain) ?? ChainId.MAINNET, + tokenBalance.token?.address, + tokenBalance.token?.decimals, + tokenBalance.token?.symbol, + tokenBalance.token?.name + ) + }) + .filter((token) => !!token) as Token[] + + const filteredTokens = Object.values(defaultTokens) + .filter(getTokenFilter(debouncedQuery)) + // Filter out tokens with balances so they aren't duplicated when we merge below. + .filter((token) => !(token.address?.toLowerCase() in balances)) + const mergedTokens = [...(portfolioTokens ?? []), ...filteredTokens] + + if (balancesAreLoading) { + return mergedTokens + } + + return mergedTokens + .filter((token) => { + if (onlyShowCurrenciesWithBalance) { + return balances[token.address?.toLowerCase()]?.usdValue > 0 + } + + // If there is no query, filter out unselected user-added tokens with no balance. + if (!debouncedQuery && token instanceof UserAddedToken) { + if (selectedCurrency?.equals(token) || otherSelectedCurrency?.equals(token)) return true + return balances[token.address.toLowerCase()]?.usdValue > 0 + } + return true + }) + .sort(tokenComparator.bind(null, balances)) + }, [ + data, + defaultTokens, + debouncedQuery, + balancesAreLoading, + balances, + onlyShowCurrenciesWithBalance, + selectedCurrency, + otherSelectedCurrency, + ]) - // If there is no query, filter out unselected user-added tokens with no balance. - if (!debouncedQuery && token instanceof UserAddedToken) { - if (selectedCurrency?.equals(token) || otherSelectedCurrency?.equals(token)) return true - return balances[token.address.toLowerCase()]?.usdValue > 0 - } - return true - }) - .sort(tokenComparator.bind(null, balances)) - : filteredTokens, - [ - balancesAreLoading, - filteredTokens, - balances, - onlyShowCurrenciesWithBalance, - debouncedQuery, - selectedCurrency, - otherSelectedCurrency, - ] - ) const isLoading = Boolean(balancesAreLoading && !tokenLoaderTimerElapsed) const filteredSortedTokens = useSortTokensByQuery(debouncedQuery, sortedTokens) @@ -205,7 +225,7 @@ export function CurrencySearch({ // if no results on main list, show option to expand into inactive const filteredInactiveTokens = useSearchInactiveTokenLists( - !onlyShowCurrenciesWithBalance && (filteredTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch)) + !onlyShowCurrenciesWithBalance && (sortedTokens.length === 0 || (debouncedQuery.length > 2 && !isAddressSearch)) ? debouncedQuery : undefined ) From 71d3661b22d0af8841f995fb7ede83620778f58f Mon Sep 17 00:00:00 2001 From: ksvat <147102038+ksvat@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:54:47 -0700 Subject: [PATCH 51/61] fix: currency balance for search token (#7471) --- src/components/SearchModal/CurrencySearch.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/SearchModal/CurrencySearch.tsx b/src/components/SearchModal/CurrencySearch.tsx index a47b922fd35..33dc6f0a455 100644 --- a/src/components/SearchModal/CurrencySearch.tsx +++ b/src/components/SearchModal/CurrencySearch.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line no-restricted-imports import { t, Trans } from '@lingui/macro' import { InterfaceEventName, InterfaceModalName } from '@uniswap/analytics-events' -import { ChainId, Currency, Token } from '@uniswap/sdk-core' +import { ChainId, Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' import { Trace } from 'analytics' import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' @@ -12,6 +12,7 @@ import useToggle from 'hooks/useToggle' import useNativeCurrency from 'lib/hooks/useNativeCurrency' import { getTokenFilter } from 'lib/hooks/useTokenList/filtering' import { TokenBalances, tokenComparator, useSortTokensByQuery } from 'lib/hooks/useTokenList/sorting' +import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { ChangeEvent, KeyboardEvent, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react' import AutoSizer from 'react-virtualized-auto-sizer' import { FixedSizeList } from 'react-window' @@ -291,6 +292,12 @@ export function CurrencySearch({ searchQuery, isAddressSearch )} + balance={ + tryParseCurrencyAmount( + String(balances[searchToken.isNative ? 'ETH' : searchToken.address?.toLowerCase()]?.balance ?? 0), + searchToken + ) ?? CurrencyAmount.fromRawAmount(searchToken, 0) + } /> ) : searchCurrencies?.length > 0 || filteredInactiveTokens?.length > 0 || isLoading ? ( From c5f2df4bc0f88abf66cd44b3fe43dd7b0dbac950 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Wed, 18 Oct 2023 14:03:55 -0400 Subject: [PATCH 52/61] fix: update USDbC to default USDC on Base (#7377) * fix: update usdbc to default usdc * add base to chainid logo lookup * remove nit * replace dai with usdbc --- src/constants/routing.ts | 3 ++- src/constants/tokens.ts | 9 ++++++--- src/hooks/useStablecoinPrice.ts | 2 ++ src/lib/hooks/transactions/updater.tsx | 1 + src/lib/hooks/useCurrencyLogoURIs.ts | 13 +++++++++++-- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/constants/routing.ts b/src/constants/routing.ts index 283e8a238eb..943fd9b78e4 100644 --- a/src/constants/routing.ts +++ b/src/constants/routing.ts @@ -20,6 +20,7 @@ import { OP, PORTAL_ETH_CELO, PORTAL_USDC_CELO, + USDbC_BASE, USDC_ARBITRUM, USDC_ARBITRUM_GOERLI, USDC_AVALANCHE, @@ -89,7 +90,7 @@ 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]: [nativeOnChain(ChainId.BASE), WRAPPED_NATIVE_CURRENCY[ChainId.BASE] as Token, USDC_BASE, USDbC_BASE], [ChainId.POLYGON]: [ nativeOnChain(ChainId.POLYGON), WETH_POLYGON, diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 05767faf3cd..5802786af25 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -66,13 +66,14 @@ export const PORTAL_USDC_CELO = new Token( 'USDCet', 'USDC (Portal from Ethereum)' ) -export const USDC_BASE = new Token( +export const USDbC_BASE = new Token( ChainId.BASE, '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', 6, - 'USD Base Coin', - 'USDbC' + 'USDbC', + 'USD Base Coin' ) +export const USDC_BASE = new Token(ChainId.BASE, '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', 6, 'USDC', 'USD Coin') export const DAI = new Token(ChainId.MAINNET, '0x6B175474E89094C44Da98b954EedeAC495271d0F', 18, 'DAI', 'Dai Stablecoin') export const DAI_ARBITRUM_ONE = new Token( @@ -103,6 +104,7 @@ export const DAI_POLYGON = new Token( 'DAI', 'Dai Stablecoin' ) + export const USDT_POLYGON = new Token( ChainId.POLYGON, '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', @@ -468,6 +470,7 @@ export const TOKEN_SHORTHANDS: { [shorthand: string]: { [chainId in ChainId]?: s [ChainId.POLYGON]: USDC_POLYGON.address, [ChainId.POLYGON_MUMBAI]: USDC_POLYGON_MUMBAI.address, [ChainId.BNB]: USDC_BSC.address, + [ChainId.BASE]: USDC_BASE.address, [ChainId.CELO]: PORTAL_USDC_CELO.address, [ChainId.CELO_ALFAJORES]: PORTAL_USDC_CELO.address, [ChainId.GOERLI]: USDC_GOERLI.address, diff --git a/src/hooks/useStablecoinPrice.ts b/src/hooks/useStablecoinPrice.ts index d20250c4d03..70851a0ef0e 100644 --- a/src/hooks/useStablecoinPrice.ts +++ b/src/hooks/useStablecoinPrice.ts @@ -10,6 +10,7 @@ import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_AVALANCHE, + USDC_BASE, USDC_MAINNET, USDC_POLYGON, USDT_BSC, @@ -25,6 +26,7 @@ const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } = { [ChainId.CELO]: CurrencyAmount.fromRawAmount(CUSD_CELO, 10_000e18), [ChainId.BNB]: CurrencyAmount.fromRawAmount(USDT_BSC, 100e18), [ChainId.AVALANCHE]: CurrencyAmount.fromRawAmount(USDC_AVALANCHE, 10_000e6), + [ChainId.BASE]: CurrencyAmount.fromRawAmount(USDC_BASE, 10_000e6), } /** diff --git a/src/lib/hooks/transactions/updater.tsx b/src/lib/hooks/transactions/updater.tsx index a27e24bacb7..c636f358ee0 100644 --- a/src/lib/hooks/transactions/updater.tsx +++ b/src/lib/hooks/transactions/updater.tsx @@ -39,6 +39,7 @@ const RETRY_OPTIONS_BY_CHAIN_ID: { [chainId: number]: RetryOptions } = { [ChainId.ARBITRUM_GOERLI]: { n: 10, minWait: 250, maxWait: 1000 }, [ChainId.OPTIMISM]: { n: 10, minWait: 250, maxWait: 1000 }, [ChainId.OPTIMISM_GOERLI]: { n: 10, minWait: 250, maxWait: 1000 }, + [ChainId.BASE]: { n: 10, minWait: 250, maxWait: 1000 }, } const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 1, minWait: 0, maxWait: 0 } diff --git a/src/lib/hooks/useCurrencyLogoURIs.ts b/src/lib/hooks/useCurrencyLogoURIs.ts index f3e693b22ee..e0e9fbf3a1c 100644 --- a/src/lib/hooks/useCurrencyLogoURIs.ts +++ b/src/lib/hooks/useCurrencyLogoURIs.ts @@ -10,7 +10,7 @@ import CeloLogo from '../../assets/svg/celo_logo.svg' import MaticLogo from '../../assets/svg/matic-token-icon.svg' import { isCelo, NATIVE_CHAIN_ID, nativeOnChain } from '../../constants/tokens' -type Network = 'ethereum' | 'arbitrum' | 'optimism' | 'polygon' | 'smartchain' | 'celo' | 'avalanchec' +type Network = 'ethereum' | 'arbitrum' | 'optimism' | 'polygon' | 'smartchain' | 'celo' | 'avalanchec' | 'base' export function chainIdToNetworkName(networkId: ChainId): Network { switch (networkId) { @@ -28,6 +28,8 @@ export function chainIdToNetworkName(networkId: ChainId): Network { return 'celo' case ChainId.AVALANCHE: return 'avalanchec' + case ChainId.BASE: + return 'base' default: return 'ethereum' } @@ -52,7 +54,14 @@ export function getNativeLogoURI(chainId: ChainId = ChainId.MAINNET): string { function getTokenLogoURI(address: string, chainId: ChainId = ChainId.MAINNET): string | void { const networkName = chainIdToNetworkName(chainId) - const networksWithUrls = [ChainId.ARBITRUM_ONE, ChainId.MAINNET, ChainId.OPTIMISM, ChainId.BNB, ChainId.AVALANCHE] + const networksWithUrls = [ + ChainId.ARBITRUM_ONE, + ChainId.MAINNET, + ChainId.OPTIMISM, + ChainId.BNB, + ChainId.AVALANCHE, + ChainId.BASE, + ] if (isCelo(chainId) && address === nativeOnChain(chainId).wrapped.address) { return CeloLogo } From 86fc15907a20bcfa08900891853b3b2d1a5891f6 Mon Sep 17 00:00:00 2001 From: cartcrom <39385577+cartcrom@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:34:37 -0400 Subject: [PATCH 53/61] refactor: consolidate percent formatting util (#7467) * refactor: consolidate percent formatting util * refactor: use generic percent formatter * fix: add descriptive comment for formatDelta --- .../AccountDrawer/AuthenticatedHeader.tsx | 4 +- .../MiniPortfolio/Tokens/index.tsx | 4 +- src/components/Charts/PriceChart/index.tsx | 4 +- .../CurrencyInputPanel/FiatValue.tsx | 4 +- src/components/NavBar/SuggestionRow.tsx | 4 +- .../Settings/MaxSlippageSettings/index.tsx | 18 ++-- src/components/Settings/MenuButton/index.tsx | 4 +- src/components/Tokens/TokenTable/TokenRow.tsx | 4 +- src/components/swap/PriceImpactModal.tsx | 4 +- src/components/swap/PriceImpactWarning.tsx | 4 +- src/components/swap/SwapLineItem.tsx | 15 +-- src/components/swap/SwapRoute.tsx | 4 +- .../SwapDetailsDropdown.test.tsx.snap | 2 +- .../__snapshots__/SwapLineItem.test.tsx.snap | 10 +- .../SwapModalFooter.test.tsx.snap | 2 +- src/pages/PoolDetails/PoolDetailsStats.tsx | 4 +- src/utils/formatNumbers.test.ts | 94 ++++++------------- src/utils/formatNumbers.ts | 46 +++------ 18 files changed, 91 insertions(+), 140 deletions(-) diff --git a/src/components/AccountDrawer/AuthenticatedHeader.tsx b/src/components/AccountDrawer/AuthenticatedHeader.tsx index db9c25f308b..a43e7ddefdf 100644 --- a/src/components/AccountDrawer/AuthenticatedHeader.tsx +++ b/src/components/AccountDrawer/AuthenticatedHeader.tsx @@ -161,7 +161,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account const clearCollectionFilters = useWalletCollections((state) => state.clearCollectionFilters) const isClaimAvailable = useIsNftClaimAvailable((state) => state.isClaimAvailable) const shouldShowBuyFiatButton = useIsNotOriginCountry('GB') - const { formatNumber, formatPercent } = useFormatter() + const { formatNumber, formatDelta } = useFormatter() const shouldDisableNFTRoutes = useDisableNFTRoutes() @@ -284,7 +284,7 @@ export default function AuthenticatedHeader({ account, openSettings }: { account {`${formatNumber({ input: Math.abs(absoluteChange as number), type: NumberType.PortfolioBalance, - })} (${formatPercent(percentChange)})`} + })} (${formatDelta(percentChange)})`} )} diff --git a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx index 8423b07a5b3..e512837d04c 100644 --- a/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx +++ b/src/components/AccountDrawer/MiniPortfolio/Tokens/index.tsx @@ -72,7 +72,7 @@ const TokenNameText = styled(ThemedText.SubHeader)` type PortfolioToken = NonNullable function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: TokenBalance & { token: PortfolioToken }) { - const { formatPercent } = useFormatter() + const { formatDelta } = useFormatter() const percentChange = tokenProjectMarket?.pricePercentChange?.value ?? 0 const navigate = useNavigate() @@ -124,7 +124,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok - {formatPercent(percentChange)} + {formatDelta(percentChange)} ) diff --git a/src/components/Charts/PriceChart/index.tsx b/src/components/Charts/PriceChart/index.tsx index a8a72fd3c6b..3562328dc81 100644 --- a/src/components/Charts/PriceChart/index.tsx +++ b/src/components/Charts/PriceChart/index.tsx @@ -52,11 +52,11 @@ interface ChartDeltaProps { function ChartDelta({ startingPrice, endingPrice, noColor }: ChartDeltaProps) { const delta = calculateDelta(startingPrice.value, endingPrice.value) - const { formatPercent } = useFormatter() + const { formatDelta } = useFormatter() return ( - {formatPercent(delta)} + {formatDelta(delta)} ) diff --git a/src/components/CurrencyInputPanel/FiatValue.tsx b/src/components/CurrencyInputPanel/FiatValue.tsx index 128d283b486..cf7a3bde540 100644 --- a/src/components/CurrencyInputPanel/FiatValue.tsx +++ b/src/components/CurrencyInputPanel/FiatValue.tsx @@ -22,7 +22,7 @@ export function FiatValue({ fiatValue: { data?: number; isLoading: boolean } priceImpact?: Percent }) { - const { formatNumber, formatPriceImpact } = useFormatter() + const { formatNumber, formatPercent } = useFormatter() const priceImpactColor = useMemo(() => { if (!priceImpact) return undefined @@ -54,7 +54,7 @@ export function FiatValue({ The estimated difference between the USD values of input and output amounts.} > - ({formatPriceImpact(priceImpact)}) + ({formatPercent(priceImpact.multiply(-1))}) )} diff --git a/src/components/NavBar/SuggestionRow.tsx b/src/components/NavBar/SuggestionRow.tsx index 61c15f2941e..ed581a6f122 100644 --- a/src/components/NavBar/SuggestionRow.tsx +++ b/src/components/NavBar/SuggestionRow.tsx @@ -129,7 +129,7 @@ interface TokenRowProps { export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, eventProperties }: TokenRowProps) => { const addRecentlySearchedAsset = useAddRecentlySearchedAsset() const navigate = useNavigate() - const { formatFiatPrice, formatPercent } = useFormatter() + const { formatFiatPrice, formatDelta } = useFormatter() const handleClick = useCallback(() => { const address = !token.address && token.standard === TokenStandard.Native ? 'NATIVE' : token.address @@ -194,7 +194,7 @@ export const TokenRow = ({ token, isHovered, setHoveredIndex, toggleOpen, index, - {formatPercent(Math.abs(token.market?.pricePercentChange?.value ?? 0))} + {formatDelta(Math.abs(token.market?.pricePercentChange?.value ?? 0))} diff --git a/src/components/Settings/MaxSlippageSettings/index.tsx b/src/components/Settings/MaxSlippageSettings/index.tsx index f65251befa4..e9c8f9786b9 100644 --- a/src/components/Settings/MaxSlippageSettings/index.tsx +++ b/src/components/Settings/MaxSlippageSettings/index.tsx @@ -38,23 +38,23 @@ const NUMBER_WITH_MAX_TWO_DECIMAL_PLACES = /^(?:\d*\.\d{0,2}|\d+)$/ const MINIMUM_RECOMMENDED_SLIPPAGE = new Percent(5, 10_000) const MAXIMUM_RECOMMENDED_SLIPPAGE = new Percent(1, 100) -function useFormatSlippageInput() { - const { formatSlippage } = useFormatter() +function useFormatPercentInput() { + const { formatPercent } = useFormatter() - return (slippage: Percent) => formatSlippage(slippage).slice(0, -1) // remove % sign + return (slippage: Percent) => formatPercent(slippage).slice(0, -1) // remove % sign } export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Percent }) { const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance() - const { formatSlippage } = useFormatter() - const formatSlippageInput = useFormatSlippageInput() + const { formatPercent } = useFormatter() + const formatPercentInput = useFormatPercentInput() // In order to trigger `custom` mode, we need to set `userSlippageTolerance` to a value that is not `auto`. // To do so, we use `autoSlippage` value. However, since users are likely to change that value, // we render it as a placeholder instead of a value. const defaultSlippageInputValue = userSlippageTolerance !== SlippageTolerance.Auto && !userSlippageTolerance.equalTo(autoSlippage) - ? formatSlippageInput(userSlippageTolerance) + ? formatPercentInput(userSlippageTolerance) : '' // If user has previously entered a custom slippage, we want to show that value in the input field @@ -124,7 +124,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe {userSlippageTolerance === SlippageTolerance.Auto ? ( Auto ) : ( - formatSlippage(userSlippageTolerance) + formatPercent(userSlippageTolerance) )} } @@ -158,7 +158,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe parseSlippageInput(e.target.value)} onBlur={() => { @@ -176,7 +176,7 @@ export default function MaxSlippageSettings({ autoSlippage }: { autoSlippage: Pe {tooLow ? ( - Slippage below {formatSlippage(MINIMUM_RECOMMENDED_SLIPPAGE)} may result in a failed transaction + Slippage below {formatPercent(MINIMUM_RECOMMENDED_SLIPPAGE)} may result in a failed transaction ) : ( Your transaction may be frontrun and result in an unfavorable trade. diff --git a/src/components/Settings/MenuButton/index.tsx b/src/components/Settings/MenuButton/index.tsx index 4a7a4c17261..f4b49eedaac 100644 --- a/src/components/Settings/MenuButton/index.tsx +++ b/src/components/Settings/MenuButton/index.tsx @@ -49,7 +49,7 @@ const IconContainerWithSlippage = styled(IconContainer)<{ displayWarning?: boole const ButtonContent = ({ trade }: { trade?: InterfaceTrade }) => { const [userSlippageTolerance] = useUserSlippageTolerance() - const { formatSlippage } = useFormatter() + const { formatPercent } = useFormatter() if (userSlippageTolerance === SlippageTolerance.Auto || isUniswapXTrade(trade)) { return ( @@ -64,7 +64,7 @@ const ButtonContent = ({ trade }: { trade?: InterfaceTrade }) => { return ( - {formatSlippage(userSlippageTolerance)} slippage + {formatPercent(userSlippageTolerance)} slippage diff --git a/src/components/Tokens/TokenTable/TokenRow.tsx b/src/components/Tokens/TokenTable/TokenRow.tsx index ee33e7b7b8e..e7e80feff98 100644 --- a/src/components/Tokens/TokenTable/TokenRow.tsx +++ b/src/components/Tokens/TokenTable/TokenRow.tsx @@ -442,7 +442,7 @@ interface LoadedRowProps { /* Loaded State: row component with token information */ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef) => { - const { formatFiatPrice, formatNumber, formatPercent } = useFormatter() + const { formatFiatPrice, formatNumber, formatDelta } = useFormatter() const { tokenListIndex, tokenListLength, token, sortRank } = props const filterString = useAtomValue(filterStringAtom) @@ -451,7 +451,7 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef @@ -56,7 +56,7 @@ export default function PriceImpactModal({ priceImpact, onDismiss, onContinue }: This transaction will result in a{' '} - {formatPriceImpact(priceImpact)} + ~{formatPercent(priceImpact)} {' '} price impact on the market price of this pool. Do you wish to continue? diff --git a/src/components/swap/PriceImpactWarning.tsx b/src/components/swap/PriceImpactWarning.tsx index 1cfb2e03a1d..26fd2f5fb06 100644 --- a/src/components/swap/PriceImpactWarning.tsx +++ b/src/components/swap/PriceImpactWarning.tsx @@ -20,7 +20,7 @@ interface PriceImpactWarningProps { } export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningProps) { - const { formatPriceImpact } = useFormatter() + const { formatPercent } = useFormatter() const theme = useTheme() return ( @@ -41,7 +41,7 @@ export default function PriceImpactWarning({ priceImpact }: PriceImpactWarningPr - {formatPriceImpact(priceImpact)} + ~{formatPercent(priceImpact)} diff --git a/src/components/swap/SwapLineItem.tsx b/src/components/swap/SwapLineItem.tsx index 92bf5397fc8..d3f7571fe31 100644 --- a/src/components/swap/SwapLineItem.tsx +++ b/src/components/swap/SwapLineItem.tsx @@ -103,9 +103,10 @@ function Loading({ width = 50 }: { width?: number }) { return } -function ColoredPercentRow({ percent }: { percent: Percent }) { - const { formatSlippage } = useFormatter() - return {formatSlippage(percent)} +function ColoredPercentRow({ percent, estimate }: { percent: Percent; estimate?: boolean }) { + const { formatPercent } = useFormatter() + const formattedPercent = (estimate ? '~' : '') + formatPercent(percent) + return {formattedPercent} } function CurrencyAmountRow({ amount }: { amount: CurrencyAmount }) { @@ -136,7 +137,7 @@ type LineItemData = { function useLineItem(props: SwapLineItemProps): LineItemData | undefined { const { trade, syncing, allowedSlippage, type } = props - const { formatNumber, formatSlippage } = useFormatter() + const { formatNumber, formatPercent } = useFormatter() const isAutoSlippage = useUserSlippageTolerance()[0] === SlippageTolerance.Auto const feesEnabled = useFeesEnabled() @@ -179,7 +180,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { return { Label: () => Price impact, TooltipBody: () => The impact your trade has on the market price of this pool., - Value: () => (isPreview ? : ), + Value: () => (isPreview ? : ), } case SwapLineItemType.MAX_SLIPPAGE: return { @@ -187,7 +188,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { TooltipBody: () => , Value: () => ( - {isAutoSlippage && } {formatSlippage(allowedSlippage)} + {isAutoSlippage && } {formatPercent(allowedSlippage)} ), } @@ -197,7 +198,7 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined { return { Label: () => ( <> - Fee {trade.swapFee && `(${formatSlippage(trade.swapFee.percent)})`} + Fee {trade.swapFee && `(${formatPercent(trade.swapFee.percent)})`} ), TooltipBody: () => , diff --git a/src/components/swap/SwapRoute.tsx b/src/components/swap/SwapRoute.tsx index 9fbebb663b8..38825bf8d78 100644 --- a/src/components/swap/SwapRoute.tsx +++ b/src/components/swap/SwapRoute.tsx @@ -32,12 +32,12 @@ function RouteLabel({ trade }: { trade: SubmittableTrade }) { } function PriceImpactRow({ trade }: { trade: ClassicTrade }) { - const { formatPriceImpact } = useFormatter() + const { formatPercent } = useFormatter() return ( Price Impact -
{formatPriceImpact(trade.priceImpact)}
+
{formatPercent(trade.priceImpact)}
) diff --git a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap index eeedc6b5081..19dea1fe1f5 100644 --- a/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapDetailsDropdown.test.tsx.snap @@ -347,7 +347,7 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = ` - -105566.373% + ~-105566.373%
diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index cc60fbf351a..0ac8eae6913 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -1661,7 +1661,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` - -105566.373% + ~-105566.373%
@@ -3328,7 +3328,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` - -105566.373% + ~-105566.373%
@@ -4995,7 +4995,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` - -105566.373% + ~-105566.373%
@@ -6870,7 +6870,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` - -105566.373% + ~-105566.373%
@@ -8745,7 +8745,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` - -105566.373% + ~-105566.373%
diff --git a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap index 3fff80cb44b..922a085afd6 100644 --- a/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalFooter.test.tsx.snap @@ -287,7 +287,7 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] = - -105566.373% + ~-105566.373%
diff --git a/src/pages/PoolDetails/PoolDetailsStats.tsx b/src/pages/PoolDetails/PoolDetailsStats.tsx index a72ebf15a27..cbf58f9c5aa 100644 --- a/src/pages/PoolDetails/PoolDetailsStats.tsx +++ b/src/pages/PoolDetails/PoolDetailsStats.tsx @@ -204,7 +204,7 @@ const StatItemText = styled(Text)` ` function StatItem({ title, value, delta }: { title: ReactNode; value: number; delta?: number }) { - const { formatNumber, formatPercent } = useFormatter() + const { formatNumber, formatDelta } = useFormatter() return ( @@ -219,7 +219,7 @@ function StatItem({ title, value, delta }: { title: ReactNode; value: number; de {!!delta && ( - {formatPercent(delta)} + {formatDelta(delta)} )} diff --git a/src/utils/formatNumbers.test.ts b/src/utils/formatNumbers.test.ts index ed281d0a22c..e36003ac008 100644 --- a/src/utils/formatNumbers.test.ts +++ b/src/utils/formatNumbers.test.ts @@ -356,71 +356,37 @@ describe('formatUSDPrice', () => { }) }) -describe('formatPriceImpact', () => { - beforeEach(() => { - mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) - mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) - }) - - it('should correctly format undefined', () => { - const { formatPriceImpact } = renderHook(() => useFormatter()).result.current - - expect(formatPriceImpact(undefined)).toBe('-') - }) - - it('correctly formats a percent with 3 significant digits', () => { - const { formatPriceImpact } = renderHook(() => useFormatter()).result.current - - expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0.001%') - expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0.100%') - expect(formatPriceImpact(new Percent(-1, 100))).toBe('1.000%') - expect(formatPriceImpact(new Percent(-1, 10))).toBe('10.000%') - expect(formatPriceImpact(new Percent(-1, 1))).toBe('100.000%') - }) - - it('correctly formats a percent with 3 significant digits with french locale', () => { - mocked(useActiveLocale).mockReturnValue('fr-FR') - const { formatPriceImpact } = renderHook(() => useFormatter()).result.current - - expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0,001%') - expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0,100%') - expect(formatPriceImpact(new Percent(-1, 100))).toBe('1,000%') - expect(formatPriceImpact(new Percent(-1, 10))).toBe('10,000%') - expect(formatPriceImpact(new Percent(-1, 1))).toBe('100,000%') - }) -}) - -describe('formatSlippage', () => { +describe('formatPercent', () => { beforeEach(() => { mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) }) it('should correctly format undefined', () => { - const { formatSlippage } = renderHook(() => useFormatter()).result.current + const { formatPercent } = renderHook(() => useFormatter()).result.current - expect(formatSlippage(undefined)).toBe('-') + expect(formatPercent(undefined)).toBe('-') }) it('correctly formats a percent with no trailing digits', () => { - const { formatSlippage } = renderHook(() => useFormatter()).result.current + const { formatPercent } = renderHook(() => useFormatter()).result.current - expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%') - expect(formatSlippage(new Percent(1, 1000))).toBe('0.1%') - expect(formatSlippage(new Percent(1, 100))).toBe('1%') - expect(formatSlippage(new Percent(1, 10))).toBe('10%') - expect(formatSlippage(new Percent(1, 1))).toBe('100%') + expect(formatPercent(new Percent(1, 100000))).toBe('0.001%') + expect(formatPercent(new Percent(1, 1000))).toBe('0.1%') + expect(formatPercent(new Percent(1, 100))).toBe('1%') + expect(formatPercent(new Percent(1, 10))).toBe('10%') + expect(formatPercent(new Percent(1, 1))).toBe('100%') }) it('correctly formats a percent with french locale', () => { mocked(useActiveLocale).mockReturnValue('fr-FR') - const { formatSlippage } = renderHook(() => useFormatter()).result.current + const { formatPercent } = renderHook(() => useFormatter()).result.current - expect(formatSlippage(new Percent(1, 100000))).toBe('0,001%') - expect(formatSlippage(new Percent(1, 1000))).toBe('0,1%') - expect(formatSlippage(new Percent(1, 100))).toBe('1%') - expect(formatSlippage(new Percent(1, 10))).toBe('10%') - expect(formatSlippage(new Percent(1, 1))).toBe('100%') + expect(formatPercent(new Percent(1, 100000))).toBe('0,001%') + expect(formatPercent(new Percent(1, 1000))).toBe('0,1%') + expect(formatPercent(new Percent(1, 100))).toBe('1%') + expect(formatPercent(new Percent(1, 10))).toBe('10%') + expect(formatPercent(new Percent(1, 1))).toBe('100%') }) }) @@ -461,36 +427,36 @@ describe('formatReviewSwapCurrencyAmount', () => { }) }) -describe('formatPercent', () => { +describe('formatDelta', () => { beforeEach(() => { mocked(useLocalCurrencyConversionRate).mockReturnValue({ data: 1.0, isLoading: false }) mocked(useCurrencyConversionFlagEnabled).mockReturnValue(true) }) it.each([[null], [undefined], [Infinity], [NaN]])('should correctly format %p', (value) => { - const { formatPercent } = renderHook(() => useFormatter()).result.current + const { formatDelta } = renderHook(() => useFormatter()).result.current - expect(formatPercent(value)).toBe('-') + expect(formatDelta(value)).toBe('-') }) it('correctly formats a percent with 2 decimal places', () => { - const { formatPercent } = renderHook(() => useFormatter()).result.current + const { formatDelta } = renderHook(() => useFormatter()).result.current - expect(formatPercent(0)).toBe('0.00%') - expect(formatPercent(0.1)).toBe('0.10%') - expect(formatPercent(1)).toBe('1.00%') - expect(formatPercent(10)).toBe('10.00%') - expect(formatPercent(100)).toBe('100.00%') + expect(formatDelta(0)).toBe('0.00%') + expect(formatDelta(0.1)).toBe('0.10%') + expect(formatDelta(1)).toBe('1.00%') + expect(formatDelta(10)).toBe('10.00%') + expect(formatDelta(100)).toBe('100.00%') }) it('correctly formats a percent with 2 decimal places in french locale', () => { mocked(useActiveLocale).mockReturnValue('fr-FR') - const { formatPercent } = renderHook(() => useFormatter()).result.current + const { formatDelta } = renderHook(() => useFormatter()).result.current - expect(formatPercent(0)).toBe('0,00%') - expect(formatPercent(0.1)).toBe('0,10%') - expect(formatPercent(1)).toBe('1,00%') - expect(formatPercent(10)).toBe('10,00%') - expect(formatPercent(100)).toBe('100,00%') + expect(formatDelta(0)).toBe('0,00%') + expect(formatDelta(0.1)).toBe('0,10%') + expect(formatDelta(1)).toBe('1,00%') + expect(formatDelta(10)).toBe('10,00%') + expect(formatDelta(100)).toBe('100,00%') }) }) diff --git a/src/utils/formatNumbers.ts b/src/utils/formatNumbers.ts index 33644aed42c..f3bc8656dc1 100644 --- a/src/utils/formatNumbers.ts +++ b/src/utils/formatNumbers.ts @@ -466,31 +466,22 @@ function formatCurrencyAmount({ }) } -function formatPriceImpact(priceImpact: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE): string { - if (!priceImpact) return '-' +function formatPercent(percent: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE) { + if (!percent) return '-' - return `${Number(priceImpact.multiply(-1).toFixed(3)).toLocaleString(locale, { - minimumFractionDigits: 3, + return `${Number(percent.toFixed(3)).toLocaleString(locale, { maximumFractionDigits: 3, useGrouping: false, })}%` } -function formatSlippage(slippage: Percent | undefined, locale: SupportedLocale = DEFAULT_LOCALE) { - if (!slippage) return '-' - - return `${Number(slippage.toFixed(3)).toLocaleString(locale, { - maximumFractionDigits: 3, - useGrouping: false, - })}%` -} - -function formatPercent(percent: Nullish, locale: SupportedLocale = DEFAULT_LOCALE) { - if (percent === null || percent === undefined || percent === Infinity || isNaN(percent)) { +// Used to format floats representing percent change with fixed decimal places +function formatDelta(delta: Nullish, locale: SupportedLocale = DEFAULT_LOCALE) { + if (delta === null || delta === undefined || delta === Infinity || isNaN(delta)) { return '-' } - return `${Number(Math.abs(percent).toFixed(2)).toLocaleString(locale, { + return `${Number(Math.abs(delta).toFixed(2)).toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2, useGrouping: false, @@ -694,21 +685,11 @@ export function useFormatter() { [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] ) - const formatPriceImpactWithLocales = useCallback( - (priceImpact: Percent | undefined) => formatPriceImpact(priceImpact, formatterLocale), - [formatterLocale] - ) - const formatReviewSwapCurrencyAmountWithLocales = useCallback( (amount: CurrencyAmount) => formatReviewSwapCurrencyAmount(amount, formatterLocale), [formatterLocale] ) - const formatSlippageWithLocales = useCallback( - (slippage: Percent | undefined) => formatSlippage(slippage, formatterLocale), - [formatterLocale] - ) - const formatTickPriceWithLocales = useCallback( (options: Omit) => formatTickPrice({ @@ -742,8 +723,13 @@ export function useFormatter() { [currencyToFormatWith, formatterLocale, localCurrencyConversionRateToFormatWith] ) + const formatDeltaWithLocales = useCallback( + (percent: Nullish) => formatDelta(percent, formatterLocale), + [formatterLocale] + ) + const formatPercentWithLocales = useCallback( - (percent: Nullish) => formatPercent(percent, formatterLocale), + (percent: Percent | undefined) => formatPercent(percent, formatterLocale), [formatterLocale] ) @@ -753,11 +739,10 @@ export function useFormatter() { formatFiatPrice: formatFiatPriceWithLocales, formatNumber: formatNumberWithLocales, formatNumberOrString: formatNumberOrStringWithLocales, + formatDelta: formatDeltaWithLocales, formatPercent: formatPercentWithLocales, formatPrice: formatPriceWithLocales, - formatPriceImpact: formatPriceImpactWithLocales, formatReviewSwapCurrencyAmount: formatReviewSwapCurrencyAmountWithLocales, - formatSlippage: formatSlippageWithLocales, formatTickPrice: formatTickPriceWithLocales, }), [ @@ -765,11 +750,10 @@ export function useFormatter() { formatFiatPriceWithLocales, formatNumberOrStringWithLocales, formatNumberWithLocales, + formatDeltaWithLocales, formatPercentWithLocales, - formatPriceImpactWithLocales, formatPriceWithLocales, formatReviewSwapCurrencyAmountWithLocales, - formatSlippageWithLocales, formatTickPriceWithLocales, ] ) From aada666c1aba47c69663d9b142f0840637d1c328 Mon Sep 17 00:00:00 2001 From: Zach Pomerantz Date: Wed, 18 Oct 2023 12:44:52 -0700 Subject: [PATCH 54/61] fix: de-flake Cypress through various means (#7488) * build: reduce retries to discourage flakes * fix: lazy-load asset logos * chore: simplify logging test * fix: guard against dutch orders for pricing * test: only stub non-pricing quotes * fix: opt in flicker * test: mock statsig --- cypress.config.ts | 2 +- ...wapFlowLogging.test.ts => logging.test.ts} | 0 cypress/e2e/swap/uniswapx.test.ts | 46 ++++++++++++++++--- cypress/support/commands.ts | 8 +--- cypress/support/setupTests.ts | 3 ++ src/components/Logo/AssetLogo.tsx | 1 + .../__snapshots__/CommonBases.test.tsx.snap | 6 +++ .../__snapshots__/SwapLineItem.test.tsx.snap | 20 ++++++++ .../SwapModalHeader.test.tsx.snap | 8 ++++ src/components/swap/styled.tsx | 5 +- src/hooks/useStablecoinPrice.ts | 5 +- src/hooks/useUSDPrice.ts | 13 ++++-- .../Landing/__snapshots__/index.test.tsx.snap | 2 + .../PoolDetailsStats.test.tsx.snap | 2 + .../__snapshots__/index.test.tsx.snap | 2 + src/pages/Swap/UniswapXOptIn.tsx | 14 +----- 16 files changed, 102 insertions(+), 35 deletions(-) rename cypress/e2e/swap/{swapFlowLogging.test.ts => logging.test.ts} (100%) diff --git a/cypress.config.ts b/cypress.config.ts index d2491fe956a..2d8a9bc626d 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ defaultCommandTimeout: 24000, // 2x average block time chromeWebSecurity: false, experimentalMemoryManagement: true, // better memory management, see https://github.com/cypress-io/cypress/pull/25462 - retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 2 }, + retries: { runMode: process.env.CYPRESS_RETRIES ? +process.env.CYPRESS_RETRIES : 1 }, video: false, // GH provides 2 CPUs, and cypress video eats one up, see https://github.com/cypress-io/cypress/issues/20468#issuecomment-1307608025 e2e: { async setupNodeEvents(on, config) { diff --git a/cypress/e2e/swap/swapFlowLogging.test.ts b/cypress/e2e/swap/logging.test.ts similarity index 100% rename from cypress/e2e/swap/swapFlowLogging.test.ts rename to cypress/e2e/swap/logging.test.ts diff --git a/cypress/e2e/swap/uniswapx.test.ts b/cypress/e2e/swap/uniswapx.test.ts index d42981047f3..fa5e358b1c5 100644 --- a/cypress/e2e/swap/uniswapx.test.ts +++ b/cypress/e2e/swap/uniswapx.test.ts @@ -1,18 +1,36 @@ import { ChainId, CurrencyAmount } from '@uniswap/sdk-core' +import { CyHttpMessages } from 'cypress/types/net-stubbing' import { FeatureFlag } from 'featureFlags' import { DAI, nativeOnChain, USDC_MAINNET } from '../../../src/constants/tokens' import { getTestSelector } from '../../utils' -const QuoteEndpoint = 'https://api.uniswap.org/v2/quote' const QuoteWhereUniswapXIsBetter = 'uniswapx/quote1.json' const QuoteWithEthInput = 'uniswapx/quote2.json' +const QuoteEndpoint = 'https://api.uniswap.org/v2/quote' const OrderSubmissionEndpoint = 'https://api.uniswap.org/v2/order' - const OrderStatusEndpoint = 'https://api.uniswap.org/v2/orders?swapper=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266&orderHashes=0xa9dd6f05ad6d6c79bee654c31ede4d0d2392862711be0f3bc4a9124af24a6a19' +/** + * Stubs quote to return a quote for non-price requests + * Price quotes are blocked with 409, as the backend would not accept them regardless + */ +function stubNonPriceQuoteWith(fixture: string) { + cy.intercept(QuoteEndpoint, (req: CyHttpMessages.IncomingHttpRequest) => { + let body = req.body + if (typeof body === 'string') { + body = JSON.parse(body) + } + if (body.intent === 'pricing') { + req.reply({ statusCode: 409 }) + } else { + req.reply({ fixture }) + } + }).as('quote') +} + /** Stubs the provider to return a tx receipt corresponding to the mock filled uniswapx order's txHash */ function stubSwapTxReceipt() { cy.hardhat().then((hardhat) => { @@ -26,15 +44,16 @@ function stubSwapTxReceipt() { describe('UniswapX Toggle', () => { beforeEach(() => { - cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter }) + stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter) cy.visit(`/swap/?inputCurrency=${USDC_MAINNET.address}&outputCurrency=${DAI.address}`, { featureFlags: [{ name: FeatureFlag.uniswapXDefaultEnabled, value: false }], }) }) - it('only displays uniswapx ui when setting is on', () => { + it('displays uniswapx ui when setting is on', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('300') + cy.wait('@quote') // UniswapX UI should not be visible cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist') @@ -49,6 +68,7 @@ describe('UniswapX Toggle', () => { it('prompts opt-in if UniswapX is better', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('300') + cy.wait('@quote') // UniswapX should not display in gas estimate row before opt-in cy.get(getTestSelector('gas-estimate-uniswapx-icon')).should('not.exist') @@ -72,7 +92,7 @@ describe('UniswapX Toggle', () => { describe('UniswapX Orders', () => { beforeEach(() => { - cy.intercept(QuoteEndpoint, { fixture: QuoteWhereUniswapXIsBetter }) + stubNonPriceQuoteWith(QuoteWhereUniswapXIsBetter) cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' }) cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' }) @@ -87,6 +107,8 @@ describe('UniswapX Orders', () => { it('can swap exact-in trades using uniswapX', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('300') + cy.wait('@quote') + cy.contains('Try it now').click() // Submit uniswapx order signature @@ -106,6 +128,8 @@ describe('UniswapX Orders', () => { it('can swap exact-out trades using uniswapX', () => { // Setup a swap cy.get('#swap-currency-output .token-amount-input').type('300') + cy.wait('@quote') + cy.contains('Try it now').click() // Submit uniswapx order signature @@ -125,6 +149,8 @@ describe('UniswapX Orders', () => { it('renders proper view if uniswapx order expires', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('300') + cy.wait('@quote') + cy.contains('Try it now').click() // Submit uniswapx order signature @@ -141,6 +167,8 @@ describe('UniswapX Orders', () => { it('renders proper view if uniswapx order has insufficient funds', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('300') + cy.wait('@quote') + cy.contains('Try it now').click() // Submit uniswapx order signature @@ -157,7 +185,7 @@ describe('UniswapX Orders', () => { describe('UniswapX Eth Input', () => { beforeEach(() => { - cy.intercept(QuoteEndpoint, { fixture: QuoteWithEthInput }) + stubNonPriceQuoteWith(QuoteWithEthInput) cy.intercept(OrderSubmissionEndpoint, { fixture: 'uniswapx/orderResponse.json' }) cy.intercept(OrderStatusEndpoint, { fixture: 'uniswapx/openStatusResponse.json' }) @@ -177,6 +205,8 @@ describe('UniswapX Eth Input', () => { it('can swap using uniswapX with ETH as input', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('1') + + cy.wait('@quote') cy.contains('Try it now').click() // Prompt ETH wrap to use for order @@ -209,6 +239,8 @@ describe('UniswapX Eth Input', () => { it('switches swap input to WETH after wrap', () => { // Setup a swap cy.get('#swap-currency-input .token-amount-input').type('1') + cy.wait('@quote') + cy.contains('Try it now').click() // Prompt ETH wrap and confirm @@ -344,7 +376,7 @@ describe('UniswapX activity history', () => { // Open activity history cy.get(getTestSelector('mini-portfolio-navbar')).contains('Activity').click() - // Ensure gql and local order have been deduped, such that there is only one swap activity listed + // Ensure gql and local order have been deduped, such that there is one swap activity listed cy.get(getTestSelector('activity-content')).contains('Swapped').should('have.length', 1) }) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 3c7659f279e..df81ac09763 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -74,18 +74,14 @@ Cypress.Commands.overwrite( } ) -Cypress.Commands.add('waitForAmplitudeEvent', (eventName, timeout = 5000 /* 5s */) => { - const startTime = new Date().getTime() - +Cypress.Commands.add('waitForAmplitudeEvent', (eventName) => { function checkRequest() { - return cy.wait('@amplitude', { timeout }).then((interception) => { + return cy.wait('@amplitude').then((interception) => { const events = interception.request.body.events const event = events.find((event: any) => event.event_type === eventName) if (event) { return cy.wrap(event) - } else if (new Date().getTime() - startTime > timeout) { - throw new Error(`Event ${eventName} not found within the specified timeout`) } else { return checkRequest() } diff --git a/cypress/support/setupTests.ts b/cypress/support/setupTests.ts index 7ae34a206dc..fac176c6f3d 100644 --- a/cypress/support/setupTests.ts +++ b/cypress/support/setupTests.ts @@ -34,6 +34,9 @@ beforeEach(() => { ) }).intercept('https://*.sentry.io', { statusCode: 200 }) + // Mock statsig to allow us to mock flags. + cy.intercept(/statsig/, { statusCode: 409 }) + // Mock our own token list responses to avoid the latency of IPFS. cy.intercept('https://gateway.ipfs.io/ipns/tokens.uniswap.org', TokenListJSON) .intercept('https://gateway.ipfs.io/ipns/extendedtokens.uniswap.org', { statusCode: 404 }) diff --git a/src/components/Logo/AssetLogo.tsx b/src/components/Logo/AssetLogo.tsx index ad8fa1e0177..e302cb060ee 100644 --- a/src/components/Logo/AssetLogo.tsx +++ b/src/components/Logo/AssetLogo.tsx @@ -79,6 +79,7 @@ export default function AssetLogo({ onLoad={() => void setImgLoaded(true)} onError={nextSrc} imgLoaded={imgLoaded} + loading="lazy" /> ) : ( diff --git a/src/components/SearchModal/__snapshots__/CommonBases.test.tsx.snap b/src/components/SearchModal/__snapshots__/CommonBases.test.tsx.snap index 98a88023977..14c274dc6fc 100644 --- a/src/components/SearchModal/__snapshots__/CommonBases.test.tsx.snap +++ b/src/components/SearchModal/__snapshots__/CommonBases.test.tsx.snap @@ -105,6 +105,7 @@ exports[`CommonBases renders without crashing 1`] = ` ETH logo
@@ -130,6 +131,7 @@ exports[`CommonBases renders without crashing 1`] = ` DAI logo
@@ -155,6 +157,7 @@ exports[`CommonBases renders without crashing 1`] = ` USDC logo
@@ -180,6 +183,7 @@ exports[`CommonBases renders without crashing 1`] = ` USDT logo
@@ -205,6 +209,7 @@ exports[`CommonBases renders without crashing 1`] = ` WBTC logo
@@ -230,6 +235,7 @@ exports[`CommonBases renders without crashing 1`] = ` WETH logo
diff --git a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap index 0ac8eae6913..4daf2a27fc7 100644 --- a/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapLineItem.test.tsx.snap @@ -2762,6 +2762,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ABC logo
@@ -2828,6 +2829,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` DEF logo
@@ -2846,6 +2848,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` ABC logo
@@ -2887,6 +2890,7 @@ exports[`SwapLineItem.tsx exact input 1`] = ` DEF logo
@@ -4429,6 +4433,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ABC logo
@@ -4495,6 +4500,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` DEF logo
@@ -4513,6 +4519,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` ABC logo
@@ -4554,6 +4561,7 @@ exports[`SwapLineItem.tsx exact input api 1`] = ` DEF logo
@@ -6096,6 +6104,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ABC logo
@@ -6162,6 +6171,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` GHI logo
@@ -6180,6 +6190,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` ABC logo
@@ -6221,6 +6232,7 @@ exports[`SwapLineItem.tsx exact output 1`] = ` GHI logo
@@ -7971,6 +7983,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ABC logo
@@ -8037,6 +8050,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` DEF logo
@@ -8055,6 +8069,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` ABC logo
@@ -8096,6 +8111,7 @@ exports[`SwapLineItem.tsx fee on buy 1`] = ` DEF logo
@@ -9846,6 +9862,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ABC logo
@@ -9912,6 +9929,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` DEF logo
@@ -9930,6 +9948,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` ABC logo
@@ -9971,6 +9990,7 @@ exports[`SwapLineItem.tsx fee on sell 1`] = ` DEF logo
diff --git a/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap b/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap index b0cf7d1fbe7..e1d366f2605 100644 --- a/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap +++ b/src/components/swap/__snapshots__/SwapModalHeader.test.tsx.snap @@ -165,6 +165,7 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = ABC logo
@@ -213,6 +214,7 @@ exports[`SwapModalHeader.tsx matches base snapshot, test trade exact input 1`] = DEF logo
@@ -388,6 +390,7 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s ETH logo
@@ -436,6 +439,7 @@ exports[`SwapModalHeader.tsx renders ETH input token for an ETH input UniswapX s DEF logo
@@ -611,6 +615,7 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = ` DEF logo
@@ -659,6 +664,7 @@ exports[`SwapModalHeader.tsx renders preview trades with loading states 1`] = ` DEF logo
@@ -834,6 +840,7 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = ` ABC logo
@@ -882,6 +889,7 @@ exports[`SwapModalHeader.tsx test trade exact output, no recipient 1`] = ` GHI logo
diff --git a/src/components/swap/styled.tsx b/src/components/swap/styled.tsx index d8c1dcc7c05..2176852a958 100644 --- a/src/components/swap/styled.tsx +++ b/src/components/swap/styled.tsx @@ -107,14 +107,13 @@ const UniswapXShineInner = styled.div` ` // overflow hidden to hide the SwapMustacheShadow -export const SwapOptInSmallContainer = styled.div<{ visible: boolean; shouldAnimate: boolean }>` +export const SwapOptInSmallContainer = styled.div<{ visible: boolean }>` visibility: ${({ visible }) => (visible ? 'visible' : 'hidden')}; overflow: hidden; margin-top: -14px; transform: translateY(${({ visible }) => (visible ? 0 : -80)}px); transition: all ease 400ms; - animation: ${({ visible, shouldAnimate }) => - !shouldAnimate ? '' : visible ? `spring-down 900ms ease forwards` : 'back-up 200ms ease forwards'}; + animation: ${({ visible }) => (visible ? `spring-down 900ms ease forwards` : 'back-up 200ms ease forwards')}; ${springDownKeyframes} ${backUpKeyframes} diff --git a/src/hooks/useStablecoinPrice.ts b/src/hooks/useStablecoinPrice.ts index 70851a0ef0e..695f773d83e 100644 --- a/src/hooks/useStablecoinPrice.ts +++ b/src/hooks/useStablecoinPrice.ts @@ -2,7 +2,7 @@ import { ChainId, Currency, CurrencyAmount, Price, Token, TradeType } from '@uni import { useWeb3React } from '@web3-react/core' import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount' import { useMemo, useRef } from 'react' -import { INTERNAL_ROUTER_PREFERENCE_PRICE } from 'state/routing/types' +import { ClassicTrade, INTERNAL_ROUTER_PREFERENCE_PRICE } from 'state/routing/types' import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade' import { @@ -55,7 +55,8 @@ export default function useStablecoinPrice(currency?: Currency): Price
@@ -4548,6 +4549,7 @@ exports[`disable nft on landing page renders nft information and card 1`] = ` ETH logo
diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap index 03b49fca7ba..8f406f71a0f 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsStats.test.tsx.snap @@ -255,6 +255,7 @@ exports[`PoolDetailsStats pool balance chart not visible on mobile 1`] = ` UNKNOWN logo
@@ -274,6 +275,7 @@ exports[`PoolDetailsStats pool balance chart not visible on mobile 1`] = ` WETH logo
diff --git a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap index 1aeec655d20..81a57f01ea9 100644 --- a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap @@ -911,6 +911,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the UNKNOWN logo
@@ -985,6 +986,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the WETH logo
diff --git a/src/pages/Swap/UniswapXOptIn.tsx b/src/pages/Swap/UniswapXOptIn.tsx index 0ea44934d4a..f75480ad6f2 100644 --- a/src/pages/Swap/UniswapXOptIn.tsx +++ b/src/pages/Swap/UniswapXOptIn.tsx @@ -15,7 +15,7 @@ import { UniswapXShine, } from 'components/swap/styled' import { formatCommonPropertiesForTrade } from 'lib/utils/analytics' -import { PropsWithChildren, useEffect, useRef, useState } from 'react' +import { PropsWithChildren, useRef, useState } from 'react' import { X } from 'react-feather' import { useLocation } from 'react-router-dom' import { Text } from 'rebass' @@ -76,16 +76,6 @@ const OptInContents = ({ const isVisible = isOnClassic const location = useLocation() - // adding this as we need to mount and then set shouldAnimate = true after it mounts to avoid a flicker on initial mount - const [shouldAnimate, setShouldAnimate] = useState(false) - - useEffect(() => { - if (!isVisible || shouldAnimate) return - // delay visible animation a bit - const tm = setTimeout(() => setShouldAnimate(true), 350) - return () => clearTimeout(tm) - }, [isVisible, shouldAnimate]) - const tryItNowElement = ( + From 5357c58ac916ddf926f9d571ee9eb2b07e856660 Mon Sep 17 00:00:00 2001 From: Jack Short Date: Wed, 18 Oct 2023 13:01:47 -0700 Subject: [PATCH 55/61] chore: removing german from supported languages (#7490) * chore: removing german from supported languages * updating tests * Update src/utils/formatNumbers.test.ts Co-authored-by: Charles Bachmeier --------- Co-authored-by: Charles Bachmeier --- lingui.config.ts | 1 - src/constants/locales.ts | 2 -- src/lib/utils/formatLocaleNumber.test.ts | 1 - src/utils/formatNumbers.test.ts | 8 ++++---- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lingui.config.ts b/lingui.config.ts index dd9dba09b33..289ebbbb1aa 100644 --- a/lingui.config.ts +++ b/lingui.config.ts @@ -28,7 +28,6 @@ const linguiConfig = { 'ca-ES', 'cs-CZ', 'da-DK', - 'de-DE', 'el-GR', 'en-US', 'es-ES', diff --git a/src/constants/locales.ts b/src/constants/locales.ts index 4ac3da9af6e..1c5f44eb459 100644 --- a/src/constants/locales.ts +++ b/src/constants/locales.ts @@ -6,7 +6,6 @@ export const SUPPORTED_LOCALES = [ 'ca-ES', 'cs-CZ', 'da-DK', - 'de-DE', 'el-GR', 'es-ES', 'fi-FI', @@ -44,7 +43,6 @@ export const LOCALE_LABEL: { [locale in SupportedLocale]: string } = { 'ca-ES': 'Català', 'cs-CZ': 'čeština', 'da-DK': 'dansk', - 'de-DE': 'Deutsch', 'el-GR': 'ελληνικά', 'en-US': 'English', 'es-ES': 'Español', diff --git a/src/lib/utils/formatLocaleNumber.test.ts b/src/lib/utils/formatLocaleNumber.test.ts index 01563c99fd7..856bb0c7755 100644 --- a/src/lib/utils/formatLocaleNumber.test.ts +++ b/src/lib/utils/formatLocaleNumber.test.ts @@ -33,7 +33,6 @@ function expectedOutput(l: SupportedLocale): string { return `4 000 000,123` case 'ca-ES': case 'da-DK': - case 'de-DE': case 'el-GR': case 'es-ES': case 'id-ID': diff --git a/src/utils/formatNumbers.test.ts b/src/utils/formatNumbers.test.ts index e36003ac008..c8164d4e254 100644 --- a/src/utils/formatNumbers.test.ts +++ b/src/utils/formatNumbers.test.ts @@ -33,12 +33,12 @@ describe('formatNumber', () => { expect(formatNumber({ input: 0, type: NumberType.TokenNonTx })).toBe('0') }) - it('formats token reference numbers correctly with deutsch locale', () => { - mocked(useActiveLocale).mockReturnValue('de-DE') + it('formats token reference numbers correctly with Dutch locale', () => { + mocked(useActiveLocale).mockReturnValue('nl-NL') const { formatNumber } = renderHook(() => useFormatter()).result.current - expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx })).toBe('>999\xa0Bio.') - expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx })).toBe('1,00\xa0Mio.') + expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx })).toBe('>999\xa0bln.') + expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx })).toBe('1,00\xa0mln.') expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx })).toBe('1.234,00') expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx })).toBe('0,009') expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx })).toBe('0,090') From 819e2f571287d4b96d2f54cc78c194cb5f28d45c Mon Sep 17 00:00:00 2001 From: Nate Wienert Date: Thu, 19 Oct 2023 13:05:26 -0400 Subject: [PATCH 56/61] fix: fix contrast on l2 network text (#7466) * fix: fix contrast on l2 network text --- .../TransactionConfirmationModal/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index 35316567cde..80410226c36 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -193,6 +193,10 @@ export function ConfirmationModalContent({ ) } +const StyledL2Badge = styled(Badge)` + padding: 6px 8px; +` + function L2Content({ onDismiss, chainId, @@ -225,12 +229,12 @@ function L2Content({ {!inline && ( - - + + - {info.label} + {info.label} - + )} From b02352e8bf767c3d251897c6ece62189730c78a8 Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:38:16 -0700 Subject: [PATCH 57/61] fix: dont try to log unserializable objects (#7497) * fix: dont log unserializable objects to amplitude * fix: add more fields * fix: nits * fix: add chainid --- src/hooks/useContract.ts | 11 ++++++++--- src/hooks/useSwapTaxes.ts | 5 ++++- src/lib/hooks/useCurrency.ts | 8 +++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/hooks/useContract.ts b/src/hooks/useContract.ts index 77de1a6e9d3..4ab9e414f48 100644 --- a/src/hooks/useContract.ts +++ b/src/hooks/useContract.ts @@ -150,7 +150,7 @@ export function useMainnetInterfaceMulticall() { } export function useV3NFTPositionManagerContract(withSignerIfPossible?: boolean): NonfungiblePositionManager | null { - const { account } = useWeb3React() + const { account, chainId } = useWeb3React() const contract = useContract( NONFUNGIBLE_POSITION_MANAGER_ADDRESSES, NFTPositionManagerABI, @@ -160,10 +160,15 @@ export function useV3NFTPositionManagerContract(withSignerIfPossible?: boolean): if (contract && account) { sendAnalyticsEvent(InterfaceEventName.WALLET_PROVIDER_USED, { source: 'useV3NFTPositionManagerContract', - contract, + contract: { + name: 'V3NonfungiblePositionManager', + address: contract.address, + withSignerIfPossible, + chainId, + }, }) } - }, [account, contract]) + }, [account, chainId, contract, withSignerIfPossible]) return contract } diff --git a/src/hooks/useSwapTaxes.ts b/src/hooks/useSwapTaxes.ts index d82f5d759e8..384fddaeb9e 100644 --- a/src/hooks/useSwapTaxes.ts +++ b/src/hooks/useSwapTaxes.ts @@ -20,7 +20,10 @@ function useFeeOnTransferDetectorContract(): FeeOnTransferDetector | null { if (contract && account) { sendAnalyticsEvent(InterfaceEventName.WALLET_PROVIDER_USED, { source: 'useFeeOnTransferDetectorContract', - contract, + contract: { + name: 'FeeOnTransferDetector', + address: FEE_ON_TRANSFER_DETECTOR_ADDRESS, + }, }) } }, [account, contract]) diff --git a/src/lib/hooks/useCurrency.ts b/src/lib/hooks/useCurrency.ts index acdb9480d78..24a607fd444 100644 --- a/src/lib/hooks/useCurrency.ts +++ b/src/lib/hooks/useCurrency.ts @@ -89,7 +89,13 @@ export function useTokenFromMapOrNetwork(tokens: TokenMap, tokenAddress?: string if (tokenFromNetwork) { sendAnalyticsEvent(InterfaceEventName.WALLET_PROVIDER_USED, { source: 'useTokenFromActiveNetwork', - token: tokenFromNetwork, + token: { + name: tokenFromNetwork?.name, + symbol: tokenFromNetwork?.symbol, + address: tokenFromNetwork?.address, + isNative: tokenFromNetwork?.isNative, + chainId: tokenFromNetwork?.chainId, + }, }) } }, [tokenFromNetwork]) From 36242d14b0e41f217df5dcb0a89663b7e491790b Mon Sep 17 00:00:00 2001 From: eddie <66155195+just-toby@users.noreply.github.com> Date: Thu, 19 Oct 2023 13:49:13 -0700 Subject: [PATCH 58/61] feat: add temporary logging to swap_signed (#7472) --- src/hooks/useUniswapXSwapCallback.ts | 12 +++++++++--- src/hooks/useUniversalRouter.ts | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/hooks/useUniswapXSwapCallback.ts b/src/hooks/useUniswapXSwapCallback.ts index d4b892c7bd7..d0cff4cc102 100644 --- a/src/hooks/useUniswapXSwapCallback.ts +++ b/src/hooks/useUniswapXSwapCallback.ts @@ -1,11 +1,12 @@ import { BigNumber } from '@ethersproject/bignumber' import * as Sentry from '@sentry/react' -import { SwapEventName } from '@uniswap/analytics-events' +import { CustomUserProperties, SwapEventName } from '@uniswap/analytics-events' import { Percent } from '@uniswap/sdk-core' import { DutchOrder, DutchOrderBuilder } from '@uniswap/uniswapx-sdk' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, useTrace } from 'analytics' import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' +import { getConnection } from 'connection' import { formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { useCallback } from 'react' import { DutchOrderTrade, TradeFillType } from 'state/routing/types' @@ -13,6 +14,7 @@ import { trace } from 'tracing/trace' import { SignatureExpiredError, UserRejectedRequestError } from 'utils/errors' import { signTypedData } from 'utils/signing' import { didUserReject, swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage' +import { getWalletMeta } from 'utils/walletMeta' type DutchAuctionOrderError = { errorCode?: number; detail?: string } type DutchAuctionOrderSuccess = { hash: string } @@ -54,7 +56,7 @@ export function useUniswapXSwapCallback({ fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number } allowedSlippage: Percent }) { - const { account, provider } = useWeb3React() + const { account, provider, connector } = useWeb3React() const analyticsContext = useTrace() const { data } = useCachedPortfolioBalancesQuery({ account }) @@ -122,6 +124,10 @@ export function useUniswapXSwapCallback({ portfolioBalanceUsd, }), ...analyticsContext, + // TODO (WEB-2993): remove these after debugging missing user properties. + [CustomUserProperties.WALLET_ADDRESS]: account, + [CustomUserProperties.WALLET_TYPE]: getConnection(connector).getName(), + [CustomUserProperties.PEER_WALLET_AGENT]: provider ? getWalletMeta(provider)?.agent : undefined, }) const res = await fetch(`${UNISWAP_API_URL}/order`, { @@ -160,6 +166,6 @@ export function useUniswapXSwapCallback({ response: { orderHash: body.hash, deadline: updatedOrder.info.deadline }, } }), - [account, provider, trade, allowedSlippage, fiatValues, analyticsContext, portfolioBalanceUsd] + [account, provider, trade, allowedSlippage, fiatValues, portfolioBalanceUsd, analyticsContext, connector] ) } diff --git a/src/hooks/useUniversalRouter.ts b/src/hooks/useUniversalRouter.ts index 1eae9882741..e15e3a33a81 100644 --- a/src/hooks/useUniversalRouter.ts +++ b/src/hooks/useUniversalRouter.ts @@ -1,12 +1,13 @@ import { BigNumber } from '@ethersproject/bignumber' import { t } from '@lingui/macro' -import { SwapEventName } from '@uniswap/analytics-events' +import { CustomUserProperties, SwapEventName } from '@uniswap/analytics-events' import { Percent } from '@uniswap/sdk-core' import { FlatFeeOptions, SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' import { FeeOptions, toHex } from '@uniswap/v3-sdk' import { useWeb3React } from '@web3-react/core' import { sendAnalyticsEvent, useTrace } from 'analytics' import { useCachedPortfolioBalancesQuery } from 'components/PrefetchBalancesWrapper/PrefetchBalancesWrapper' +import { getConnection } from 'connection' import useBlockNumber from 'lib/hooks/useBlockNumber' import { formatCommonPropertiesForTrade, formatSwapSignedAnalyticsEventProperties } from 'lib/utils/analytics' import { useCallback } from 'react' @@ -17,6 +18,7 @@ import { calculateGasMargin } from 'utils/calculateGasMargin' import { UserRejectedRequestError, WrongChainError } from 'utils/errors' import isZero from 'utils/isZero' import { didUserReject, swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage' +import { getWalletMeta } from 'utils/walletMeta' import { PermitSignature } from './usePermitAllowance' @@ -52,7 +54,7 @@ export function useUniversalRouterSwapCallback( fiatValues: { amountIn?: number; amountOut?: number; feeUsd?: number }, options: SwapOptions ) { - const { account, chainId, provider } = useWeb3React() + const { account, chainId, provider, connector } = useWeb3React() const analyticsContext = useTrace() const blockNumber = useBlockNumber() const isAutoSlippage = useUserSlippageTolerance()[0] === 'auto' @@ -125,6 +127,10 @@ export function useUniversalRouterSwapCallback( portfolioBalanceUsd, }), ...analyticsContext, + // TODO (WEB-2993): remove these after debugging missing user properties. + [CustomUserProperties.WALLET_ADDRESS]: account, + [CustomUserProperties.WALLET_TYPE]: getConnection(connector).getName(), + [CustomUserProperties.PEER_WALLET_AGENT]: provider ? getWalletMeta(provider)?.agent : undefined, }) if (tx.data !== response.data) { sendAnalyticsEvent(SwapEventName.SWAP_MODIFIED_IN_WALLET, { @@ -163,11 +169,16 @@ export function useUniversalRouterSwapCallback( chainId, provider, trade, - options, + options.slippageTolerance, + options.deadline, + options.permit, + options.feeOptions, + options.flatFeeOptions, analyticsContext, blockNumber, isAutoSlippage, fiatValues, portfolioBalanceUsd, + connector, ]) } From 226fc441a75b084bdfabdbe24304deef03c27c2f Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Fri, 20 Oct 2023 09:54:13 -0700 Subject: [PATCH 59/61] refactor: allow missing input for useSwapTaxes (#7498) --- src/hooks/useSwapTaxes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useSwapTaxes.ts b/src/hooks/useSwapTaxes.ts index 384fddaeb9e..588c29edc03 100644 --- a/src/hooks/useSwapTaxes.ts +++ b/src/hooks/useSwapTaxes.ts @@ -72,7 +72,7 @@ async function getSwapTaxes( return { inputTax, outputTax } } -export function useSwapTaxes(inputTokenAddress: string | undefined, outputTokenAddress: string | undefined) { +export function useSwapTaxes(inputTokenAddress?: string, outputTokenAddress?: string) { const fotDetector = useFeeOnTransferDetectorContract() const [{ inputTax, outputTax }, setTaxes] = useState({ inputTax: ZERO_PERCENT, outputTax: ZERO_PERCENT }) const { chainId } = useWeb3React() From 8734ee5986d9eebf1956b144ce3d086023e461f3 Mon Sep 17 00:00:00 2001 From: Kristie Huang Date: Fri, 20 Oct 2023 14:13:58 -0400 Subject: [PATCH 60/61] fix: dedupe matic native token (#7485) * fix: dedupe matic native token, wip * use precompile address * prefill swap currency with page chain, not connected chain * fix token-explore test --- cypress/e2e/token-explore.test.ts | 4 +- src/components/FiatOnrampModal/utils.test.ts | 6 +-- src/components/FiatOnrampModal/utils.ts | 4 +- src/constants/tokens.ts | 40 ++++++++++---------- src/pages/Swap/index.tsx | 4 +- src/state/routing/utils.ts | 4 +- src/utils/nativeTokens.ts | 1 + 7 files changed, 31 insertions(+), 32 deletions(-) diff --git a/cypress/e2e/token-explore.test.ts b/cypress/e2e/token-explore.test.ts index 036581b5497..2802a7ac02b 100644 --- a/cypress/e2e/token-explore.test.ts +++ b/cypress/e2e/token-explore.test.ts @@ -59,9 +59,7 @@ describe('Token explore', () => { // in metamask modal using plain cypress. this is a workaround. cy.visit('/tokens/polygon') cy.get(getTestSelector('tokens-network-filter-selected')).should('contain', 'Polygon') - cy.get(getTestSelector('token-table-row-NATIVE')) - .find(getTestSelector('name-cell')) - .should('include.text', 'Polygon Matic') + cy.get(getTestSelector('token-table-row-NATIVE')).find(getTestSelector('name-cell')).should('include.text', 'Matic') }) it('should update when token explore table network changed', () => { diff --git a/src/components/FiatOnrampModal/utils.test.ts b/src/components/FiatOnrampModal/utils.test.ts index 4cca77699c3..e20eb2c9d07 100644 --- a/src/components/FiatOnrampModal/utils.test.ts +++ b/src/components/FiatOnrampModal/utils.test.ts @@ -1,6 +1,6 @@ import { ChainId, WETH9 } from '@uniswap/sdk-core' import { - MATIC, + MATIC_MAINNET, USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, @@ -32,7 +32,7 @@ describe('getDefaultCurrencyCode', () => { expect(getDefaultCurrencyCode('NATIVE', 'polygon')).toBe('matic_polygon') }) it('MATIC/ethereum should return the correct currency code', () => { - expect(getDefaultCurrencyCode(MATIC.address, 'ethereum')).toBe('polygon') + expect(getDefaultCurrencyCode(MATIC_MAINNET.address, 'ethereum')).toBe('polygon') }) it('USDC/arbitrum should return the correct currency code', () => { expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'arbitrum')).toBe('usdc_arbitrum') @@ -56,7 +56,7 @@ describe('getDefaultCurrencyCode', () => { expect(getDefaultCurrencyCode(USDC_ARBITRUM.address, 'ethereum')).toBe('eth') expect(getDefaultCurrencyCode(USDC_OPTIMISM.address, 'ethereum')).toBe('eth') expect(getDefaultCurrencyCode(USDC_POLYGON.address, 'ethereum')).toBe('eth') - expect(getDefaultCurrencyCode(MATIC.address, 'arbitrum')).toBe('eth') + expect(getDefaultCurrencyCode(MATIC_MAINNET.address, 'arbitrum')).toBe('eth') }) }) diff --git a/src/components/FiatOnrampModal/utils.ts b/src/components/FiatOnrampModal/utils.ts index 7742c5e0b37..953ccb9e948 100644 --- a/src/components/FiatOnrampModal/utils.ts +++ b/src/components/FiatOnrampModal/utils.ts @@ -1,6 +1,6 @@ import { ChainId, WETH9 } from '@uniswap/sdk-core' import { - MATIC, + MATIC_MAINNET, USDC_ARBITRUM, USDC_MAINNET, USDC_OPTIMISM, @@ -28,7 +28,7 @@ const CURRENCY_CODES: { [USDC_MAINNET.address.toLowerCase()]: 'usdc', [USDT.address.toLowerCase()]: 'usdt', [WBTC.address.toLowerCase()]: 'wbtc', - [MATIC.address.toLowerCase()]: 'polygon', + [MATIC_MAINNET.address.toLowerCase()]: 'polygon', native: 'eth', }, [Chain.Arbitrum]: { diff --git a/src/constants/tokens.ts b/src/constants/tokens.ts index 5802786af25..071351ae017 100644 --- a/src/constants/tokens.ts +++ b/src/constants/tokens.ts @@ -90,13 +90,14 @@ export const DAI_OPTIMISM = new Token( 'DAI', 'Dai stable coin' ) -export const MATIC = new Token( +export const MATIC_MAINNET = new Token( ChainId.MAINNET, '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0', 18, 'MATIC', 'Polygon Matic' ) +const MATIC_POLYGON = new Token(ChainId.POLYGON, '0x0000000000000000000000000000000000001010', 18, 'MATIC', 'Matic') export const DAI_POLYGON = new Token( ChainId.POLYGON, '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', @@ -149,7 +150,13 @@ export const WBTC_OPTIMISM = new Token( 'WBTC', 'Wrapped BTC' ) - +const MATIC_POLYGON_MUMBAI = new Token( + ChainId.POLYGON_MUMBAI, + '0x0000000000000000000000000000000000001010', + 18, + 'MATIC', + 'Matic' +) export const WETH_POLYGON_MUMBAI = new Token( ChainId.POLYGON_MUMBAI, '0xa6fa4fb5f76172d178d61b04b0ecd319c5d1c0aa', @@ -355,25 +362,18 @@ function getCeloNativeCurrency(chainId: number) { } } -export function isMatic(chainId: number): chainId is ChainId.POLYGON | ChainId.POLYGON_MUMBAI { +export function isPolygon(chainId: number): chainId is ChainId.POLYGON | ChainId.POLYGON_MUMBAI { return chainId === ChainId.POLYGON_MUMBAI || chainId === ChainId.POLYGON } -class MaticNativeCurrency extends NativeCurrency { - equals(other: Currency): boolean { - return other.isNative && other.chainId === this.chainId - } - - get wrapped(): Token { - if (!isMatic(this.chainId)) throw new Error('Not matic') - const wrapped = WRAPPED_NATIVE_CURRENCY[this.chainId] - invariant(wrapped instanceof Token) - return wrapped - } - - public constructor(chainId: number) { - if (!isMatic(chainId)) throw new Error('Not matic') - super(chainId, 18, 'MATIC', 'Polygon Matic') +function getPolygonNativeCurrency(chainId: number) { + switch (chainId) { + case ChainId.POLYGON: + return MATIC_POLYGON + case ChainId.POLYGON_MUMBAI: + return MATIC_POLYGON_MUMBAI + default: + throw new Error('Not polygon') } } @@ -439,8 +439,8 @@ const cachedNativeCurrency: { [chainId: number]: NativeCurrency | Token } = {} export function nativeOnChain(chainId: number): NativeCurrency | Token { if (cachedNativeCurrency[chainId]) return cachedNativeCurrency[chainId] let nativeCurrency: NativeCurrency | Token - if (isMatic(chainId)) { - nativeCurrency = new MaticNativeCurrency(chainId) + if (isPolygon(chainId)) { + nativeCurrency = getPolygonNativeCurrency(chainId) } else if (isCelo(chainId)) { nativeCurrency = getCeloNativeCurrency(chainId) } else if (isBsc(chainId)) { diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 1436c8d50d9..d00593132aa 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -193,8 +193,8 @@ export function Swap({ const trace = useTrace() // token warning stuff - const prefilledInputCurrency = useCurrency(initialInputCurrencyId) - const prefilledOutputCurrency = useCurrency(initialOutputCurrencyId) + const prefilledInputCurrency = useCurrency(initialInputCurrencyId, chainId) + const prefilledOutputCurrency = useCurrency(initialOutputCurrencyId, chainId) const [loadedInputCurrency, setLoadedInputCurrency] = useState(prefilledInputCurrency) const [loadedOutputCurrency, setLoadedOutputCurrency] = useState(prefilledOutputCurrency) diff --git a/src/state/routing/utils.ts b/src/state/routing/utils.ts index 52d1fc3d12d..fba9e2ec853 100644 --- a/src/state/routing/utils.ts +++ b/src/state/routing/utils.ts @@ -5,7 +5,7 @@ import { DutchOrderInfo, DutchOrderInfoJSON } from '@uniswap/uniswapx-sdk' import { Pair, Route as V2Route } from '@uniswap/v2-sdk' import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk' import { BIPS_BASE } from 'constants/misc' -import { isAvalanche, isBsc, isMatic, nativeOnChain } from 'constants/tokens' +import { isAvalanche, isBsc, isPolygon, nativeOnChain } from 'constants/tokens' import { toSlippagePercent } from 'utils/slippage' import { getApproveInfo, getWrapInfo } from './gas' @@ -338,7 +338,7 @@ export function isExactInput(tradeType: TradeType): boolean { export function currencyAddressForSwapQuote(currency: Currency): string { if (currency.isNative) { - if (isMatic(currency.chainId)) return SwapRouterNativeAssets.MATIC + if (isPolygon(currency.chainId)) return SwapRouterNativeAssets.MATIC if (isBsc(currency.chainId)) return SwapRouterNativeAssets.BNB if (isAvalanche(currency.chainId)) return SwapRouterNativeAssets.AVAX return SwapRouterNativeAssets.ETH diff --git a/src/utils/nativeTokens.ts b/src/utils/nativeTokens.ts index 5b796ef09c5..c16bb86c6c3 100644 --- a/src/utils/nativeTokens.ts +++ b/src/utils/nativeTokens.ts @@ -8,6 +8,7 @@ export function getNativeTokenDBAddress(chain: Chain): string | undefined { return undefined } switch (chain) { + // Celo & Polygon have precompiles for their native tokens case Chain.Celo: case Chain.Polygon: return nativeOnChain(pageChainId).wrapped.address From 6798bf3cf1a4bb28ba0f629cc6a48167d7b8bee9 Mon Sep 17 00:00:00 2001 From: Charles Bachmeier Date: Fri, 20 Oct 2023 13:11:22 -0700 Subject: [PATCH 61/61] feat: [info] add PDP loading skeleton (#7494) * initial skeleton setup * responsive table skeleton * correct table widht * right side column added * add comments * move loading components to their corresponding component * remove extra bubble and adjust some styles * move table skeleton to its own file * add shared styles and skele file * add loading skeleton tests * design style nits * update tests * bips_base * fix regression --- .../PoolDetails/PoolDetailsHeader.test.tsx | 7 + src/pages/PoolDetails/PoolDetailsHeader.tsx | 24 + src/pages/PoolDetails/PoolDetailsStats.tsx | 75 +- .../PoolDetailsStatsButtons.test.tsx | 7 + .../PoolDetails/PoolDetailsStatsButtons.tsx | 21 +- .../PoolDetails/PoolDetailsTableSkeleton.tsx | 98 ++ .../PoolDetailsHeader.test.tsx.snap | 115 ++ .../PoolDetailsStatsButtons.test.tsx.snap | 70 + .../__snapshots__/index.test.tsx.snap | 1455 ++++++++++++++--- src/pages/PoolDetails/index.test.tsx | 3 +- src/pages/PoolDetails/index.tsx | 99 +- src/pages/PoolDetails/shared.ts | 13 + src/pages/RouteDefinitions.tsx | 6 +- 13 files changed, 1696 insertions(+), 297 deletions(-) create mode 100644 src/pages/PoolDetails/PoolDetailsTableSkeleton.tsx create mode 100644 src/pages/PoolDetails/shared.ts diff --git a/src/pages/PoolDetails/PoolDetailsHeader.test.tsx b/src/pages/PoolDetails/PoolDetailsHeader.test.tsx index 61ca771c64b..67949764443 100644 --- a/src/pages/PoolDetails/PoolDetailsHeader.test.tsx +++ b/src/pages/PoolDetails/PoolDetailsHeader.test.tsx @@ -13,6 +13,13 @@ describe('PoolDetailsHeader', () => { toggleReversed: jest.fn(), } + it('loading skeleton is shown', () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.getByTestId('pdp-header-loading-skeleton')).toBeInTheDocument() + }) + it('renders header text correctly', () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() diff --git a/src/pages/PoolDetails/PoolDetailsHeader.tsx b/src/pages/PoolDetails/PoolDetailsHeader.tsx index 3589831b215..81672bcc2ab 100644 --- a/src/pages/PoolDetails/PoolDetailsHeader.tsx +++ b/src/pages/PoolDetails/PoolDetailsHeader.tsx @@ -4,6 +4,7 @@ import blankTokenUrl from 'assets/svg/blank_token.svg' import Column from 'components/Column' import { ChainLogo } from 'components/Logo/ChainLogo' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' import { BIPS_BASE } from 'constants/misc' import { chainIdToBackendName } from 'graphql/data/util' import { useCurrency } from 'hooks/Tokens' @@ -15,6 +16,7 @@ import { ClickableStyle, ThemedText } from 'theme/components' import { shortenAddress } from 'utils' import { ReversedArrowsIcon } from './icons' +import { DetailBubble } from './shared' const HeaderColumn = styled(Column)` gap: 36px; @@ -35,6 +37,12 @@ const ToggleReverseArrows = styled(ReversedArrowsIcon)` ${ClickableStyle} ` +const IconBubble = styled(LoadingBubble)` + width: 32px; + height: 32px; + border-radius: 50%; +` + interface Token { id: string symbol: string @@ -47,6 +55,7 @@ interface PoolDetailsHeaderProps { token1?: Token feeTier?: number toggleReversed: React.DispatchWithoutAction + loading?: boolean } export function PoolDetailsHeader({ @@ -56,10 +65,25 @@ export function PoolDetailsHeader({ token1, feeTier, toggleReversed, + loading, }: PoolDetailsHeaderProps) { const currencies = [useCurrency(token0?.id, chainId) ?? undefined, useCurrency(token1?.id, chainId) ?? undefined] const chainName = chainIdToBackendName(chainId) const origin = `/tokens/${chainName}` + + if (loading) + return ( + + + + + + + + + + ) + return ( diff --git a/src/pages/PoolDetails/PoolDetailsStats.tsx b/src/pages/PoolDetails/PoolDetailsStats.tsx index cbf58f9c5aa..b83cb4c2d3c 100644 --- a/src/pages/PoolDetails/PoolDetailsStats.tsx +++ b/src/pages/PoolDetails/PoolDetailsStats.tsx @@ -2,6 +2,7 @@ import { Trans } from '@lingui/macro' import Column from 'components/Column' import CurrencyLogo from 'components/Logo/CurrencyLogo' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' import { DeltaArrow } from 'components/Tokens/TokenDetails/Delta' import { PoolData } from 'graphql/thegraph/PoolData' import { useCurrency } from 'hooks/Tokens' @@ -15,6 +16,8 @@ import { colors } from 'theme/colors' import { ThemedText } from 'theme/components' import { NumberType, useFormatter } from 'utils/formatNumbers' +import { DetailBubble } from './shared' + const HeaderText = styled(Text)` font-weight: 485; font-size: 24px; @@ -90,13 +93,25 @@ const BalanceChartSide = styled.div<{ percent: number; $color: string; isLeft: b ${({ isLeft }) => (isLeft ? leftBarChartStyles : rightBarChartStyles)} ` +const StatSectionBubble = styled(LoadingBubble)` + width: 180px; + height: 40px; +` + +const StatHeaderBubble = styled(LoadingBubble)` + width: 116px; + height: 24px; + border-radius: 8px; +` + interface PoolDetailsStatsProps { - poolData: PoolData - isReversed: boolean + poolData?: PoolData + isReversed?: boolean chainId?: number + loading?: boolean } -export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsStatsProps) { +export function PoolDetailsStats({ poolData, isReversed, chainId, loading }: PoolDetailsStatsProps) { const isScreenSize = useScreenSize() const screenIsNotLarge = isScreenSize['lg'] const { formatNumber } = useFormatter() @@ -112,26 +127,46 @@ export function PoolDetailsStats({ poolData, isReversed, chainId }: PoolDetailsS } const [token0, token1] = useMemo(() => { - const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1 - const token0FullData = { - ...poolData?.token0, - price: poolData?.token0Price, - tvl: poolData?.tvlToken0, - color: color0, - percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth, - currency: currency0, - } - const token1FullData = { - ...poolData?.token1, - price: poolData?.token1Price, - tvl: poolData?.tvlToken1, - color: color1, - percent: poolData?.tvlToken1 / fullWidth, - currency: currency1, + if (poolData) { + const fullWidth = poolData?.tvlToken0 / poolData?.token0Price + poolData?.tvlToken1 + const token0FullData = { + ...poolData?.token0, + price: poolData?.token0Price, + tvl: poolData?.tvlToken0, + color: color0, + percent: poolData?.tvlToken0 / poolData?.token0Price / fullWidth, + currency: currency0, + } + const token1FullData = { + ...poolData?.token1, + price: poolData?.token1Price, + tvl: poolData?.tvlToken1, + color: color1, + percent: poolData?.tvlToken1 / fullWidth, + currency: currency1, + } + return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData] + } else { + return [undefined, undefined] } - return isReversed ? [token1FullData, token0FullData] : [token0FullData, token1FullData] }, [color0, color1, currency0, currency1, isReversed, poolData]) + if (loading || !token0 || !token1 || !poolData) { + return ( + + + + + {Array.from({ length: 4 }).map((_, i) => ( + + + + + ))} + + ) + } + return ( diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx index 356a7e75d52..70e88857c5e 100644 --- a/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx +++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.test.tsx @@ -26,6 +26,13 @@ describe('PoolDetailsStatsButton', () => { mocked(useMultiChainPositions).mockReturnValue(useMultiChainPositionsReturnValue) }) + it('loading skeleton shown correctly', () => { + const { asFragment } = render() + expect(asFragment()).toMatchSnapshot() + + expect(screen.getByTestId('pdp-buttons-loading-skeleton')).toBeVisible() + }) + it('renders both buttons correctly', () => { const { asFragment } = render() expect(asFragment()).toMatchSnapshot() diff --git a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx index 497b202725d..dae1bf8c42e 100644 --- a/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx +++ b/src/pages/PoolDetails/PoolDetailsStatsButtons.tsx @@ -4,6 +4,7 @@ import { PositionInfo } from 'components/AccountDrawer/MiniPortfolio/Pools/cache import useMultiChainPositions from 'components/AccountDrawer/MiniPortfolio/Pools/useMultiChainPositions' import { ButtonEmphasis, ButtonSize, ThemeButton } from 'components/Button' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' import { Token } from 'graphql/thegraph/__generated__/types-and-hooks' import { useCurrency } from 'hooks/Tokens' import { useSwitchChain } from 'hooks/useSwitchChain' @@ -26,11 +27,18 @@ const PoolButton = styled(ThemeButton)` width: 50%; ` +const ButtonBubble = styled(LoadingBubble)` + height: 44px; + width: 175px; + border-radius: 900px; +` + interface PoolDetailsStatsButtonsProps { chainId?: number token0?: Token token1?: Token feeTier?: number + loading?: boolean } function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1?: Token, feeTier?: number) { @@ -45,7 +53,7 @@ function findMatchingPosition(positions: PositionInfo[], token0?: Token, token1? ) } -export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: PoolDetailsStatsButtonsProps) { +export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier, loading }: PoolDetailsStatsButtonsProps) { const { chainId: walletChainId, connector, account } = useWeb3React() const { positions: userOwnedPositions } = useMultiChainPositions(account ?? '', chainId ? [chainId] : undefined) const position = userOwnedPositions && findMatchingPosition(userOwnedPositions, token0, token1, feeTier) @@ -64,7 +72,15 @@ export function PoolDetailsStatsButtons({ chainId, token0, token1, feeTier }: Po ) } } - if (!currency0 || !currency1) return null + + if (loading || !currency0 || !currency1) + return ( + + + + + ) + return ( Add liquidity - theme.surface3}; + padding-bottom: 12px; + overflow-y: hidden; + ${ScrollBarStyles} +` + +const TableRow = styled(Row)<{ $borderBottom?: boolean }>` + justify-content: space-between; + border-bottom: ${({ $borderBottom, theme }) => ($borderBottom ? `1px solid ${theme.surface3}` : 'none')}}; + padding: 12px; + min-width: max-content; +` + +const TableElement = styled(ThemedText.BodySecondary)<{ + alignRight?: boolean + small?: boolean + large?: boolean +}>` + display: flex; + padding: 0px 8px; + flex: ${({ small }) => (small ? 'unset' : '1')}; + width: ${({ small }) => (small ? '44px' : 'auto')}; + min-width: ${({ large, small }) => (large ? '136px' : small ? 'unset' : '121px')} !important; + justify-content: ${({ alignRight }) => (alignRight ? 'flex-end' : 'flex-start')}; +` +{ + /* TODO(WEB-2735): When making real datatable, merge in this code and deprecate this skeleton file */ +} +export function PoolDetailsTableSkeleton() { + return ( + + + + + + Time + + + + Type + + + USD + + + + + + + + + Maker + + + Txn + + + {Array.from({ length: 10 }).map((_, i) => ( + + + + + + + + + + + + + + + + + + + + + + + + ))} +
+ ) +} diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap index 33be9190b32..5f635c21f0d 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsHeader.test.tsx.snap @@ -1,5 +1,120 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`PoolDetailsHeader loading skeleton is shown 1`] = ` + + .c5 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c6 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + +.c0 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + +.c2 { + border-radius: 12px; + border-radius: 12px; + height: 24px; + width: 50%; + width: 50%; + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% ); + will-change: background-position; + background-size: 400%; +} + +.c3 { + height: 16px; + width: 300px; +} + +.c8 { + height: 16px; + width: 137px; +} + +.c1 { + gap: 36px; +} + +.c7 { + width: 32px; + height: 32px; + border-radius: 50%; +} + +
+
+
+
+
+
+
+
+
+ +`; + exports[`PoolDetailsHeader renders header text correctly 1`] = ` .c2 { diff --git a/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap index 2b8fcc5fb74..a6f743b439f 100644 --- a/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/PoolDetailsStatsButtons.test.tsx.snap @@ -1,5 +1,75 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`PoolDetailsStatsButton loading skeleton shown correctly 1`] = ` + + .c0 { + box-sizing: border-box; + margin: 0; + min-width: 0; +} + +.c1 { + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c3 { + border-radius: 12px; + border-radius: 12px; + height: 24px; + width: 50%; + width: 50%; + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% ); + will-change: background-position; + background-size: 400%; +} + +.c2 { + gap: 12px; +} + +.c4 { + height: 44px; + width: 175px; + border-radius: 900px; +} + +@media (max-width:1023px) { + .c2 { + display: none; + } +} + +
+
+
+
+ +`; + exports[`PoolDetailsStatsButton renders both buttons correctly 1`] = ` .c0 { diff --git a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap index 81a57f01ea9..b4e98fb6e06 100644 --- a/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap +++ b/src/pages/PoolDetails/__snapshots__/index.test.tsx.snap @@ -8,7 +8,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the min-width: 0; } -.c9 { +.c11 { box-sizing: border-box; margin: 0; min-width: 0; @@ -17,7 +17,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: max-content; } -.c31 { +.c52 { box-sizing: border-box; margin: 0; min-width: 0; @@ -44,7 +44,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the justify-content: flex-start; } -.c8 { +.c10 { width: 100%; display: -webkit-box; display: -webkit-flex; @@ -62,7 +62,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the gap: 18px; } -.c10 { +.c12 { width: -webkit-max-content; width: -moz-max-content; width: max-content; @@ -82,7 +82,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the gap: 8px; } -.c32 { +.c53 { width: -webkit-max-content; width: -moz-max-content; width: max-content; @@ -102,15 +102,15 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the padding: 4px 0px; } -.c6 { +.c8 { color: #7D7D7D; } -.c7 { +.c9 { color: #222222; } -.c43 { +.c64 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -121,11 +121,11 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the font-weight: 500; } -.c43:hover { +.c64:hover { opacity: 0.6; } -.c43:active { +.c64:active { opacity: 0.4; } @@ -143,7 +143,113 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the justify-content: flex-start; } -.c40 { +.c5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + gap: 8px; +} + +.c19 { + border-radius: 12px; + border-radius: 12px; + height: 24px; + width: 50%; + width: 50%; + -webkit-animation: fAQEyV 1.5s infinite; + animation: fAQEyV 1.5s infinite; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: linear-gradient( to left, #22222212 25%, rgba(53,53,53,0.07) 50%, #22222212 75% ); + will-change: background-position; + background-size: 400%; +} + +.c42 { + background-color: transparent; + bottom: 0; + border-radius: inherit; + height: 100%; + left: 0; + position: absolute; + right: 0; + top: 0; + -webkit-transition: 150ms ease background-color; + transition: 150ms ease background-color; + width: 100%; +} + +.c39 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background-color: #FFEFFF; + border-radius: 16px; + border: 0; + color: #FC72FF; + cursor: pointer; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + font-size: 16px; + font-weight: 535; + gap: 12px; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + line-height: 20px; + padding: 10px 12px; + position: relative; + -webkit-transition: 150ms ease opacity; + transition: 150ms ease opacity; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c39:active .c41 { + background-color: #B8C0DC3d; +} + +.c39:focus .c41 { + background-color: #B8C0DC3d; +} + +.c39:hover .c41 { + background-color: #98A1C014; +} + +.c39:disabled { + cursor: default; + opacity: 0.6; +} + +.c39:disabled:active .c41, +.c39:disabled:focus .c41, +.c39:disabled:hover .c41 { + background-color: transparent; +} + +.c54 { + color: #FF5F52; +} + +.c61 { opacity: 0; -webkit-transition: opacity 250ms ease-in; transition: opacity 250ms ease-in; @@ -152,7 +258,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the border-radius: 50%; } -.c39 { +.c60 { width: 20px; height: 20px; background: #22222212; @@ -162,7 +268,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the border-radius: 50%; } -.c38 { +.c59 { position: relative; display: -webkit-box; display: -webkit-flex; @@ -170,35 +276,100 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the display: flex; } -.c46 { +.c67 { color: #CECECE; font-weight: 485; font-size: 16px; } -.c36 { +.c18 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: 436px; + margin-bottom: 24px; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + width: 100%; +} + +.c24 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: flex-end; + -webkit-box-align: flex-end; + -ms-flex-align: flex-end; + align-items: flex-end; + height: 100%; + margin-bottom: 44px; + padding-bottom: 66px; + overflow: hidden; +} + +.c20 { + height: 16px; + width: 180px; +} + +.c21 { + height: 32px; + border-radius: 8px; +} + +.c22 { + margin-top: 4px; + height: 40px; +} + +.c25 { + -webkit-animation: wave 8s cubic-bezier(0.36,0.45,0.63,0.53) infinite; + animation: wave 8s cubic-bezier(0.36,0.45,0.63,0.53) infinite; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + overflow: hidden; + margin-top: 90px; +} + +.c23 { + height: 6px; +} + +.c57 { gap: 12px; width: 100%; } -.c37 { +.c58 { gap: 8px; width: 100%; } -.c41 { +.c62 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.c42 { +.c63 { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; } -.c44 { +.c65 { gap: 8px; padding: 8px 12px; border-radius: 20px; @@ -217,15 +388,15 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the transition-duration: 125ms; } -.c44:hover { +.c65:hover { opacity: 0.6; } -.c44:active { +.c65:active { opacity: 0.4; } -.c45 { +.c66 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -234,83 +405,22 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the white-space: pre-wrap; } -.c21 { - background-color: transparent; - bottom: 0; - border-radius: inherit; - height: 100%; - left: 0; - position: absolute; - right: 0; - top: 0; - -webkit-transition: 150ms ease background-color; - transition: 150ms ease background-color; - width: 100%; -} - -.c18 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - background-color: #FFEFFF; - border-radius: 16px; - border: 0; - color: #FC72FF; - cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - font-size: 16px; - font-weight: 535; - gap: 12px; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; - line-height: 20px; - padding: 10px 12px; - position: relative; - -webkit-transition: 150ms ease opacity; - transition: 150ms ease opacity; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.c18:active .c20 { - background-color: #B8C0DC3d; -} - -.c18:focus .c20 { - background-color: #B8C0DC3d; -} - -.c18:hover .c20 { - background-color: #98A1C014; -} - -.c18:disabled { - cursor: default; - opacity: 0.6; +.c33 { + height: 16px; + width: 80px; } -.c18:disabled:active .c20, -.c18:disabled:focus .c20, -.c18:disabled:hover .c20 { - background-color: transparent; +.c36 { + height: 20px; + width: 20px; + border-radius: 100px; } -.c4 { +.c6 { gap: 36px; } -.c5 { +.c7 { -webkit-text-decoration: none; text-decoration: none; -webkit-text-decoration: none; @@ -320,21 +430,21 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the transition-duration: 125ms; } -.c5:hover { +.c7:hover { opacity: 0.6; } -.c5:active { +.c7:active { opacity: 0.4; } -.c14 { +.c16 { background: #F9F9F9; padding: 2px 6px; border-radius: 4px; } -.c15 { +.c17 { -webkit-text-decoration: none; text-decoration: none; cursor: pointer; @@ -342,21 +452,21 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the transition-duration: 125ms; } -.c15:hover { +.c17:hover { opacity: 0.6; } -.c15:active { +.c17:active { opacity: 0.4; } -.c11 { +.c13 { position: relative; top: 0; left: 0; } -.c12 { +.c14 { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; @@ -367,39 +477,35 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the left: 0; } -.c12 img { +.c14 img { width: 16px; height: 32px; object-fit: cover; } -.c12 img:first-child { +.c14 img:first-child { border-radius: 16px 0 0 16px; object-position: 0 0; } -.c12 img:last-child { +.c14 img:last-child { border-radius: 0 16px 16px 0; object-position: 100% 0; } -.c13 { +.c15 { width: 32px; height: 32px; border-radius: 50%; } -.c33 { - color: #FF5F52; -} - -.c23 { +.c44 { font-weight: 485; font-size: 24px; line-height: 36px; } -.c22 { +.c43 { gap: 24px; padding: 20px; border-radius: 20px; @@ -407,7 +513,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: 100%; } -.c24 { +.c45 { gap: 8px; -webkit-flex: 1; -ms-flex: 1; @@ -415,14 +521,14 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the min-width: 180px; } -.c25 { +.c46 { -webkit-box-pack: justify; -webkit-justify-content: space-between; -ms-flex-pack: justify; justify-content: space-between; } -.c26 { +.c47 { font-weight: 485; font-size: 18px; line-height: 24px; @@ -431,7 +537,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the width: max-content; } -.c27 { +.c48 { height: 8px; width: 40.698463777008904%; background: #FC72FF; @@ -440,7 +546,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the border-right: 1px solid #F9F9F9; } -.c28 { +.c49 { height: 8px; width: 59.3015362229911%; background: #4C82FB; @@ -449,7 +555,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the border-left: 1px solid #F9F9F9; } -.c29 { +.c50 { gap: 4px; width: 100%; -webkit-align-items: flex-end; @@ -458,23 +564,143 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the align-items: flex-end; } -.c30 { +.c51 { color: #222222; font-size: 36px; font-weight: 485; line-height: 44px; } -.c17 { +.c38 { gap: 12px; } -.c19 { +.c40 { padding: 12px 16px 12px 12px; border-radius: 900px; width: 50%; } +.c28 { + gap: 24px; + border-radius: 20px; + border: 1px solid #22222212; + padding-bottom: 12px; + overflow-y: hidden; + -webkit-scrollbar-width: thin; + -moz-scrollbar-width: thin; + -ms-scrollbar-width: thin; + scrollbar-width: thin; + -webkit-scrollbar-color: #22222212 transparent; + -moz-scrollbar-color: #22222212 transparent; + -ms-scrollbar-color: #22222212 transparent; + scrollbar-color: #22222212 transparent; + height: 100%; +} + +.c28::-webkit-scrollbar { + background: transparent; + height: 4px; + overflow-x: scroll; +} + +.c28::-webkit-scrollbar-thumb { + background: #22222212; + border-radius: 8px; +} + +.c29 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + border-bottom: 1px solid #22222212; + padding: 12px; + min-width: -webkit-max-content; + min-width: -moz-max-content; + min-width: max-content; +} + +.c35 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + border-bottom: none; + padding: 12px; + min-width: -webkit-max-content; + min-width: -moz-max-content; + min-width: max-content; +} + +.c30 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: auto; + min-width: 136px !important; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c31 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: auto; + min-width: 121px !important; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c32 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: auto; + min-width: 121px !important; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; +} + +.c34 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 0px 8px; + -webkit-flex: unset; + -ms-flex: unset; + flex: unset; + width: 44px; + min-width: unset !important; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; +} + .c2 { padding: 48px; width: 100%; @@ -482,21 +708,43 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the -webkit-box-align: flex-start; -ms-flex-align: flex-start; align-items: flex-start; + gap: 60px; } -.c16 { +.c4 { + gap: 24px; + width: 65vw; + overflow: hidden; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.c26 { + border: 0.5px solid #22222212; + margin: 16px 0px; + width: 100%; +} + +.c27 { + width: 180px; + height: 32px; +} + +.c37 { gap: 24px; margin: 0 48px 0 auto; width: 22vw; min-width: 360px; } -.c34 { +.c55 { gap: 24px; padding: 20px; } -.c35 { +.c56 { width: 100%; font-size: 24px; font-weight: 485; @@ -504,19 +752,19 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) and (min-width:640px) { - .c36 { + .c57 { max-width: 45%; } } @media (max-width:1023px) { - .c23 { + .c44 { width: 100%; } } @media (max-width:1023px) { - .c22 { + .c43 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -533,13 +781,13 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:640px) { - .c24 { + .c45 { min-width: 150px; } } @media (max-width:1023px) { - .c25 { + .c46 { -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; @@ -547,7 +795,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c26 { + .c47 { font-size: 20px; line-height: 28px; width: 100%; @@ -555,7 +803,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c29 { + .c50 { -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; @@ -568,14 +816,14 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c30 { + .c51 { font-size: 20px; line-height: 28px; } } @media (max-width:1023px) { - .c17 { + .c38 { display: none; } } @@ -585,6 +833,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column; + gap: unset; } } @@ -595,7 +844,13 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) { - .c16 { + .c4 { + width: 100%; + } +} + +@media (max-width:1023px) { + .c37 { margin: 44px 0px; width: 100%; min-width: unset; @@ -603,7 +858,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:1023px) and (min-width:640px) { - .c34 { + .c55 { -webkit-flex-direction: row; -ms-flex-direction: row; flex-direction: row; @@ -615,7 +870,7 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the } @media (max-width:639px) { - .c34 { + .c55 { padding: unset; } } @@ -627,147 +882,847 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the class="c3 c4" >
-
- Explore + +
+ Explore +
+
+
+  >  +
+ +
+ Pool +
+
+
+  >  +
+
+ USDC / WETH (0x88e6...5640) +
- -
-  >  -
-
- Pool +
+
+
+ + +
+
+
+ USDC / WETH +
+
+
+ 0.05% +
+ + +
-
-
-  > 
- USDC / WETH (0x88e6...5640) +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+
+
+
+
- - + + + + + Time
+ Type +
+
+ USD +
+
+
+
+
+
+
+
- USDC / WETH + Maker +
+
+ Txn
- 0.05% -
- - - +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Stats
Pool balances
90.93M USDC
82,526.49 WETH
@@ -777,36 +1732,36 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the data-testid="pool-balance-chart" >
TVL
$223.2M
0.37%
@@ -827,28 +1782,28 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
24H volume
$233.4M
17.75%
@@ -869,18 +1824,18 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
24H fees
$116.7K
@@ -888,56 +1843,56 @@ exports[`PoolDetailsPage pool header is displayed when data is received from the
Info
UNKNOWN logo
Unknown Token
UNKNOWN
WETH logo
Wrapped Ether
WETH
No token information available diff --git a/src/pages/PoolDetails/index.test.tsx b/src/pages/PoolDetails/index.test.tsx index ce6b343c022..093dd1b7810 100644 --- a/src/pages/PoolDetails/index.test.tsx +++ b/src/pages/PoolDetails/index.test.tsx @@ -76,7 +76,6 @@ describe('PoolDetailsPage', () => { }) }) - // TODO replace with loading skeleton when designed it('nothing displayed while data is loading', () => { mocked(usePoolData).mockReturnValue({ data: undefined, @@ -86,7 +85,7 @@ describe('PoolDetailsPage', () => { render() waitFor(() => { - expect(screen.getByText(/not found/i)).not.toBeInTheDocument() + expect(screen.getByTestId('pdp-links-loading-skeleton')).toBeInTheDocument() }) }) diff --git a/src/pages/PoolDetails/index.tsx b/src/pages/PoolDetails/index.tsx index 971e42be178..e5420f2c1e9 100644 --- a/src/pages/PoolDetails/index.tsx +++ b/src/pages/PoolDetails/index.tsx @@ -1,6 +1,8 @@ import { Trans } from '@lingui/macro' import Column from 'components/Column' import Row from 'components/Row' +import { LoadingBubble } from 'components/Tokens/loading' +import { LoadingChart } from 'components/Tokens/TokenDetails/Skeleton' import { TokenDescription } from 'components/Tokens/TokenDetails/TokenDescription' import { getValidUrlChainName, supportedChainIdFromGQLChain } from 'graphql/data/util' import { usePoolData } from 'graphql/thegraph/PoolData' @@ -15,14 +17,18 @@ import { isAddress } from 'utils' import { PoolDetailsHeader } from './PoolDetailsHeader' import { PoolDetailsStats } from './PoolDetailsStats' import { PoolDetailsStatsButtons } from './PoolDetailsStatsButtons' +import { PoolDetailsTableSkeleton } from './PoolDetailsTableSkeleton' +import { DetailBubble, SmallDetailBubble } from './shared' const PageWrapper = styled(Row)` padding: 48px; width: 100%; align-items: flex-start; + gap: 60px; @media (max-width: ${BREAKPOINTS.lg - 1}px) { flex-direction: column; + gap: unset; } @media (max-width: ${BREAKPOINTS.sm - 1}px) { @@ -30,6 +36,33 @@ const PageWrapper = styled(Row)` } ` +const LeftColumn = styled(Column)` + gap: 24px; + width: 65vw; + overflow: hidden; + justify-content: flex-start; + + @media (max-width: ${BREAKPOINTS.lg - 1}px) { + width: 100%; + } +` + +const HR = styled.hr` + border: 0.5px solid ${({ theme }) => theme.surface3}; + margin: 16px 0px; + width: 100%; +` + +const ChartHeaderBubble = styled(LoadingBubble)` + width: 180px; + height: 32px; +` + +const LinkColumn = styled(Column)` + gap: 16px; + padding: 20px; +` + const RightColumn = styled(Column)` gap: 24px; margin: 0 48px 0 auto; @@ -79,31 +112,55 @@ export default function PoolDetailsPage() { const isInvalidPool = !chainName || !poolAddress || !getValidUrlChainName(chainName) || !isAddress(poolAddress) const poolNotFound = (!loading && !poolData) || isInvalidPool - // TODO(WEB-2814): Add skeleton once designed - if (loading) return null if (poolNotFound) return return ( - + + + + + +
+ + +
- - {poolData && } - {(token0 || token1) && ( - - - Info - - {token0 && } - {token1 && } - - )} + + + {(token0 || token1 || loading) && + (loading ? ( + + + {Array.from({ length: 3 }).map((_, i) => ( + + + + + ))} + + ) : ( + + + Info + + {token0 && } + {token1 && } + + ))}
) diff --git a/src/pages/PoolDetails/shared.ts b/src/pages/PoolDetails/shared.ts new file mode 100644 index 00000000000..a787f33a96a --- /dev/null +++ b/src/pages/PoolDetails/shared.ts @@ -0,0 +1,13 @@ +import { LoadingBubble } from 'components/Tokens/loading' +import styled from 'styled-components' + +export const DetailBubble = styled(LoadingBubble)<{ $height?: number; $width?: number }>` + height: ${({ $height }) => ($height ? `${$height}px` : '16px')}; + width: ${({ $width }) => ($width ? `${$width}px` : '80px')}; +` + +export const SmallDetailBubble = styled(LoadingBubble)` + height: 20px; + width: 20px; + border-radius: 100px; +` diff --git a/src/pages/RouteDefinitions.tsx b/src/pages/RouteDefinitions.tsx index fa713a3b353..0ff735ad111 100644 --- a/src/pages/RouteDefinitions.tsx +++ b/src/pages/RouteDefinitions.tsx @@ -139,7 +139,11 @@ export const routes: RouteDefinition[] = [ }), createRouteDefinition({ path: 'explore/pools/:chainName/:poolAddress', - getElement: () => , + getElement: () => ( + + + + ), enabled: (args) => Boolean(args.infoExplorePageEnabled && args.infoPoolPageEnabled), }), createRouteDefinition({